diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 19:05:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 19:05:44 +0000 |
commit | b045529c40c83601909dca7b76a53498e9a70f33 (patch) | |
tree | 88371572105933fd950676c07b3a12163a0c9de0 /src/contrib | |
parent | Initial commit. (diff) | |
download | knot-b045529c40c83601909dca7b76a53498e9a70f33.tar.xz knot-b045529c40c83601909dca7b76a53498e9a70f33.zip |
Adding upstream version 3.3.4.upstream/3.3.4
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/contrib')
173 files changed, 59400 insertions, 0 deletions
diff --git a/src/contrib/Makefile.inc b/src/contrib/Makefile.inc new file mode 100644 index 0000000..c316b5f --- /dev/null +++ b/src/contrib/Makefile.inc @@ -0,0 +1,222 @@ +noinst_LTLIBRARIES += libcontrib.la + +libcontrib_la_CPPFLAGS = $(AM_CPPFLAGS) $(CFLAG_VISIBILITY) +libcontrib_la_LDFLAGS = $(AM_LDFLAGS) $(LDFLAG_EXCLUDE_LIBS) +libcontrib_la_LIBADD = $(pthread_LIBS) +libcontrib_LIBS = libcontrib.la +if USE_GNUTLS_MEMSET +libcontrib_la_CPPFLAGS += $(gnutls_CFLAGS) +libcontrib_LIBS += $(gnutls_LIBS) +endif USE_GNUTLS_MEMSET + +EXTRA_DIST += \ + contrib/licenses/0BSD \ + contrib/licenses/BSD-3-Clause \ + contrib/licenses/LGPL-2.0 \ + contrib/licenses/MIT \ + contrib/musl/LICENSE \ + contrib/libngtcp2/LICENSE \ + contrib/openbsd/LICENSE \ + contrib/ucw/LICENSE \ + contrib/url-parser/LICENSE \ + contrib/url-parser/README.md \ + contrib/dnstap/dnstap.proto + +libcontrib_la_SOURCES = \ + contrib/asan.h \ + contrib/base32hex.c \ + contrib/base32hex.h \ + contrib/base64.c \ + contrib/base64.h \ + contrib/base64url.c \ + contrib/base64url.h \ + contrib/conn_pool.c \ + contrib/conn_pool.h \ + contrib/color.h \ + contrib/ctype.h \ + contrib/files.c \ + contrib/files.h \ + contrib/getline.c \ + contrib/getline.h \ + contrib/json.c \ + contrib/json.h \ + contrib/macros.h \ + contrib/mempattern.c \ + contrib/mempattern.h \ + contrib/musl/inet_ntop.c \ + contrib/musl/inet_ntop.h \ + contrib/net.c \ + contrib/net.h \ + contrib/os.h \ + contrib/qp-trie/trie.c \ + contrib/qp-trie/trie.h \ + contrib/semaphore.c \ + contrib/semaphore.h \ + contrib/sockaddr.c \ + contrib/sockaddr.h \ + contrib/spinlock.h \ + contrib/string.c \ + contrib/string.h \ + contrib/strtonum.h \ + contrib/time.c \ + contrib/time.h \ + contrib/toeplitz.h \ + contrib/tolower.h \ + contrib/trim.h \ + contrib/wire_ctx.h \ + contrib/openbsd/siphash.c \ + contrib/openbsd/siphash.h \ + contrib/openbsd/strlcat.c \ + contrib/openbsd/strlcat.h \ + contrib/openbsd/strlcpy.c \ + contrib/openbsd/strlcpy.h \ + contrib/proxyv2/proxyv2.c \ + contrib/proxyv2/proxyv2.h \ + contrib/ucw/array-sort.h \ + contrib/ucw/binsearch.h \ + contrib/ucw/heap.c \ + contrib/ucw/heap.h \ + contrib/ucw/lists.c \ + contrib/ucw/lists.h \ + contrib/ucw/mempool.c \ + contrib/ucw/mempool.h \ + contrib/url-parser/url_parser.c \ + contrib/url-parser/url_parser.h \ + contrib/vpool/vpool.c \ + contrib/vpool/vpool.h + +if HAVE_LIBDNSTAP +noinst_LTLIBRARIES += libdnstap.la + +libdnstap_la_CPPFLAGS = $(AM_CPPFLAGS) $(CFLAG_VISIBILITY) $(DNSTAP_CFLAGS) +libdnstap_la_LDFLAGS = $(AM_LDFLAGS) $(LDFLAG_EXCLUDE_LIBS) +libdnstap_LIBS = libdnstap.la $(DNSTAP_LIBS) + +SUFFIXES = .proto .pb-c.c .pb-c.h + +.proto.pb-c.c: + $(AM_V_GEN)@PROTOC_C@ --c_out=. -I$(srcdir) $< + +.proto.pb-c.h: + $(AM_V_GEN)@PROTOC_C@ --c_out=. -I$(srcdir) $< + +libdnstap_la_SOURCES = \ + contrib/dnstap/convert.c \ + contrib/dnstap/convert.h \ + contrib/dnstap/dnstap.c \ + contrib/dnstap/dnstap.h \ + contrib/dnstap/message.c \ + contrib/dnstap/message.h \ + contrib/dnstap/reader.c \ + contrib/dnstap/reader.h \ + contrib/dnstap/writer.c \ + contrib/dnstap/writer.h + +nodist_libdnstap_la_SOURCES = \ + contrib/dnstap/dnstap.pb-c.c \ + contrib/dnstap/dnstap.pb-c.h + +BUILT_SOURCES += $(nodist_libdnstap_la_SOURCES) +CLEANFILES += $(nodist_libdnstap_la_SOURCES) +endif HAVE_LIBDNSTAP + +if EMBEDDED_LIBNGTCP2 +noinst_LTLIBRARIES += libembngtcp2.la + +libembngtcp2_la_CPPFLAGS = $(AM_CPPFLAGS) $(CFLAG_VISIBILITY) \ + $(embedded_libngtcp2_CFLAGS) $(gnutls_CFLAGS) +libembngtcp2_la_LDFLAGS = $(AM_LDFLAGS) $(LDFLAG_EXCLUDE_LIBS) +libembngtcp2_LIBS = libembngtcp2.la $(embedded_libngtcp2_LIBS) $(gnutls_LIBS) + +libembngtcp2_la_SOURCES = \ + contrib/libngtcp2/ngtcp2/crypto/gnutls.c \ + contrib/libngtcp2/ngtcp2/crypto/shared.c \ + contrib/libngtcp2/ngtcp2/crypto/shared.h \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_acktr.c \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_acktr.h \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_addr.c \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_addr.h \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_balloc.c \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_balloc.h \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_bbr.c \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_bbr.h \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_buf.c \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_buf.h \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_cc.c \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_cc.h \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_cid.c \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_cid.h \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_conn.c \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_conn.h \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_conn_stat.h \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_conv.c \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_conv.h \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_conversion.c \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_conversion.h \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_crypto.c \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_crypto.h \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_err.c \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_err.h \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_frame_chain.c \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_frame_chain.h \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_gaptr.c \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_gaptr.h \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_idtr.c \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_idtr.h \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_ksl.c \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_ksl.h \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_log.c \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_log.h \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_macro.h \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_map.c \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_map.h \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_mem.c \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_mem.h \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_net.h \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_objalloc.c \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_objalloc.h \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_opl.c \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_opl.h \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_path.c \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_path.h \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_pkt.c \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_pkt.h \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_pktns_id.h \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_pmtud.c \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_pmtud.h \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_ppe.c \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_ppe.h \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_pq.c \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_pq.h \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_pv.c \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_pv.h \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_qlog.c \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_qlog.h \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_range.c \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_range.h \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_rcvry.h \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_ringbuf.c \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_ringbuf.h \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_rob.c \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_rob.h \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_rst.c \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_rst.h \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_rtb.c \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_rtb.h \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_str.c \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_str.h \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_strm.c \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_strm.h \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_tstamp.h \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_unreachable.c \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_unreachable.h \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_vec.c \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_vec.h \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_version.c \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_window_filter.c \ + contrib/libngtcp2/ngtcp2/lib/ngtcp2_window_filter.h \ + contrib/libngtcp2/ngtcp2/ngtcp2.h \ + contrib/libngtcp2/ngtcp2/ngtcp2_crypto.h \ + contrib/libngtcp2/ngtcp2/ngtcp2_crypto_gnutls.h \ + contrib/libngtcp2/ngtcp2/version.h +endif EMBEDDED_LIBNGTCP2 diff --git a/src/contrib/asan.h b/src/contrib/asan.h new file mode 100644 index 0000000..5feb2c1 --- /dev/null +++ b/src/contrib/asan.h @@ -0,0 +1,37 @@ +/* Copyright (C) 2015 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#pragma once + +/* + * see sanitizer/asan_interface.h in compiler-rt (LLVM) + */ +#ifndef __has_feature + #define __has_feature(feature) 0 +#endif +#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__) + void __asan_poison_memory_region(void const volatile *addr, size_t size); + void __asan_unpoison_memory_region(void const volatile *addr, size_t size); + #define ASAN_POISON_MEMORY_REGION(addr, size) \ + __asan_poison_memory_region((addr), (size)) + #define ASAN_UNPOISON_MEMORY_REGION(addr, size) \ + __asan_unpoison_memory_region((addr), (size)) +#else + #define ASAN_POISON_MEMORY_REGION(addr, size) \ + ((void)(addr), (void)(size)) + #define ASAN_UNPOISON_MEMORY_REGION(addr, size) \ + ((void)(addr), (void)(size)) +#endif diff --git a/src/contrib/base32hex.c b/src/contrib/base32hex.c new file mode 100644 index 0000000..ad216d2 --- /dev/null +++ b/src/contrib/base32hex.c @@ -0,0 +1,353 @@ +/* Copyright (C) 2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "contrib/base32hex.h" +#include "libknot/errcode.h" + +#include <stdlib.h> +#include <stdint.h> + +/*! \brief Maximal length of binary input to Base32hex encoding. */ +#define MAX_BIN_DATA_LEN ((INT32_MAX / 8) * 5) + +/*! \brief Base32hex padding character. */ +static const uint8_t base32hex_pad = '='; +/*! \brief Base32hex alphabet. */ +static const uint8_t base32hex_enc[] = "0123456789abcdefghijklmnopqrstuv"; + +/*! \brief Indicates bad Base32hex character. */ +#define KO 255 +/*! \brief Indicates Base32hex padding character. */ +#define PD 32 + +/*! \brief Transformation and validation table for decoding Base32hex. */ +static const uint8_t base32hex_dec[256] = { + [ 0] = KO, [ 43] = KO, ['V'] = 31, [129] = KO, [172] = KO, [215] = KO, + [ 1] = KO, [ 44] = KO, ['W'] = KO, [130] = KO, [173] = KO, [216] = KO, + [ 2] = KO, [ 45] = KO, ['X'] = KO, [131] = KO, [174] = KO, [217] = KO, + [ 3] = KO, [ 46] = KO, ['Y'] = KO, [132] = KO, [175] = KO, [218] = KO, + [ 4] = KO, [ 47] = KO, ['Z'] = KO, [133] = KO, [176] = KO, [219] = KO, + [ 5] = KO, ['0'] = 0, [ 91] = KO, [134] = KO, [177] = KO, [220] = KO, + [ 6] = KO, ['1'] = 1, [ 92] = KO, [135] = KO, [178] = KO, [221] = KO, + [ 7] = KO, ['2'] = 2, [ 93] = KO, [136] = KO, [179] = KO, [222] = KO, + [ 8] = KO, ['3'] = 3, [ 94] = KO, [137] = KO, [180] = KO, [223] = KO, + [ 9] = KO, ['4'] = 4, [ 95] = KO, [138] = KO, [181] = KO, [224] = KO, + [ 10] = KO, ['5'] = 5, [ 96] = KO, [139] = KO, [182] = KO, [225] = KO, + [ 11] = KO, ['6'] = 6, ['a'] = 10, [140] = KO, [183] = KO, [226] = KO, + [ 12] = KO, ['7'] = 7, ['b'] = 11, [141] = KO, [184] = KO, [227] = KO, + [ 13] = KO, ['8'] = 8, ['c'] = 12, [142] = KO, [185] = KO, [228] = KO, + [ 14] = KO, ['9'] = 9, ['d'] = 13, [143] = KO, [186] = KO, [229] = KO, + [ 15] = KO, [ 58] = KO, ['e'] = 14, [144] = KO, [187] = KO, [230] = KO, + [ 16] = KO, [ 59] = KO, ['f'] = 15, [145] = KO, [188] = KO, [231] = KO, + [ 17] = KO, [ 60] = KO, ['g'] = 16, [146] = KO, [189] = KO, [232] = KO, + [ 18] = KO, ['='] = PD, ['h'] = 17, [147] = KO, [190] = KO, [233] = KO, + [ 19] = KO, [ 62] = KO, ['i'] = 18, [148] = KO, [191] = KO, [234] = KO, + [ 20] = KO, [ 63] = KO, ['j'] = 19, [149] = KO, [192] = KO, [235] = KO, + [ 21] = KO, [ 64] = KO, ['k'] = 20, [150] = KO, [193] = KO, [236] = KO, + [ 22] = KO, ['A'] = 10, ['l'] = 21, [151] = KO, [194] = KO, [237] = KO, + [ 23] = KO, ['B'] = 11, ['m'] = 22, [152] = KO, [195] = KO, [238] = KO, + [ 24] = KO, ['C'] = 12, ['n'] = 23, [153] = KO, [196] = KO, [239] = KO, + [ 25] = KO, ['D'] = 13, ['o'] = 24, [154] = KO, [197] = KO, [240] = KO, + [ 26] = KO, ['E'] = 14, ['p'] = 25, [155] = KO, [198] = KO, [241] = KO, + [ 27] = KO, ['F'] = 15, ['q'] = 26, [156] = KO, [199] = KO, [242] = KO, + [ 28] = KO, ['G'] = 16, ['r'] = 27, [157] = KO, [200] = KO, [243] = KO, + [ 29] = KO, ['H'] = 17, ['s'] = 28, [158] = KO, [201] = KO, [244] = KO, + [ 30] = KO, ['I'] = 18, ['t'] = 29, [159] = KO, [202] = KO, [245] = KO, + [ 31] = KO, ['J'] = 19, ['u'] = 30, [160] = KO, [203] = KO, [246] = KO, + [ 32] = KO, ['K'] = 20, ['v'] = 31, [161] = KO, [204] = KO, [247] = KO, + [ 33] = KO, ['L'] = 21, ['w'] = KO, [162] = KO, [205] = KO, [248] = KO, + [ 34] = KO, ['M'] = 22, ['x'] = KO, [163] = KO, [206] = KO, [249] = KO, + [ 35] = KO, ['N'] = 23, ['y'] = KO, [164] = KO, [207] = KO, [250] = KO, + [ 36] = KO, ['O'] = 24, ['z'] = KO, [165] = KO, [208] = KO, [251] = KO, + [ 37] = KO, ['P'] = 25, [123] = KO, [166] = KO, [209] = KO, [252] = KO, + [ 38] = KO, ['Q'] = 26, [124] = KO, [167] = KO, [210] = KO, [253] = KO, + [ 39] = KO, ['R'] = 27, [125] = KO, [168] = KO, [211] = KO, [254] = KO, + [ 40] = KO, ['S'] = 28, [126] = KO, [169] = KO, [212] = KO, [255] = KO, + [ 41] = KO, ['T'] = 29, [127] = KO, [170] = KO, [213] = KO, + [ 42] = KO, ['U'] = 30, [128] = KO, [171] = KO, [214] = KO, +}; + +int32_t knot_base32hex_encode(const uint8_t *in, + const uint32_t in_len, + uint8_t *out, + const uint32_t out_len) +{ + // Checking inputs. + if (in == NULL || out == NULL) { + return KNOT_EINVAL; + } + if (in_len > MAX_BIN_DATA_LEN || out_len < ((in_len + 4) / 5) * 8) { + return KNOT_ERANGE; + } + + uint8_t rest_len = in_len % 5; + const uint8_t *stop = in + in_len - rest_len; + uint8_t *text = out; + + // Encoding loop takes 5 bytes and creates 8 characters. + while (in < stop) { + text[0] = base32hex_enc[in[0] >> 3]; + text[1] = base32hex_enc[(in[0] & 0x07) << 2 | in[1] >> 6]; + text[2] = base32hex_enc[(in[1] & 0x3E) >> 1]; + text[3] = base32hex_enc[(in[1] & 0x01) << 4 | in[2] >> 4]; + text[4] = base32hex_enc[(in[2] & 0x0F) << 1 | in[3] >> 7]; + text[5] = base32hex_enc[(in[3] & 0x7C) >> 2]; + text[6] = base32hex_enc[(in[3] & 0x03) << 3 | in[4] >> 5]; + text[7] = base32hex_enc[in[4] & 0x1F]; + text += 8; + in += 5; + } + + // Processing of padding, if any. + switch (rest_len) { + case 4: + text[0] = base32hex_enc[in[0] >> 3]; + text[1] = base32hex_enc[(in[0] & 0x07) << 2 | in[1] >> 6]; + text[2] = base32hex_enc[(in[1] & 0x3E) >> 1]; + text[3] = base32hex_enc[(in[1] & 0x01) << 4 | in[2] >> 4]; + text[4] = base32hex_enc[(in[2] & 0x0F) << 1 | in[3] >> 7]; + text[5] = base32hex_enc[(in[3] & 0x7C) >> 2]; + text[6] = base32hex_enc[(in[3] & 0x03) << 3]; + text[7] = base32hex_pad; + text += 8; + break; + case 3: + text[0] = base32hex_enc[in[0] >> 3]; + text[1] = base32hex_enc[(in[0] & 0x07) << 2 | in[1] >> 6]; + text[2] = base32hex_enc[(in[1] & 0x3E) >> 1]; + text[3] = base32hex_enc[(in[1] & 0x01) << 4 | in[2] >> 4]; + text[4] = base32hex_enc[(in[2] & 0x0F) << 1]; + text[5] = base32hex_pad; + text[6] = base32hex_pad; + text[7] = base32hex_pad; + text += 8; + break; + case 2: + text[0] = base32hex_enc[in[0] >> 3]; + text[1] = base32hex_enc[(in[0] & 0x07) << 2 | in[1] >> 6]; + text[2] = base32hex_enc[(in[1] & 0x3E) >> 1]; + text[3] = base32hex_enc[(in[1] & 0x01) << 4]; + text[4] = base32hex_pad; + text[5] = base32hex_pad; + text[6] = base32hex_pad; + text[7] = base32hex_pad; + text += 8; + break; + case 1: + text[0] = base32hex_enc[in[0] >> 3]; + text[1] = base32hex_enc[(in[0] & 0x07) << 2]; + text[2] = base32hex_pad; + text[3] = base32hex_pad; + text[4] = base32hex_pad; + text[5] = base32hex_pad; + text[6] = base32hex_pad; + text[7] = base32hex_pad; + text += 8; + break; + } + + return (text - out); +} + +int32_t knot_base32hex_encode_alloc(const uint8_t *in, + const uint32_t in_len, + uint8_t **out) +{ + // Checking inputs. + if (out == NULL) { + return KNOT_EINVAL; + } + if (in_len > MAX_BIN_DATA_LEN) { + return KNOT_ERANGE; + } + + // Compute output buffer length. + uint32_t out_len = ((in_len + 4) / 5) * 8; + + // Allocate output buffer. + *out = malloc(out_len); + if (*out == NULL) { + return KNOT_ENOMEM; + } + + // Encode data. + int32_t ret = knot_base32hex_encode(in, in_len, *out, out_len); + if (ret < 0) { + free(*out); + *out = NULL; + } + + return ret; +} + +int32_t knot_base32hex_decode(const uint8_t *in, + const uint32_t in_len, + uint8_t *out, + const uint32_t out_len) +{ + // Checking inputs. + if (in == NULL || out == NULL) { + return KNOT_EINVAL; + } + if (in_len > INT32_MAX || out_len < ((in_len + 7) / 8) * 5) { + return KNOT_ERANGE; + } + if ((in_len % 8) != 0) { + return KNOT_BASE32HEX_ESIZE; + } + + const uint8_t *stop = in + in_len; + uint8_t *bin = out; + uint8_t pad_len = 0; + uint8_t c1, c2, c3, c4, c5, c6, c7, c8; + + // Decoding loop takes 8 characters and creates 5 bytes. + while (in < stop) { + // Filling and transforming 8 Base32hex chars. + c1 = base32hex_dec[in[0]]; + c2 = base32hex_dec[in[1]]; + c3 = base32hex_dec[in[2]]; + c4 = base32hex_dec[in[3]]; + c5 = base32hex_dec[in[4]]; + c6 = base32hex_dec[in[5]]; + c7 = base32hex_dec[in[6]]; + c8 = base32hex_dec[in[7]]; + + // Check 8. char if is bad or padding. + if (c8 >= PD) { + if (c8 == PD && pad_len == 0) { + pad_len = 1; + } else { + return KNOT_BASE32HEX_ECHAR; + } + } + + // Check 7. char if is bad or padding (if so, 6. must be too). + if (c7 >= PD) { + if (c7 == PD && c6 == PD && pad_len == 1) { + pad_len = 3; + } else { + return KNOT_BASE32HEX_ECHAR; + } + } + + // Check 6. char if is bad or padding. + if (c6 >= PD) { + if (!(c6 == PD && pad_len == 3)) { + return KNOT_BASE32HEX_ECHAR; + } + } + + // Check 5. char if is bad or padding. + if (c5 >= PD) { + if (c5 == PD && pad_len == 3) { + pad_len = 4; + } else { + return KNOT_BASE32HEX_ECHAR; + } + } + + // Check 4. char if is bad or padding (if so, 3. must be too). + if (c4 >= PD) { + if (c4 == PD && c3 == PD && pad_len == 4) { + pad_len = 6; + } else { + return KNOT_BASE32HEX_ECHAR; + } + } + + // Check 3. char if is bad or padding. + if (c3 >= PD) { + if (!(c3 == PD && pad_len == 6)) { + return KNOT_BASE32HEX_ECHAR; + } + } + + // 1. and 2. chars must not be padding. + if (c2 >= PD || c1 >= PD) { + return KNOT_BASE32HEX_ECHAR; + } + + // Computing of output data based on padding length. + switch (pad_len) { + case 0: + bin[4] = (c7 << 5) + c8; + // FALLTHROUGH + case 1: + bin[3] = (c5 << 7) + (c6 << 2) + (c7 >> 3); + // FALLTHROUGH + case 3: + bin[2] = (c4 << 4) + (c5 >> 1); + // FALLTHROUGH + case 4: + bin[1] = (c2 << 6) + (c3 << 1) + (c4 >> 4); + // FALLTHROUGH + case 6: + bin[0] = (c1 << 3) + (c2 >> 2); + } + + // Update output end. + switch (pad_len) { + case 0: + bin += 5; + break; + case 1: + bin += 4; + break; + case 3: + bin += 3; + break; + case 4: + bin += 2; + break; + case 6: + bin += 1; + break; + } + + in += 8; + } + + return (bin - out); +} + +int32_t knot_base32hex_decode_alloc(const uint8_t *in, + const uint32_t in_len, + uint8_t **out) +{ + // Checking inputs. + if (out == NULL) { + return KNOT_EINVAL; + } + + // Compute output buffer length. + uint32_t out_len = ((in_len + 7) / 8) * 5; + + // Allocate output buffer. + *out = malloc(out_len); + if (*out == NULL) { + return KNOT_ENOMEM; + } + + // Decode data. + int32_t ret = knot_base32hex_decode(in, in_len, *out, out_len); + if (ret < 0) { + free(*out); + *out = NULL; + } + + return ret; +} diff --git a/src/contrib/base32hex.h b/src/contrib/base32hex.h new file mode 100644 index 0000000..4a6fd8f --- /dev/null +++ b/src/contrib/base32hex.h @@ -0,0 +1,104 @@ +/* Copyright (C) 2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +/*! + * \brief Base32hex implementation (RFC 4648). + * + * \note Input Base32hex string can contain a-v characters. These characters + * are considered as A-V equivalent. Lower-case variant is used for encoding! + */ + +#pragma once + +#include <stdint.h> + +/*! + * \brief Encodes binary data using Base32hex. + * + * \note Output data buffer contains Base32hex text string which isn't + * terminated with '\0'! + * + * \param in Input binary data. + * \param in_len Length of input data. + * \param out Output data buffer. + * \param out_len Size of output buffer. + * + * \retval >=0 length of output string. + * \retval KNOT_E* if error. + */ +int32_t knot_base32hex_encode(const uint8_t *in, + const uint32_t in_len, + uint8_t *out, + const uint32_t out_len); + +/*! + * \brief Encodes binary data using Base32hex and output stores to own buffer. + * + * \note Output data buffer contains Base32hex text string which isn't + * terminated with '\0'! + * + * \note Output buffer should be deallocated after use. + * + * \param in Input binary data. + * \param in_len Length of input data. + * \param out Output data buffer. + * + * \retval >=0 length of output string. + * \retval KNOT_E* if error. + */ +int32_t knot_base32hex_encode_alloc(const uint8_t *in, + const uint32_t in_len, + uint8_t **out); + +/*! + * \brief Decodes text data using Base32hex. + * + * \note Input data needn't be terminated with '\0'. + * + * \note Input data must be continuous Base32hex string! + * + * \param in Input text data. + * \param in_len Length of input string. + * \param out Output data buffer. + * \param out_len Size of output buffer. + * + * \retval >=0 length of output data. + * \retval KNOT_E* if error. + */ +int32_t knot_base32hex_decode(const uint8_t *in, + const uint32_t in_len, + uint8_t *out, + const uint32_t out_len); + +/*! + * \brief Decodes text data using Base32hex and output stores to own buffer. + * + * \note Input data needn't be terminated with '\0'. + * + * \note Input data must be continuous Base32hex string! + * + * \note Output buffer should be deallocated after use. + * + * \param in Input text data. + * \param in_len Length of input string. + * \param out Output data buffer. + * + * \retval >=0 length of output data. + * \retval KNOT_E* if error. + */ +int32_t knot_base32hex_decode_alloc(const uint8_t *in, + const uint32_t in_len, + uint8_t **out); diff --git a/src/contrib/base64.c b/src/contrib/base64.c new file mode 100644 index 0000000..ab4a560 --- /dev/null +++ b/src/contrib/base64.c @@ -0,0 +1,272 @@ +/* Copyright (C) 2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "contrib/base64.h" +#include "libknot/errcode.h" + +#include <stdlib.h> +#include <stdint.h> + +/*! \brief Maximal length of binary input to Base64 encoding. */ +#define MAX_BIN_DATA_LEN ((INT32_MAX / 4) * 3) + +/*! \brief Base64 padding character. */ +static const uint8_t base64_pad = '='; +/*! \brief Base64 alphabet. */ +static const uint8_t base64_enc[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +/*! \brief Indicates bad Base64 character. */ +#define KO 255 +/*! \brief Indicates Base64 padding character. */ +#define PD 64 + +/*! \brief Transformation and validation table for decoding Base64. */ +static const uint8_t base64_dec[256] = { + [ 0] = KO, ['+'] = 62, ['V'] = 21, [129] = KO, [172] = KO, [215] = KO, + [ 1] = KO, [ 44] = KO, ['W'] = 22, [130] = KO, [173] = KO, [216] = KO, + [ 2] = KO, [ 45] = KO, ['X'] = 23, [131] = KO, [174] = KO, [217] = KO, + [ 3] = KO, [ 46] = KO, ['Y'] = 24, [132] = KO, [175] = KO, [218] = KO, + [ 4] = KO, ['/'] = 63, ['Z'] = 25, [133] = KO, [176] = KO, [219] = KO, + [ 5] = KO, ['0'] = 52, [ 91] = KO, [134] = KO, [177] = KO, [220] = KO, + [ 6] = KO, ['1'] = 53, [ 92] = KO, [135] = KO, [178] = KO, [221] = KO, + [ 7] = KO, ['2'] = 54, [ 93] = KO, [136] = KO, [179] = KO, [222] = KO, + [ 8] = KO, ['3'] = 55, [ 94] = KO, [137] = KO, [180] = KO, [223] = KO, + [ 9] = KO, ['4'] = 56, [ 95] = KO, [138] = KO, [181] = KO, [224] = KO, + [ 10] = KO, ['5'] = 57, [ 96] = KO, [139] = KO, [182] = KO, [225] = KO, + [ 11] = KO, ['6'] = 58, ['a'] = 26, [140] = KO, [183] = KO, [226] = KO, + [ 12] = KO, ['7'] = 59, ['b'] = 27, [141] = KO, [184] = KO, [227] = KO, + [ 13] = KO, ['8'] = 60, ['c'] = 28, [142] = KO, [185] = KO, [228] = KO, + [ 14] = KO, ['9'] = 61, ['d'] = 29, [143] = KO, [186] = KO, [229] = KO, + [ 15] = KO, [ 58] = KO, ['e'] = 30, [144] = KO, [187] = KO, [230] = KO, + [ 16] = KO, [ 59] = KO, ['f'] = 31, [145] = KO, [188] = KO, [231] = KO, + [ 17] = KO, [ 60] = KO, ['g'] = 32, [146] = KO, [189] = KO, [232] = KO, + [ 18] = KO, ['='] = PD, ['h'] = 33, [147] = KO, [190] = KO, [233] = KO, + [ 19] = KO, [ 62] = KO, ['i'] = 34, [148] = KO, [191] = KO, [234] = KO, + [ 20] = KO, [ 63] = KO, ['j'] = 35, [149] = KO, [192] = KO, [235] = KO, + [ 21] = KO, [ 64] = KO, ['k'] = 36, [150] = KO, [193] = KO, [236] = KO, + [ 22] = KO, ['A'] = 0, ['l'] = 37, [151] = KO, [194] = KO, [237] = KO, + [ 23] = KO, ['B'] = 1, ['m'] = 38, [152] = KO, [195] = KO, [238] = KO, + [ 24] = KO, ['C'] = 2, ['n'] = 39, [153] = KO, [196] = KO, [239] = KO, + [ 25] = KO, ['D'] = 3, ['o'] = 40, [154] = KO, [197] = KO, [240] = KO, + [ 26] = KO, ['E'] = 4, ['p'] = 41, [155] = KO, [198] = KO, [241] = KO, + [ 27] = KO, ['F'] = 5, ['q'] = 42, [156] = KO, [199] = KO, [242] = KO, + [ 28] = KO, ['G'] = 6, ['r'] = 43, [157] = KO, [200] = KO, [243] = KO, + [ 29] = KO, ['H'] = 7, ['s'] = 44, [158] = KO, [201] = KO, [244] = KO, + [ 30] = KO, ['I'] = 8, ['t'] = 45, [159] = KO, [202] = KO, [245] = KO, + [ 31] = KO, ['J'] = 9, ['u'] = 46, [160] = KO, [203] = KO, [246] = KO, + [ 32] = KO, ['K'] = 10, ['v'] = 47, [161] = KO, [204] = KO, [247] = KO, + [ 33] = KO, ['L'] = 11, ['w'] = 48, [162] = KO, [205] = KO, [248] = KO, + [ 34] = KO, ['M'] = 12, ['x'] = 49, [163] = KO, [206] = KO, [249] = KO, + [ 35] = KO, ['N'] = 13, ['y'] = 50, [164] = KO, [207] = KO, [250] = KO, + [ 36] = KO, ['O'] = 14, ['z'] = 51, [165] = KO, [208] = KO, [251] = KO, + [ 37] = KO, ['P'] = 15, [123] = KO, [166] = KO, [209] = KO, [252] = KO, + [ 38] = KO, ['Q'] = 16, [124] = KO, [167] = KO, [210] = KO, [253] = KO, + [ 39] = KO, ['R'] = 17, [125] = KO, [168] = KO, [211] = KO, [254] = KO, + [ 40] = KO, ['S'] = 18, [126] = KO, [169] = KO, [212] = KO, [255] = KO, + [ 41] = KO, ['T'] = 19, [127] = KO, [170] = KO, [213] = KO, + [ 42] = KO, ['U'] = 20, [128] = KO, [171] = KO, [214] = KO, +}; + +int32_t knot_base64_encode(const uint8_t *in, + const uint32_t in_len, + uint8_t *out, + const uint32_t out_len) +{ + // Checking inputs. + if (in == NULL || out == NULL) { + return KNOT_EINVAL; + } + if (in_len > MAX_BIN_DATA_LEN || out_len < ((in_len + 2) / 3) * 4) { + return KNOT_ERANGE; + } + + uint8_t rest_len = in_len % 3; + const uint8_t *stop = in + in_len - rest_len; + uint8_t *text = out; + + // Encoding loop takes 3 bytes and creates 4 characters. + while (in < stop) { + text[0] = base64_enc[in[0] >> 2]; + text[1] = base64_enc[(in[0] & 0x03) << 4 | in[1] >> 4]; + text[2] = base64_enc[(in[1] & 0x0F) << 2 | in[2] >> 6]; + text[3] = base64_enc[in[2] & 0x3F]; + text += 4; + in += 3; + } + + // Processing of padding, if any. + switch (rest_len) { + case 2: + text[0] = base64_enc[in[0] >> 2]; + text[1] = base64_enc[(in[0] & 0x03) << 4 | in[1] >> 4]; + text[2] = base64_enc[(in[1] & 0x0F) << 2]; + text[3] = base64_pad; + text += 4; + break; + case 1: + text[0] = base64_enc[in[0] >> 2]; + text[1] = base64_enc[(in[0] & 0x03) << 4]; + text[2] = base64_pad; + text[3] = base64_pad; + text += 4; + break; + } + + return (text - out); +} + +int32_t knot_base64_encode_alloc(const uint8_t *in, + const uint32_t in_len, + uint8_t **out) +{ + // Checking inputs. + if (out == NULL) { + return KNOT_EINVAL; + } + if (in_len > MAX_BIN_DATA_LEN) { + return KNOT_ERANGE; + } + + // Compute output buffer length. + uint32_t out_len = ((in_len + 2) / 3) * 4; + + // Allocate output buffer. + *out = malloc(out_len); + if (*out == NULL) { + return KNOT_ENOMEM; + } + + // Encode data. + int32_t ret = knot_base64_encode(in, in_len, *out, out_len); + if (ret < 0) { + free(*out); + *out = NULL; + } + + return ret; +} + +int32_t knot_base64_decode(const uint8_t *in, + const uint32_t in_len, + uint8_t *out, + const uint32_t out_len) +{ + // Checking inputs. + if (in == NULL || out == NULL) { + return KNOT_EINVAL; + } + if (in_len > INT32_MAX || out_len < ((in_len + 3) / 4) * 3) { + return KNOT_ERANGE; + } + if ((in_len % 4) != 0) { + return KNOT_BASE64_ESIZE; + } + + const uint8_t *stop = in + in_len; + uint8_t *bin = out; + uint8_t pad_len = 0; + uint8_t c1, c2, c3, c4; + + // Decoding loop takes 4 characters and creates 3 bytes. + while (in < stop) { + // Filling and transforming 4 Base64 chars. + c1 = base64_dec[in[0]]; + c2 = base64_dec[in[1]]; + c3 = base64_dec[in[2]]; + c4 = base64_dec[in[3]]; + + // Check 4. char if is bad or padding. + if (c4 >= PD) { + if (c4 == PD && pad_len == 0) { + pad_len = 1; + } else { + return KNOT_BASE64_ECHAR; + } + } + + // Check 3. char if is bad or padding. + if (c3 >= PD) { + if (c3 == PD && pad_len == 1) { + pad_len = 2; + } else { + return KNOT_BASE64_ECHAR; + } + } + + // Check 1. and 2. chars if are not padding. + if (c2 >= PD || c1 >= PD) { + return KNOT_BASE64_ECHAR; + } + + // Computing of output data based on padding length. + switch (pad_len) { + case 0: + bin[2] = (c3 << 6) + c4; + // FALLTHROUGH + case 1: + bin[1] = (c2 << 4) + (c3 >> 2); + // FALLTHROUGH + case 2: + bin[0] = (c1 << 2) + (c2 >> 4); + } + + // Update output end. + switch (pad_len) { + case 0: + bin += 3; + break; + case 1: + bin += 2; + break; + case 2: + bin += 1; + break; + } + + in += 4; + } + + return (bin - out); +} + +int32_t knot_base64_decode_alloc(const uint8_t *in, + const uint32_t in_len, + uint8_t **out) +{ + // Checking inputs. + if (out == NULL) { + return KNOT_EINVAL; + } + + // Compute output buffer length. + uint32_t out_len = ((in_len + 3) / 4) * 3; + + // Allocate output buffer. + *out = malloc(out_len); + if (*out == NULL) { + return KNOT_ENOMEM; + } + + // Decode data. + int32_t ret = knot_base64_decode(in, in_len, *out, out_len); + if (ret < 0) { + free(*out); + *out = NULL; + } + + return ret; +} diff --git a/src/contrib/base64.h b/src/contrib/base64.h new file mode 100644 index 0000000..0a98733 --- /dev/null +++ b/src/contrib/base64.h @@ -0,0 +1,103 @@ +/* Copyright (C) 2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +/*! + * \brief Base64 implementation (RFC 4648). + */ + +#pragma once + +#include <stdint.h> + +/*! + * \brief Encodes binary data using Base64. + * + * \note Output data buffer contains Base64 text string which isn't + * terminated with '\0'! + * + * \param in Input binary data. + * \param in_len Length of input data. + * \param out Output data buffer. + * \param out_len Size of output buffer. + * + * \retval >=0 length of output string. + * \retval KNOT_E* if error. + */ +int32_t knot_base64_encode(const uint8_t *in, + const uint32_t in_len, + uint8_t *out, + const uint32_t out_len); + +/*! + * \brief Encodes binary data using Base64 and output stores to own buffer. + * + * \note Output data buffer contains Base64 text string which isn't + * terminated with '\0'! + * + * \note Output buffer should be deallocated after use. + * + * \param in Input binary data. + * \param in_len Length of input data. + * \param out Output data buffer. + * + * \retval >=0 length of output string. + * \retval KNOT_E* if error. + */ +int32_t knot_base64_encode_alloc(const uint8_t *in, + const uint32_t in_len, + uint8_t **out); + +/*! + * \brief Decodes text data using Base64. + * + * \note Input data needn't be terminated with '\0'. + * + * \note Input data must be continuous Base64 string! + * + * \param in Input text data. + * \param in_len Length of input string. + * \param out Output data buffer. + * \param out_len Size of output buffer. + * + * \retval >=0 length of output data. + * \retval KNOT_E* if error. + */ +int32_t knot_base64_decode(const uint8_t *in, + const uint32_t in_len, + uint8_t *out, + const uint32_t out_len); + +/*! + * \brief Decodes text data using Base64 and output stores to own buffer. + * + * \note Input data needn't be terminated with '\0'. + * + * \note Input data must be continuous Base64 string! + * + * \note Output buffer should be deallocated after use. + * + * \param in Input text data. + * \param in_len Length of input string. + * \param out Output data buffer. + * + * \retval >=0 length of output data. + * \retval KNOT_E* if error. + */ +int32_t knot_base64_decode_alloc(const uint8_t *in, + const uint32_t in_len, + uint8_t **out); + +/*! @} */ diff --git a/src/contrib/base64url.c b/src/contrib/base64url.c new file mode 100644 index 0000000..34e2bbe --- /dev/null +++ b/src/contrib/base64url.c @@ -0,0 +1,287 @@ +/* Copyright (C) 2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "contrib/base64url.h" +#include "libknot/errcode.h" + +#include <stdlib.h> +#include <stdint.h> +#include <ctype.h> + +/*! \brief Maximal length of binary input to Base64url encoding. */ +#define MAX_BIN_DATA_LEN ((INT32_MAX / 4) * 3) + +/*! \brief Base64url padding character. */ +static const uint8_t base64url_pad = '\0'; +/*! \brief Base64 alphabet. */ +static const uint8_t base64url_enc[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + +/*! \brief Indicates bad Base64 character. */ +#define KO 255 +/*! \brief Indicates Base64 padding character. */ +#define PD 64 + +/*! \brief Transformation and validation table for decoding Base64. */ +static const uint8_t base64url_dec[256] = { + [ 0] = PD, [ 43] = KO, ['V'] = 21, [129] = KO, [172] = KO, [215] = KO, + [ 1] = KO, [ 44] = KO, ['W'] = 22, [130] = KO, [173] = KO, [216] = KO, + [ 2] = KO, ['-'] = 62, ['X'] = 23, [131] = KO, [174] = KO, [217] = KO, + [ 3] = KO, [ 46] = KO, ['Y'] = 24, [132] = KO, [175] = KO, [218] = KO, + [ 4] = KO, [ 47] = KO, ['Z'] = 25, [133] = KO, [176] = KO, [219] = KO, + [ 5] = KO, ['0'] = 52, [ 91] = KO, [134] = KO, [177] = KO, [220] = KO, + [ 6] = KO, ['1'] = 53, [ 92] = KO, [135] = KO, [178] = KO, [221] = KO, + [ 7] = KO, ['2'] = 54, [ 93] = KO, [136] = KO, [179] = KO, [222] = KO, + [ 8] = KO, ['3'] = 55, [ 94] = KO, [137] = KO, [180] = KO, [223] = KO, + [ 9] = KO, ['4'] = 56, ['_'] = 63, [138] = KO, [181] = KO, [224] = KO, + [ 10] = KO, ['5'] = 57, [ 96] = KO, [139] = KO, [182] = KO, [225] = KO, + [ 11] = KO, ['6'] = 58, ['a'] = 26, [140] = KO, [183] = KO, [226] = KO, + [ 12] = KO, ['7'] = 59, ['b'] = 27, [141] = KO, [184] = KO, [227] = KO, + [ 13] = KO, ['8'] = 60, ['c'] = 28, [142] = KO, [185] = KO, [228] = KO, + [ 14] = KO, ['9'] = 61, ['d'] = 29, [143] = KO, [186] = KO, [229] = KO, + [ 15] = KO, [ 58] = KO, ['e'] = 30, [144] = KO, [187] = KO, [230] = KO, + [ 16] = KO, [ 59] = KO, ['f'] = 31, [145] = KO, [188] = KO, [231] = KO, + [ 17] = KO, [ 60] = KO, ['g'] = 32, [146] = KO, [189] = KO, [232] = KO, + [ 18] = KO, [ 61] = KO, ['h'] = 33, [147] = KO, [190] = KO, [233] = KO, + [ 19] = KO, [ 62] = KO, ['i'] = 34, [148] = KO, [191] = KO, [234] = KO, + [ 20] = KO, [ 63] = KO, ['j'] = 35, [149] = KO, [192] = KO, [235] = KO, + [ 21] = KO, [ 64] = KO, ['k'] = 36, [150] = KO, [193] = KO, [236] = KO, + [ 22] = KO, ['A'] = 0, ['l'] = 37, [151] = KO, [194] = KO, [237] = KO, + [ 23] = KO, ['B'] = 1, ['m'] = 38, [152] = KO, [195] = KO, [238] = KO, + [ 24] = KO, ['C'] = 2, ['n'] = 39, [153] = KO, [196] = KO, [239] = KO, + [ 25] = KO, ['D'] = 3, ['o'] = 40, [154] = KO, [197] = KO, [240] = KO, + [ 26] = KO, ['E'] = 4, ['p'] = 41, [155] = KO, [198] = KO, [241] = KO, + [ 27] = KO, ['F'] = 5, ['q'] = 42, [156] = KO, [199] = KO, [242] = KO, + [ 28] = KO, ['G'] = 6, ['r'] = 43, [157] = KO, [200] = KO, [243] = KO, + [ 29] = KO, ['H'] = 7, ['s'] = 44, [158] = KO, [201] = KO, [244] = KO, + [ 30] = KO, ['I'] = 8, ['t'] = 45, [159] = KO, [202] = KO, [245] = KO, + [ 31] = KO, ['J'] = 9, ['u'] = 46, [160] = KO, [203] = KO, [246] = KO, + [ 32] = KO, ['K'] = 10, ['v'] = 47, [161] = KO, [204] = KO, [247] = KO, + [ 33] = KO, ['L'] = 11, ['w'] = 48, [162] = KO, [205] = KO, [248] = KO, + [ 34] = KO, ['M'] = 12, ['x'] = 49, [163] = KO, [206] = KO, [249] = KO, + [ 35] = KO, ['N'] = 13, ['y'] = 50, [164] = KO, [207] = KO, [250] = KO, + [ 36] = KO, ['O'] = 14, ['z'] = 51, [165] = KO, [208] = KO, [251] = KO, + ['%'] = KO, ['P'] = 15, [123] = KO, [166] = KO, [209] = KO, [252] = KO, + [ 38] = KO, ['Q'] = 16, [124] = KO, [167] = KO, [210] = KO, [253] = KO, + [ 39] = KO, ['R'] = 17, [125] = KO, [168] = KO, [211] = KO, [254] = KO, + [ 40] = KO, ['S'] = 18, [126] = KO, [169] = KO, [212] = KO, [255] = KO, + [ 41] = KO, ['T'] = 19, [127] = KO, [170] = KO, [213] = KO, + [ 42] = KO, ['U'] = 20, [128] = KO, [171] = KO, [214] = KO, +}; + +int32_t knot_base64url_encode(const uint8_t *in, + const uint32_t in_len, + uint8_t *out, + const uint32_t out_len) +{ + // Checking inputs. + if (in == NULL || out == NULL) { + return KNOT_EINVAL; + } + if (in_len > MAX_BIN_DATA_LEN || out_len < ((in_len + 2) / 3) * 4) { + return KNOT_ERANGE; + } + + uint8_t rest_len = in_len % 3; + const uint8_t *stop = in + in_len - rest_len; + uint8_t *text = out; + + // Encoding loop takes 3 bytes and creates 4 characters. + while (in < stop) { + text[0] = base64url_enc[in[0] >> 2]; + text[1] = base64url_enc[(in[0] & 0x03) << 4 | in[1] >> 4]; + text[2] = base64url_enc[(in[1] & 0x0F) << 2 | in[2] >> 6]; + text[3] = base64url_enc[in[2] & 0x3F]; + text += 4; + in += 3; + } + + // Processing of padding, if any. + switch (rest_len) { + case 2: + text[0] = base64url_enc[in[0] >> 2]; + text[1] = base64url_enc[(in[0] & 0x03) << 4 | in[1] >> 4]; + text[2] = base64url_enc[(in[1] & 0x0F) << 2]; + text[3] = base64url_pad; + text += 3; + break; + case 1: + text[0] = base64url_enc[in[0] >> 2]; + text[1] = base64url_enc[(in[0] & 0x03) << 4]; + text[2] = base64url_pad; + text[3] = base64url_pad; + text += 2; + break; + } + return (text - out); +} + +int32_t knot_base64url_encode_alloc(const uint8_t *in, + const uint32_t in_len, + uint8_t **out) +{ + // Checking inputs. + if (out == NULL) { + return KNOT_EINVAL; + } + if (in_len > MAX_BIN_DATA_LEN) { + return KNOT_ERANGE; + } + + // Compute output buffer length. + uint32_t out_len = ((in_len + 2) / 3) * 4; + + // Allocate output buffer. + *out = malloc(out_len); + if (*out == NULL) { + return KNOT_ENOMEM; + } + + // Encode data. + int32_t ret = knot_base64url_encode(in, in_len, *out, out_len); + if (ret < 0) { + free(*out); + *out = NULL; + } + + return ret; +} + +int32_t knot_base64url_decode(const uint8_t *in, + uint32_t in_len, + uint8_t *out, + const uint32_t out_len) +{ + // Checking inputs. + if (in == NULL || out == NULL) { + return KNOT_EINVAL; + } + + // cut up to two "%3d" from the end of input + int pad3d = 0; + const uint8_t *end = in + in_len; + char *perc3d = "d3%d3%", *stop3d = perc3d + 6; + while (end != in && perc3d != stop3d && tolower(*--end) == *perc3d) { + if (*perc3d++ == '%') { + in_len -= 3; + pad3d++; + } + } + + if (in_len > INT32_MAX || out_len < ((in_len + 3) / 4) * 3) { + return KNOT_ERANGE; + } + + const uint8_t *stop = in + in_len; + uint8_t *bin = out; + uint8_t pad_len = 0; + uint8_t c1, c2, c3, c4; + + // Decoding loop takes 4 characters and creates 3 bytes. + while (in < stop) { + // Filling and transforming 4 Base64 chars. + c1 = base64url_dec[in[0]] ; + c2 = base64url_dec[in[1]] ; + c3 = (in + 2 < stop) ? base64url_dec[in[2]] : PD; + c4 = (in + 3 < stop) ? base64url_dec[in[3]] : PD; + + // Check 1. and 2. chars if are not padding + if (c1 >= PD || c2 >= PD) { + return KNOT_BASE64_ECHAR; + } + // Check 3. char if is bad or padding. + else if (c3 >= PD) { + if (c3 == PD) { + pad_len = 2; + } else { + return KNOT_BASE64_ECHAR; + } + } + // Check 3. char if is bad or padding. + else if (c4 >= PD) { + if (c4 == PD) { + pad_len = 1; + } else { + return KNOT_BASE64_ECHAR; + } + } + + if (pad_len > 0 && in <= stop - 4) { + return KNOT_BASE64_ECHAR; + } + + // Computing of output data based on padding length. + switch (pad_len) { + case 0: + bin[2] = (c3 << 6) + c4; + // FALLTHROUGH + case 1: + bin[1] = (c2 << 4) + (c3 >> 2); + // FALLTHROUGH + case 2: + bin[0] = (c1 << 2) + (c2 >> 4); + } + + // Update output end. + switch (pad_len) { + case 0: + bin += 3; + break; + case 1: + bin += 2; + goto end; + case 2: + bin += 1; + goto end; + } + + in += 4; + } + +end: + if (pad3d > pad_len) { + return KNOT_BASE64_ECHAR; + } + return (bin - out); +} + +int32_t knot_base64url_decode_alloc(const uint8_t *in, + const uint32_t in_len, + uint8_t **out) +{ + // Checking inputs. + if (out == NULL) { + return KNOT_EINVAL; + } + + // Compute output buffer length. + uint32_t out_len = ((in_len + 3) / 4) * 3; + + // Allocate output buffer. + *out = malloc(out_len); + if (*out == NULL) { + return KNOT_ENOMEM; + } + + // Decode data. + int32_t ret = knot_base64url_decode(in, in_len, *out, out_len); + if (ret < 0) { + free(*out); + *out = NULL; + } + + return ret; +} diff --git a/src/contrib/base64url.h b/src/contrib/base64url.h new file mode 100644 index 0000000..ba4bb43 --- /dev/null +++ b/src/contrib/base64url.h @@ -0,0 +1,103 @@ +/* Copyright (C) 2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +/*! + * \brief Base64url implementation (RFC 4648). + */ + +#pragma once + +#include <stdint.h> + +/*! + * \brief Encodes binary data using Base64. + * + * \note Output data buffer contains Base64 text string which isn't + * terminated with '\0'! + * + * \param in Input binary data. + * \param in_len Length of input data. + * \param out Output data buffer. + * \param out_len Size of output buffer. + * + * \retval >=0 length of output string. + * \retval KNOT_E* if error. + */ +int32_t knot_base64url_encode(const uint8_t *in, + const uint32_t in_len, + uint8_t *out, + const uint32_t out_len); + +/*! + * \brief Encodes binary data using Base64 and output stores to own buffer. + * + * \note Output data buffer contains Base64 text string which isn't + * terminated with '\0'! + * + * \note Output buffer should be deallocated after use. + * + * \param in Input binary data. + * \param in_len Length of input data. + * \param out Output data buffer. + * + * \retval >=0 length of output string. + * \retval KNOT_E* if error. + */ +int32_t knot_base64url_encode_alloc(const uint8_t *in, + const uint32_t in_len, + uint8_t **out); + +/*! + * \brief Decodes text data using Base64. + * + * \note Input data needn't be terminated with '\0'. + * + * \note Input data must be continuous Base64 string! + * + * \param in Input text data. + * \param in_len Length of input string. + * \param out Output data buffer. + * \param out_len Size of output buffer. + * + * \retval >=0 length of output data. + * \retval KNOT_E* if error. + */ +int32_t knot_base64url_decode(const uint8_t *in, + uint32_t in_len, + uint8_t *out, + const uint32_t out_len); + +/*! + * \brief Decodes text data using Base64 and output stores to own buffer. + * + * \note Input data needn't be terminated with '\0'. + * + * \note Input data must be continuous Base64 string! + * + * \note Output buffer should be deallocated after use. + * + * \param in Input text data. + * \param in_len Length of input string. + * \param out Output data buffer. + * + * \retval >=0 length of output data. + * \retval KNOT_E* if error. + */ +int32_t knot_base64url_decode_alloc(const uint8_t *in, + const uint32_t in_len, + uint8_t **out); + +/*! @} */ diff --git a/src/contrib/color.h b/src/contrib/color.h new file mode 100644 index 0000000..6a7ff6a --- /dev/null +++ b/src/contrib/color.h @@ -0,0 +1,31 @@ +/* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#pragma once + +#define COL_RST(active) ((active) ? "\x1B[0m" : "") + +#define COL_BOLD(active) ((active) ? "\x1B[1m" : "") +#define COL_DIM(active) ((active) ? "\x1B[2m" : "") +#define COL_UNDR(active) ((active) ? "\x1B[4m" : "") + +#define COL_RED(active) ((active) ? "\x1B[31m" : "") +#define COL_GRN(active) ((active) ? "\x1B[32m" : "") +#define COL_YELW(active) ((active) ? "\x1B[93m" : "") +#define COL_BLUE(active) ((active) ? "\x1B[34m" : "") +#define COL_MGNT(active) ((active) ? "\x1B[35m" : "") +#define COL_CYAN(active) ((active) ? "\x1B[36m" : "") +#define COL_WHIT(active) ((active) ? "\x1B[97m" : "") diff --git a/src/contrib/conn_pool.c b/src/contrib/conn_pool.c new file mode 100644 index 0000000..a344c57 --- /dev/null +++ b/src/contrib/conn_pool.c @@ -0,0 +1,266 @@ +/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "contrib/conn_pool.h" + +#include "contrib/sockaddr.h" + +conn_pool_t *global_conn_pool = NULL; +conn_pool_t *global_sessticket_pool = NULL; + +const conn_pool_fd_t CONN_POOL_FD_INVALID = -1; + +static conn_pool_fd_t pool_pop(conn_pool_t *pool, size_t i); + +/*! + * \brief Try to get an open connection older than specified timestamp. + * + * \param pool Pool to search in. + * \param older_than Timestamp that the connection must be older than. + * \param next_oldest Out: the timestamp of the oldest connection (other than the returned). + * + * \return -1 if error (no such connection), >= 0 connection file descriptor. + * + * \warning The returned connection is not necessarily the oldest one. + */ +static conn_pool_fd_t get_old(conn_pool_t *pool, + knot_time_t older_than, + knot_time_t *next_oldest) +{ + assert(pool); + + *next_oldest = 0; + + conn_pool_fd_t fd = CONN_POOL_FD_INVALID; + pthread_mutex_lock(&pool->mutex); + + for (size_t i = 0; i < pool->capacity; i++) { + knot_time_t la = pool->conns[i].last_active; + if (fd == CONN_POOL_FD_INVALID && knot_time_cmp(la, older_than) < 0) { + fd = pool_pop(pool, i); + } else if (knot_time_cmp(la, *next_oldest) < 0) { + *next_oldest = la; + } + } + + pthread_mutex_unlock(&pool->mutex); + return fd; +} + +static void *closing_thread(void *_arg) +{ + conn_pool_t *pool = _arg; + + while (true) { + knot_time_t now = knot_time(), next = 0; + knot_timediff_t timeout = conn_pool_timeout(pool, 0); + assert(timeout != 0); + + while (true) { + conn_pool_fd_t old_fd = get_old(pool, now - timeout + 1, &next); + if (old_fd != CONN_POOL_FD_INVALID) { + pool->close_cb(old_fd); + } else { + break; + } + } + + if (next == 0) { + sleep(timeout); + } else { + sleep(next + timeout - now); + } + } + + return NULL; // we never get here since the thread will be cancelled instead +} + +conn_pool_t *conn_pool_init(size_t capacity, knot_timediff_t timeout, + conn_pool_close_cb_t close_cb, + conn_pool_invalid_cb_t invalid_cb) +{ + if (capacity == 0 || timeout == 0) { + return NULL; + } + + conn_pool_t *pool = calloc(1, sizeof(*pool) + capacity * sizeof(pool->conns[0])); + if (pool != NULL) { + pool->capacity = capacity; + pool->timeout = timeout; + if (pthread_mutex_init(&pool->mutex, 0) != 0) { + free(pool); + return NULL; + } + if (pthread_create(&pool->closing_thread, NULL, closing_thread, pool) != 0) { + pthread_mutex_destroy(&pool->mutex); + free(pool); + return NULL; + } + pool->close_cb = close_cb; + pool->invalid_cb = invalid_cb; + } + return pool; +} + +void conn_pool_deinit(conn_pool_t *pool) +{ + if (pool != NULL) { + pthread_cancel(pool->closing_thread); + pthread_join(pool->closing_thread, NULL); + + conn_pool_fd_t fd; + knot_time_t unused; + while ((fd = get_old(pool, 0, &unused)) != CONN_POOL_FD_INVALID) { + pool->close_cb(fd); + } + + pthread_mutex_destroy(&pool->mutex); + free(pool); + } +} + +knot_timediff_t conn_pool_timeout(conn_pool_t *pool, + knot_timediff_t new_timeout) +{ + if (pool == NULL) { + return 0; + } + + pthread_mutex_lock(&pool->mutex); + + knot_timediff_t prev = pool->timeout; + if (new_timeout != 0) { + pool->timeout = new_timeout; + } + + pthread_mutex_unlock(&pool->mutex); + return prev; +} + +static conn_pool_fd_t pool_pop(conn_pool_t *pool, size_t i) +{ + conn_pool_memb_t *conn = &pool->conns[i]; + assert(conn->last_active != 0); + assert(pool->usage > 0); + conn_pool_fd_t fd = conn->fd; + memset(conn, 0, sizeof(*conn)); + pool->usage--; + return fd; +} + +conn_pool_fd_t conn_pool_get(conn_pool_t *pool, + const struct sockaddr_storage *src, + const struct sockaddr_storage *dst) +{ + if (pool == NULL) { + return CONN_POOL_FD_INVALID; + } + + conn_pool_fd_t fd = CONN_POOL_FD_INVALID; + pthread_mutex_lock(&pool->mutex); + + for (size_t i = 0; i < pool->capacity; i++) { + if (pool->conns[i].last_active != 0 && + sockaddr_cmp(&pool->conns[i].dst, dst, false) == 0 && + sockaddr_cmp(&pool->conns[i].src, src, true) == 0) { + fd = pool_pop(pool, i); + break; + } + } + + pthread_mutex_unlock(&pool->mutex); + + if (fd != CONN_POOL_FD_INVALID && pool->invalid_cb(fd)) { + pool->close_cb(fd); + fd = CONN_POOL_FD_INVALID; + } + + return fd; +} + +static void pool_push(conn_pool_t *pool, size_t i, + const struct sockaddr_storage *src, + const struct sockaddr_storage *dst, + conn_pool_fd_t fd) +{ + conn_pool_memb_t *conn = &pool->conns[i]; + assert(conn->last_active == 0); + assert(pool->usage < pool->capacity); + conn->last_active = knot_time(); + conn->fd = fd; + memcpy(&conn->src, src, sizeof(conn->src)); + memcpy(&conn->dst, dst, sizeof(conn->dst)); + pool->usage++; +} + +conn_pool_fd_t conn_pool_put(conn_pool_t *pool, + const struct sockaddr_storage *src, + const struct sockaddr_storage *dst, + conn_pool_fd_t fd) +{ + if (pool == NULL || pool->capacity == 0) { + return fd; + } + + knot_time_t oldest_time = 0; + size_t oldest_i = pool->capacity; + + pthread_mutex_lock(&pool->mutex); + + for (size_t i = 0; i < pool->capacity; i++) { + knot_time_t la = pool->conns[i].last_active; + if (la == 0) { + pool_push(pool, i, src, dst, fd); + pthread_mutex_unlock(&pool->mutex); + return CONN_POOL_FD_INVALID; + } else if (knot_time_cmp(la, oldest_time) < 0) { + oldest_time = la; + oldest_i = i; + } + } + + assert(oldest_i < pool->capacity); + conn_pool_fd_t oldest_fd = pool_pop(pool, oldest_i); + pool_push(pool, oldest_i, src, dst, fd); + pthread_mutex_unlock(&pool->mutex); + return oldest_fd; +} + +void conn_pool_close_cb_dflt(conn_pool_fd_t fd) +{ + if (fd != CONN_POOL_FD_INVALID) { + close((int)fd); + } +} + +bool conn_pool_invalid_cb_dflt(conn_pool_fd_t fd) +{ + uint8_t unused; + int peek = recv((int)fd, &unused, 1, MSG_PEEK | MSG_DONTWAIT); + return (peek >= 0); // closed or pending data +} + +bool conn_pool_invalid_cb_allvalid(conn_pool_fd_t fd) +{ + (void)fd; + return false; +} diff --git a/src/contrib/conn_pool.h b/src/contrib/conn_pool.h new file mode 100644 index 0000000..fece34a --- /dev/null +++ b/src/contrib/conn_pool.h @@ -0,0 +1,128 @@ +/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <pthread.h> +#include <stdbool.h> +#include <sys/socket.h> + +#include "contrib/time.h" + +typedef intptr_t conn_pool_fd_t; +extern const conn_pool_fd_t CONN_POOL_FD_INVALID; + +typedef void (*conn_pool_close_cb_t)(conn_pool_fd_t fd); +typedef bool (*conn_pool_invalid_cb_t)(conn_pool_fd_t fd); + +typedef struct { + struct sockaddr_storage src; + struct sockaddr_storage dst; + conn_pool_fd_t fd; + knot_time_t last_active; +} conn_pool_memb_t; + +typedef struct { + size_t capacity; + size_t usage; + knot_timediff_t timeout; + pthread_mutex_t mutex; + pthread_t closing_thread; + conn_pool_close_cb_t close_cb; + conn_pool_invalid_cb_t invalid_cb; + conn_pool_memb_t conns[]; +} conn_pool_t; + +extern conn_pool_t *global_conn_pool; +extern conn_pool_t *global_sessticket_pool; // pool for outgoing QUIC connection session tickets + +/*! + * \brief Allocate connection pool. + * + * \param capacity Connection pool capacity (must be positive number). + * \param timeout Connection timeout (must be positive number). + * \param close_cb Callback for closing fd. + * \param invalid_cb Callback detecting if given fd is already unusable. + * + * \return Connection pool or NULL if error. + */ +conn_pool_t *conn_pool_init(size_t capacity, knot_timediff_t timeout, + conn_pool_close_cb_t close_cb, + conn_pool_invalid_cb_t invalid_cb); + +/*! + * \brief Deallocate the pool, close all connections, terminate closing thread. + * + * \param pool Connection pool. + */ +void conn_pool_deinit(conn_pool_t *pool); + +/*! + * \brief Get and/or set connection timeout. + * + * \param pool Connection pool. + * \param new_timeout Optional: set new timeout (if positive number). + * + * \return Previous value of timeout. + */ +knot_timediff_t conn_pool_timeout(conn_pool_t *pool, + knot_timediff_t new_timeout); + +/*! + * \brief Try to get an open connection if present, check if alive. + * + * \param pool Pool to search in. + * \param src Connection source address. + * \param dst Connection destination address. + * + * \retval -1 If error (no such connection). + * \return >= 0 File descriptor of the connection. + */ +conn_pool_fd_t conn_pool_get(conn_pool_t *pool, + const struct sockaddr_storage *src, + const struct sockaddr_storage *dst); + +/*! + * \brief Put an open connection to the pool, possibly displacing the oldest one there. + * + * \param pool Pool to insert into. + * \param src Connestion source address. + * \param dst Connection destination adress. + * \param fd Connection file descriptor. + * + * \retval -1 If connection stored to free slot. + * \retval fd If not able to store connection. + * \return >= 0 File descriptor of the displaced old connection. + */ +conn_pool_fd_t conn_pool_put(conn_pool_t *pool, + const struct sockaddr_storage *src, + const struct sockaddr_storage *dst, + conn_pool_fd_t fd); + +/*! + * \brief Default close callback calling close() on given fd. + */ +void conn_pool_close_cb_dflt(conn_pool_fd_t fd); + +/*! + * \brief Default invalidness callback detecting socket not ready to write. + */ +bool conn_pool_invalid_cb_dflt(conn_pool_fd_t fd); + +/*! + * \brief Default invalidness callback always reporting valid fd. + */ +bool conn_pool_invalid_cb_allvalid(conn_pool_fd_t fd); diff --git a/src/contrib/ctype.h b/src/contrib/ctype.h new file mode 100644 index 0000000..d1e9d27 --- /dev/null +++ b/src/contrib/ctype.h @@ -0,0 +1,193 @@ +/* Copyright (C) 2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +/*! + * \brief Locale-independent ctype functions. + */ + +#pragma once + +#include <ctype.h> +#include <stdbool.h> +#include <stdint.h> + +enum { + CT_DIGIT = 1 << 0, + CT_UPPER = 1 << 1, + CT_LOWER = 1 << 2, + CT_XDIGT = 1 << 3, + CT_PUNCT = 1 << 4, + CT_PRINT = 1 << 5, + CT_SPACE = 1 << 6, +}; + +static const uint8_t char_mask[256] = { + // 0 - 8 + ['\t'] = CT_SPACE, + ['\n'] = CT_SPACE, + ['\v'] = CT_SPACE, + ['\f'] = CT_SPACE, + ['\r'] = CT_SPACE, + // 14 - 31 + [' '] = CT_PRINT | CT_SPACE, + + ['!'] = CT_PRINT | CT_PUNCT, + ['"'] = CT_PRINT | CT_PUNCT, + ['#'] = CT_PRINT | CT_PUNCT, + ['$'] = CT_PRINT | CT_PUNCT, + ['%'] = CT_PRINT | CT_PUNCT, + ['&'] = CT_PRINT | CT_PUNCT, + ['\''] = CT_PRINT | CT_PUNCT, + ['('] = CT_PRINT | CT_PUNCT, + [')'] = CT_PRINT | CT_PUNCT, + ['*'] = CT_PRINT | CT_PUNCT, + ['+'] = CT_PRINT | CT_PUNCT, + [','] = CT_PRINT | CT_PUNCT, + ['-'] = CT_PRINT | CT_PUNCT, + ['.'] = CT_PRINT | CT_PUNCT, + ['/'] = CT_PRINT | CT_PUNCT, + + ['0'] = CT_PRINT | CT_DIGIT | CT_XDIGT, + ['1'] = CT_PRINT | CT_DIGIT | CT_XDIGT, + ['2'] = CT_PRINT | CT_DIGIT | CT_XDIGT, + ['3'] = CT_PRINT | CT_DIGIT | CT_XDIGT, + ['4'] = CT_PRINT | CT_DIGIT | CT_XDIGT, + ['5'] = CT_PRINT | CT_DIGIT | CT_XDIGT, + ['6'] = CT_PRINT | CT_DIGIT | CT_XDIGT, + ['7'] = CT_PRINT | CT_DIGIT | CT_XDIGT, + ['8'] = CT_PRINT | CT_DIGIT | CT_XDIGT, + ['9'] = CT_PRINT | CT_DIGIT | CT_XDIGT, + + [':'] = CT_PRINT | CT_PUNCT, + [';'] = CT_PRINT | CT_PUNCT, + ['<'] = CT_PRINT | CT_PUNCT, + ['='] = CT_PRINT | CT_PUNCT, + ['>'] = CT_PRINT | CT_PUNCT, + ['?'] = CT_PRINT | CT_PUNCT, + ['@'] = CT_PRINT | CT_PUNCT, + + ['A'] = CT_PRINT | CT_UPPER | CT_XDIGT, + ['B'] = CT_PRINT | CT_UPPER | CT_XDIGT, + ['C'] = CT_PRINT | CT_UPPER | CT_XDIGT, + ['D'] = CT_PRINT | CT_UPPER | CT_XDIGT, + ['E'] = CT_PRINT | CT_UPPER | CT_XDIGT, + ['F'] = CT_PRINT | CT_UPPER | CT_XDIGT, + ['G'] = CT_PRINT | CT_UPPER, + ['H'] = CT_PRINT | CT_UPPER, + ['I'] = CT_PRINT | CT_UPPER, + ['J'] = CT_PRINT | CT_UPPER, + ['K'] = CT_PRINT | CT_UPPER, + ['L'] = CT_PRINT | CT_UPPER, + ['M'] = CT_PRINT | CT_UPPER, + ['N'] = CT_PRINT | CT_UPPER, + ['O'] = CT_PRINT | CT_UPPER, + ['P'] = CT_PRINT | CT_UPPER, + ['Q'] = CT_PRINT | CT_UPPER, + ['R'] = CT_PRINT | CT_UPPER, + ['S'] = CT_PRINT | CT_UPPER, + ['T'] = CT_PRINT | CT_UPPER, + ['U'] = CT_PRINT | CT_UPPER, + ['V'] = CT_PRINT | CT_UPPER, + ['W'] = CT_PRINT | CT_UPPER, + ['X'] = CT_PRINT | CT_UPPER, + ['Y'] = CT_PRINT | CT_UPPER, + ['Z'] = CT_PRINT | CT_UPPER, + + ['['] = CT_PRINT | CT_PUNCT, + ['\\'] = CT_PRINT | CT_PUNCT, + [']'] = CT_PRINT | CT_PUNCT, + ['^'] = CT_PRINT | CT_PUNCT, + ['_'] = CT_PRINT | CT_PUNCT, + ['`'] = CT_PRINT | CT_PUNCT, + + ['a'] = CT_PRINT | CT_LOWER | CT_XDIGT, + ['b'] = CT_PRINT | CT_LOWER | CT_XDIGT, + ['c'] = CT_PRINT | CT_LOWER | CT_XDIGT, + ['d'] = CT_PRINT | CT_LOWER | CT_XDIGT, + ['e'] = CT_PRINT | CT_LOWER | CT_XDIGT, + ['f'] = CT_PRINT | CT_LOWER | CT_XDIGT, + ['g'] = CT_PRINT | CT_LOWER, + ['h'] = CT_PRINT | CT_LOWER, + ['i'] = CT_PRINT | CT_LOWER, + ['j'] = CT_PRINT | CT_LOWER, + ['k'] = CT_PRINT | CT_LOWER, + ['l'] = CT_PRINT | CT_LOWER, + ['m'] = CT_PRINT | CT_LOWER, + ['n'] = CT_PRINT | CT_LOWER, + ['o'] = CT_PRINT | CT_LOWER, + ['p'] = CT_PRINT | CT_LOWER, + ['q'] = CT_PRINT | CT_LOWER, + ['r'] = CT_PRINT | CT_LOWER, + ['s'] = CT_PRINT | CT_LOWER, + ['t'] = CT_PRINT | CT_LOWER, + ['u'] = CT_PRINT | CT_LOWER, + ['v'] = CT_PRINT | CT_LOWER, + ['w'] = CT_PRINT | CT_LOWER, + ['x'] = CT_PRINT | CT_LOWER, + ['y'] = CT_PRINT | CT_LOWER, + ['z'] = CT_PRINT | CT_LOWER, + + ['{'] = CT_PRINT | CT_PUNCT, + ['|'] = CT_PRINT | CT_PUNCT, + ['}'] = CT_PRINT | CT_PUNCT, + ['~'] = CT_PRINT | CT_PUNCT, + // 127 - 255 +}; + +static inline bool is_alnum(uint8_t c) +{ + return char_mask[c] & (CT_DIGIT | CT_UPPER | CT_LOWER); +} + +static inline bool is_alpha(uint8_t c) +{ + return char_mask[c] & (CT_UPPER | CT_LOWER); +} + +static inline bool is_digit(uint8_t c) +{ + return char_mask[c] & CT_DIGIT; +} + +static inline bool is_xdigit(uint8_t c) +{ + return char_mask[c] & CT_XDIGT; +} + +static inline bool is_lower(uint8_t c) +{ + return char_mask[c] & CT_LOWER; +} + +static inline bool is_upper(uint8_t c) +{ + return char_mask[c] & CT_UPPER; +} + +static inline bool is_print(uint8_t c) +{ + return char_mask[c] & CT_PRINT; +} + +static inline bool is_punct(uint8_t c) +{ + return char_mask[c] & CT_PUNCT; +} + +static inline bool is_space(uint8_t c) +{ + return char_mask[c] & CT_SPACE; +} diff --git a/src/contrib/dnstap/convert.c b/src/contrib/dnstap/convert.c new file mode 100644 index 0000000..c9dca1d --- /dev/null +++ b/src/contrib/dnstap/convert.c @@ -0,0 +1,146 @@ +/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <sys/socket.h> +#include <sys/types.h> +#include <netinet/in.h> +#include <netinet/ip.h> + +#include "contrib/dnstap/convert.h" +#include "contrib/dnstap/dnstap.pb-c.h" +#include "libknot/probe/data.h" + +/*! + * \brief Translation between real and Dnstap value. + */ +typedef struct mapping { + int real; + int dnstap; +} mapping_t; + +/*! + * \brief Mapping for network family. + */ +static const mapping_t SOCKET_FAMILY_MAPPING[] = { + { AF_INET, DNSTAP__SOCKET_FAMILY__INET }, + { AF_INET6, DNSTAP__SOCKET_FAMILY__INET6 }, + { 0 } +}; + +/*! + * \brief Mapping from network protocol. + */ +static const mapping_t SOCKET_PROTOCOL_MAPPING[] = { + { KNOT_PROBE_PROTO_UDP, DNSTAP__SOCKET_PROTOCOL__UDP }, + { KNOT_PROBE_PROTO_TCP, DNSTAP__SOCKET_PROTOCOL__TCP }, + { KNOT_PROBE_PROTO_TLS, DNSTAP__SOCKET_PROTOCOL__DOT }, + { KNOT_PROBE_PROTO_HTTPS, DNSTAP__SOCKET_PROTOCOL__DOH }, + { KNOT_PROBE_PROTO_QUIC, DNSTAP__SOCKET_PROTOCOL__DOQ }, + { 0 } +}; + +/*! + * \brief Get Dnstap value for a given real value. + */ +static int encode(const mapping_t *mapping, int real) +{ + for (const mapping_t *m = mapping; m->dnstap != 0; m += 1) { + if (m->real == real) { + return m->dnstap; + } + } + + return 0; +} + +/*! + * \brief Get real value for a given Dnstap value. + */ +static int decode(const mapping_t *mapping, int dnstap) +{ + for (const mapping_t *m = mapping; m->dnstap != 0; m += 1) { + if (m->dnstap == dnstap) { + return m->real; + } + } + + return 0; +} + +/* -- public API ----------------------------------------------------------- */ + +Dnstap__SocketFamily dt_family_encode(int family) +{ + return encode(SOCKET_FAMILY_MAPPING, family); +} + +int dt_family_decode(Dnstap__SocketFamily dnstap_family) +{ + return decode(SOCKET_FAMILY_MAPPING, dnstap_family); +} + +Dnstap__SocketProtocol dt_protocol_encode(int protocol) +{ + return encode(SOCKET_PROTOCOL_MAPPING, protocol); +} + +int dt_protocol_decode(Dnstap__SocketProtocol dnstap_protocol) +{ + return decode(SOCKET_PROTOCOL_MAPPING, dnstap_protocol); +} + +bool dt_message_type_is_query(Dnstap__Message__Type type) +{ + switch (type) { + case DNSTAP__MESSAGE__TYPE__AUTH_QUERY: + case DNSTAP__MESSAGE__TYPE__CLIENT_QUERY: + case DNSTAP__MESSAGE__TYPE__FORWARDER_QUERY: + case DNSTAP__MESSAGE__TYPE__RESOLVER_QUERY: + case DNSTAP__MESSAGE__TYPE__STUB_QUERY: + case DNSTAP__MESSAGE__TYPE__TOOL_QUERY: + return true; + default: + return false; + } +} + +bool dt_message_type_is_response(Dnstap__Message__Type type) +{ + switch (type) { + case DNSTAP__MESSAGE__TYPE__AUTH_RESPONSE: + case DNSTAP__MESSAGE__TYPE__CLIENT_RESPONSE: + case DNSTAP__MESSAGE__TYPE__FORWARDER_RESPONSE: + case DNSTAP__MESSAGE__TYPE__RESOLVER_RESPONSE: + case DNSTAP__MESSAGE__TYPE__STUB_RESPONSE: + case DNSTAP__MESSAGE__TYPE__TOOL_RESPONSE: + return true; + default: + return false; + } +} + +bool dt_message_role_is_initiator(Dnstap__Message__Type type) +{ + switch (type) { + case DNSTAP__MESSAGE__TYPE__AUTH_QUERY: + case DNSTAP__MESSAGE__TYPE__AUTH_RESPONSE: + case DNSTAP__MESSAGE__TYPE__CLIENT_QUERY: + case DNSTAP__MESSAGE__TYPE__CLIENT_RESPONSE: + return false; + default: + return true; + } +} diff --git a/src/contrib/dnstap/convert.h b/src/contrib/dnstap/convert.h new file mode 100644 index 0000000..0e2a86a --- /dev/null +++ b/src/contrib/dnstap/convert.h @@ -0,0 +1,60 @@ +/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +/*! + * \brief Dnstap identifiers conversions. + */ + +#pragma once + +#include <stdbool.h> + +#include "contrib/dnstap/dnstap.pb-c.h" + +/*! + * \brief Get Dnstap socket family from the real one. + */ +Dnstap__SocketFamily dt_family_encode(int family); + +/*! + * \brief Get real socket family from the Dnstap one. + */ +int dt_family_decode(Dnstap__SocketFamily dnstap_family); + +/*! + * \brief Get Dnstap protocol from a real one. + */ +Dnstap__SocketProtocol dt_protocol_encode(int protocol); + +/*! + * \brief Get real protocol from the Dnstap one. + */ +int dt_protocol_decode(Dnstap__SocketProtocol dnstap_protocol); + +/*! + * Check if a message type is any type of a query. + */ +bool dt_message_type_is_query(Dnstap__Message__Type type); + +/*! + * Check if a message type is any type of a response. + */ +bool dt_message_type_is_response(Dnstap__Message__Type type); + +/*! + * Check if a message role is any type of an initiator. + */ +bool dt_message_role_is_initiator(Dnstap__Message__Type type); diff --git a/src/contrib/dnstap/dnstap.c b/src/contrib/dnstap/dnstap.c new file mode 100644 index 0000000..1516b4d --- /dev/null +++ b/src/contrib/dnstap/dnstap.c @@ -0,0 +1,41 @@ +/* Copyright (C) 2014 Farsight Security, Inc. <software@farsightsecurity.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <stdint.h> +#include <stdlib.h> + +#include "contrib/dnstap/dnstap.h" +#include "contrib/dnstap/dnstap.pb-c.h" + +#define DNSTAP_INITIAL_BUF_SIZE 256 + +uint8_t* dt_pack(const Dnstap__Dnstap *d, uint8_t **buf, size_t *sz) +{ + ProtobufCBufferSimple sbuf = { { NULL } }; + + sbuf.base.append = protobuf_c_buffer_simple_append; + sbuf.len = 0; + sbuf.alloced = DNSTAP_INITIAL_BUF_SIZE; + sbuf.data = malloc(sbuf.alloced); + if (sbuf.data == NULL) { + return NULL; + } + sbuf.must_free_data = 1; + + *sz = dnstap__dnstap__pack_to_buffer(d, (ProtobufCBuffer *) &sbuf); + *buf = sbuf.data; + return *buf; +} diff --git a/src/contrib/dnstap/dnstap.h b/src/contrib/dnstap/dnstap.h new file mode 100644 index 0000000..f7aecbc --- /dev/null +++ b/src/contrib/dnstap/dnstap.h @@ -0,0 +1,47 @@ +/* Copyright (C) 2014 Farsight Security, Inc. <software@farsightsecurity.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +/*! + * \author Robert Edmonds <edmonds@fsi.io> + * + * \brief Public interface for dnstap. + */ + +#pragma once + +#include <stddef.h> +#include <stdint.h> + +#include "contrib/dnstap/dnstap.pb-c.h" + +/*! \brief Frame Streams "Content Type" value for dnstap. */ +#define DNSTAP_CONTENT_TYPE "protobuf:dnstap.Dnstap" + +/*! + * \brief Serializes a filled out dnstap protobuf struct. Dynamically allocates + * storage for the serialized frame. + * + * \note This function returns a copy of its parameter return value 'buf' to + * make error checking slightly easier. + * + * \param d dnstap protobuf struct. + * \param[out] buf Serialized frame. + * \param[out] sz Size in bytes of the serialized frame. + * + * \return Serialized frame. + * \retval NULL if error. + */ +uint8_t* dt_pack(const Dnstap__Dnstap *d, uint8_t **buf, size_t *sz); diff --git a/src/contrib/dnstap/dnstap.proto b/src/contrib/dnstap/dnstap.proto new file mode 100644 index 0000000..dfc8ad4 --- /dev/null +++ b/src/contrib/dnstap/dnstap.proto @@ -0,0 +1,331 @@ +// dnstap: flexible, structured event replication format for DNS software +// +// This file contains the protobuf schemas for the "dnstap" structured event +// replication format for DNS software. + +// Written in 2013-2014 by Farsight Security, Inc. +// +// To the extent possible under law, the author(s) have dedicated all +// copyright and related and neighboring rights to this file to the public +// domain worldwide. This file is distributed without any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication along +// with this file. If not, see: +// +// <http://creativecommons.org/publicdomain/zero/1.0/>. + +syntax = "proto2"; +package dnstap; + +// "Dnstap": this is the top-level dnstap type, which is a "union" type that +// contains other kinds of dnstap payloads, although currently only one type +// of dnstap payload is defined. +// See: https://developers.google.com/protocol-buffers/docs/techniques#union +message Dnstap { + // DNS server identity. + // If enabled, this is the identity string of the DNS server which generated + // this message. Typically this would be the same string as returned by an + // "NSID" (RFC 5001) query. + optional bytes identity = 1; + + // DNS server version. + // If enabled, this is the version string of the DNS server which generated + // this message. Typically this would be the same string as returned by a + // "version.bind" query. + optional bytes version = 2; + + // Extra data for this payload. + // This field can be used for adding an arbitrary byte-string annotation to + // the payload. No encoding or interpretation is applied or enforced. + optional bytes extra = 3; + + // Identifies which field below is filled in. + enum Type { + MESSAGE = 1; + } + required Type type = 15; + + // One of the following will be filled in. + optional Message message = 14; +} + +// SocketFamily: the network protocol family of a socket. This specifies how +// to interpret "network address" fields. +enum SocketFamily { + INET = 1; // IPv4 (RFC 791) + INET6 = 2; // IPv6 (RFC 2460) +} + +// SocketProtocol: the protocol used to transport a DNS message. +enum SocketProtocol { + UDP = 1; // DNS over UDP transport (RFC 1035 section 4.2.1) + TCP = 2; // DNS over TCP transport (RFC 1035 section 4.2.2) + DOT = 3; // DNS over TLS (RFC 7858) + DOH = 4; // DNS over HTTPS (RFC 8484) + DNSCryptUDP = 5; // DNSCrypt over UDP (https://dnscrypt.info/protocol) + DNSCryptTCP = 6; // DNSCrypt over TCP (https://dnscrypt.info/protocol) + DOQ = 7; // DNS over QUIC (RFC 9250) +} + +// Policy: information about any name server operator policy +// applied to the processing of a DNS message. +message Policy { + + // Match: what aspect of the message or message exchange + // triggered the application of the Policy. + enum Match { + QNAME = 1; // Name in question section of query + CLIENT_IP = 2; // Client IP address + RESPONSE_IP = 3; // Address in A/AAAA RRSet + NS_NAME = 4; // Authoritative name server, by name + NS_IP = 5; // Authoritative name server, by IP address + } + + // The Action taken to implement the Policy. + enum Action { + NXDOMAIN = 1; // Respond with NXDOMAIN + NODATA = 2; // Respond with empty answer section + PASS = 3; // Do not alter the response (passthrough) + DROP = 4; // Do not respond. + TRUNCATE = 5; // Truncate UDP response, forcing TCP retry + LOCAL_DATA = 6; // Respond with local data from policy + } + + // type: the type of policy applied, e.g. "RPZ" for a + // policy from a Response Policy Zone. + optional string type = 1; + + // rule: the rule matched by the message. + // + // In a RPZ context, this is the owner name of the rule in + // the Reponse Policy Zone in wire format. + optional bytes rule = 2; + + // action: the policy action taken in response to the + // rule match. + optional Action action = 3; + + // match: the feature of the message exchange which matched the rule. + optional Match match = 4; + + // The matched value. Format depends on the matched feature . + optional bytes value = 5; +} + +// Message: a wire-format (RFC 1035 section 4) DNS message and associated +// metadata. Applications generating "Message" payloads should follow +// certain requirements based on the MessageType, see below. +message Message { + + // There are eight types of "Message" defined that correspond to the + // four arrows in the following diagram, slightly modified from RFC 1035 + // section 2: + + // +---------+ +----------+ +--------+ + // | | query | | query | | + // | Stub |-SQ--------CQ->| Recursive|-RQ----AQ->| Auth. | + // | Resolver| | Server | | Name | + // | |<-SR--------CR-| |<-RR----AR-| Server | + // +---------+ response | | response | | + // +----------+ +--------+ + + // Each arrow has two Type values each, one for each "end" of each arrow, + // because these are considered to be distinct events. Each end of each + // arrow on the diagram above has been marked with a two-letter Type + // mnemonic. Clockwise from upper left, these mnemonic values are: + // + // SQ: STUB_QUERY + // CQ: CLIENT_QUERY + // RQ: RESOLVER_QUERY + // AQ: AUTH_QUERY + // AR: AUTH_RESPONSE + // RR: RESOLVER_RESPONSE + // CR: CLIENT_RESPONSE + // SR: STUB_RESPONSE + + // Two additional types of "Message" have been defined for the + // "forwarding" case where an upstream DNS server is responsible for + // further recursion. These are not shown on the diagram above, but have + // the following mnemonic values: + + // FQ: FORWARDER_QUERY + // FR: FORWARDER_RESPONSE + + // The "Message" Type values are defined below. + + enum Type { + // AUTH_QUERY is a DNS query message received from a resolver by an + // authoritative name server, from the perspective of the authoritative + // name server. + AUTH_QUERY = 1; + + // AUTH_RESPONSE is a DNS response message sent from an authoritative + // name server to a resolver, from the perspective of the authoritative + // name server. + AUTH_RESPONSE = 2; + + // RESOLVER_QUERY is a DNS query message sent from a resolver to an + // authoritative name server, from the perspective of the resolver. + // Resolvers typically clear the RD (recursion desired) bit when + // sending queries. + RESOLVER_QUERY = 3; + + // RESOLVER_RESPONSE is a DNS response message received from an + // authoritative name server by a resolver, from the perspective of + // the resolver. + RESOLVER_RESPONSE = 4; + + // CLIENT_QUERY is a DNS query message sent from a client to a DNS + // server which is expected to perform further recursion, from the + // perspective of the DNS server. The client may be a stub resolver or + // forwarder or some other type of software which typically sets the RD + // (recursion desired) bit when querying the DNS server. The DNS server + // may be a simple forwarding proxy or it may be a full recursive + // resolver. + CLIENT_QUERY = 5; + + // CLIENT_RESPONSE is a DNS response message sent from a DNS server to + // a client, from the perspective of the DNS server. The DNS server + // typically sets the RA (recursion available) bit when responding. + CLIENT_RESPONSE = 6; + + // FORWARDER_QUERY is a DNS query message sent from a downstream DNS + // server to an upstream DNS server which is expected to perform + // further recursion, from the perspective of the downstream DNS + // server. + FORWARDER_QUERY = 7; + + // FORWARDER_RESPONSE is a DNS response message sent from an upstream + // DNS server performing recursion to a downstream DNS server, from the + // perspective of the downstream DNS server. + FORWARDER_RESPONSE = 8; + + // STUB_QUERY is a DNS query message sent from a stub resolver to a DNS + // server, from the perspective of the stub resolver. + STUB_QUERY = 9; + + // STUB_RESPONSE is a DNS response message sent from a DNS server to a + // stub resolver, from the perspective of the stub resolver. + STUB_RESPONSE = 10; + + // TOOL_QUERY is a DNS query message sent from a DNS software tool to a + // DNS server, from the perspective of the tool. + TOOL_QUERY = 11; + + // TOOL_RESPONSE is a DNS response message received by a DNS software + // tool from a DNS server, from the perspective of the tool. + TOOL_RESPONSE = 12; + + // UPDATE_QUERY is a Dynamic DNS Update request (RFC 2136) received + // by an authoritative name server, from the perspective of the + // authoritative name server. + UPDATE_QUERY = 13; + + // UPDATE_RESPONSE is a Dynamic DNS Update response (RFC 2136) sent + // from an authoritative name server, from the perspective of the + // authoritative name server. + UPDATE_RESPONSE = 14; + } + + // One of the Type values described above. + required Type type = 1; + + // One of the SocketFamily values described above. + optional SocketFamily socket_family = 2; + + // One of the SocketProtocol values described above. + optional SocketProtocol socket_protocol = 3; + + // The network address of the message initiator. + // For SocketFamily INET, this field is 4 octets (IPv4 address). + // For SocketFamily INET6, this field is 16 octets (IPv6 address). + optional bytes query_address = 4; + + // The network address of the message responder. + // For SocketFamily INET, this field is 4 octets (IPv4 address). + // For SocketFamily INET6, this field is 16 octets (IPv6 address). + optional bytes response_address = 5; + + // The transport port of the message initiator. + // This is a 16-bit UDP or TCP port number, depending on SocketProtocol. + optional uint32 query_port = 6; + + // The transport port of the message responder. + // This is a 16-bit UDP or TCP port number, depending on SocketProtocol. + optional uint32 response_port = 7; + + // The time at which the DNS query message was sent or received, depending + // on whether this is an AUTH_QUERY, RESOLVER_QUERY, or CLIENT_QUERY. + // This is the number of seconds since the UNIX epoch. + optional uint64 query_time_sec = 8; + + // The time at which the DNS query message was sent or received. + // This is the seconds fraction, expressed as a count of nanoseconds. + optional fixed32 query_time_nsec = 9; + + // The initiator's original wire-format DNS query message, verbatim. + optional bytes query_message = 10; + + // The "zone" or "bailiwick" pertaining to the DNS query message. + // This is a wire-format DNS domain name. + optional bytes query_zone = 11; + + // The time at which the DNS response message was sent or received, + // depending on whether this is an AUTH_RESPONSE, RESOLVER_RESPONSE, or + // CLIENT_RESPONSE. + // This is the number of seconds since the UNIX epoch. + optional uint64 response_time_sec = 12; + + // The time at which the DNS response message was sent or received. + // This is the seconds fraction, expressed as a count of nanoseconds. + optional fixed32 response_time_nsec = 13; + + // The responder's original wire-format DNS response message, verbatim. + optional bytes response_message = 14; + + // Operator policy applied to the processing of this message, if any. + optional Policy policy = 15; +} + +// All fields except for 'type' in the Message schema are optional. +// It is recommended that at least the following fields be filled in for +// particular types of Messages. + +// AUTH_QUERY: +// socket_family, socket_protocol +// query_address, query_port +// query_message +// query_time_sec, query_time_nsec + +// AUTH_RESPONSE: +// socket_family, socket_protocol +// query_address, query_port +// query_time_sec, query_time_nsec +// response_message +// response_time_sec, response_time_nsec + +// RESOLVER_QUERY: +// socket_family, socket_protocol +// query_message +// query_time_sec, query_time_nsec +// query_zone +// response_address, response_port + +// RESOLVER_RESPONSE: +// socket_family, socket_protocol +// query_time_sec, query_time_nsec +// query_zone +// response_address, response_port +// response_message +// response_time_sec, response_time_nsec + +// CLIENT_QUERY: +// socket_family, socket_protocol +// query_message +// query_time_sec, query_time_nsec + +// CLIENT_RESPONSE: +// socket_family, socket_protocol +// query_time_sec, query_time_nsec +// response_message +// response_time_sec, response_time_nsec diff --git a/src/contrib/dnstap/message.c b/src/contrib/dnstap/message.c new file mode 100644 index 0000000..a5f798e --- /dev/null +++ b/src/contrib/dnstap/message.c @@ -0,0 +1,130 @@ +/* Copyright (C) 2017 Farsight Security, Inc. <software@farsightsecurity.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <netinet/in.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include "libknot/errcode.h" + +#include "contrib/dnstap/convert.h" +#include "contrib/dnstap/message.h" + +static void set_address(const struct sockaddr *sockaddr, + ProtobufCBinaryData *addr, + protobuf_c_boolean *has_addr, + uint32_t *port, + protobuf_c_boolean *has_port) +{ + if (sockaddr == NULL) { + *has_addr = 0; + *has_port = 0; + return; + } + + *has_addr = 1; + *has_port = 1; + + if (sockaddr->sa_family == AF_INET) { + const struct sockaddr_in *sai; + sai = (const struct sockaddr_in *)sockaddr; + addr->len = sizeof(sai->sin_addr); + addr->data = (uint8_t *)&sai->sin_addr.s_addr; + *port = ntohs(sai->sin_port); + } else if (sockaddr->sa_family == AF_INET6) { + const struct sockaddr_in6 *sai6; + sai6 = (const struct sockaddr_in6 *)sockaddr; + addr->len = sizeof(sai6->sin6_addr); + addr->data = (uint8_t *)&sai6->sin6_addr.s6_addr; + *port = ntohs(sai6->sin6_port); + } +} + +static int get_family(const struct sockaddr *query_sa, + const struct sockaddr *response_sa) +{ + const struct sockaddr *source = query_sa ? query_sa : response_sa; + if (source == NULL) { + return 0; + } + + return dt_family_encode(source->sa_family); +} + +int dt_message_fill(Dnstap__Message *m, + const Dnstap__Message__Type type, + const struct sockaddr *query_sa, + const struct sockaddr *response_sa, + const int protocol, + const void *wire, + const size_t len_wire, + const struct timespec *mtime) +{ + if (m == NULL) { + return KNOT_EINVAL; + } + + memset(m, 0, sizeof(*m)); + + m->base.descriptor = &dnstap__message__descriptor; + + // Message.type + m->type = type; + + // Message.socket_family + m->socket_family = get_family(query_sa, response_sa); + m->has_socket_family = m->socket_family != 0; + + // Message.socket_protocol + m->socket_protocol = dt_protocol_encode(protocol); + m->has_socket_protocol = m->socket_protocol != 0; + + // Message addresses + set_address(query_sa, &m->query_address, &m->has_query_address, + &m->query_port, &m->has_query_port); + set_address(response_sa, &m->response_address, &m->has_response_address, + &m->response_port, &m->has_response_port); + + if (dt_message_type_is_query(type)) { + // Message.query_message + m->query_message.len = len_wire; + m->query_message.data = (uint8_t *)wire; + m->has_query_message = 1; + // Message.query_time_sec, Message.query_time_nsec + if (mtime != NULL) { + m->query_time_sec = mtime->tv_sec; + m->query_time_nsec = mtime->tv_nsec; + m->has_query_time_sec = 1; + m->has_query_time_nsec = 1; + } + } else if (dt_message_type_is_response(type)) { + // Message.response_message + m->response_message.len = len_wire; + m->response_message.data = (uint8_t *)wire; + m->has_response_message = 1; + // Message.response_time_sec, Message.response_time_nsec + if (mtime != NULL) { + m->response_time_sec = mtime->tv_sec; + m->response_time_nsec = mtime->tv_nsec; + m->has_response_time_sec = 1; + m->has_response_time_nsec = 1; + } + } + + return KNOT_EOK; +} diff --git a/src/contrib/dnstap/message.h b/src/contrib/dnstap/message.h new file mode 100644 index 0000000..b9e3aff --- /dev/null +++ b/src/contrib/dnstap/message.h @@ -0,0 +1,63 @@ +/* Copyright (C) 2017 Farsight Security, Inc. <software@farsightsecurity.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +/*! + * \author Robert Edmonds <edmonds@fsi.io> + * + * \brief Dnstap message interface. + */ + +#pragma once + +#include <sys/socket.h> +#include <sys/time.h> +#include <stddef.h> + +#include "contrib/dnstap/dnstap.pb-c.h" + +/*! + * \brief Fill a Dnstap__Message structure with the given parameters. + * + * \param[out] m + * Dnstap__Message structure to fill. Will be zeroed first. + * \param type + * One of the DNSTAP__MESSAGE__TYPE__* values. + * \param query_sa + * sockaddr_in or sockaddr_in6 to use when filling the 'socket_family', + * 'query_address', 'query_port' fields. + * \param response_sa + * sockaddr_in or sockaddr_in6 to use when filling the 'socket_family', + * 'response_address', 'response_port' fields. + * \param protocol + * \c IPPROTO_UDP or \c IPPROTO_TCP. + * \param wire + * Wire-format query message or response message (depending on 'type'). + * \param len_wire + * Length in bytes of 'wire'. + * \param mtime + * Message time. May be NULL. + * + * \retval KNOT_EOK + * \retval KNOT_EINVAL + */ +int dt_message_fill(Dnstap__Message *m, + const Dnstap__Message__Type type, + const struct sockaddr *query_sa, + const struct sockaddr *response_sa, + const int protocol, + const void *wire, + const size_t len_wire, + const struct timespec *mtime); diff --git a/src/contrib/dnstap/reader.c b/src/contrib/dnstap/reader.c new file mode 100644 index 0000000..dc1c053 --- /dev/null +++ b/src/contrib/dnstap/reader.c @@ -0,0 +1,103 @@ +/* Copyright (C) 2014 Farsight Security, Inc. <software@farsightsecurity.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "libknot/attribute.h" +#include "libknot/errcode.h" + +#include "contrib/dnstap/dnstap.h" +#include "contrib/dnstap/reader.h" + +dt_reader_t* dt_reader_create(const char *file_path) +{ + struct fstrm_file_options *fopt = NULL; + struct fstrm_reader_options *ropt = NULL; + dt_reader_t *reader = NULL; + fstrm_res res; + + reader = calloc(1, sizeof(dt_reader_t)); + if (reader == NULL) { + goto fail; + } + + // Open reader. + fopt = fstrm_file_options_init(); + fstrm_file_options_set_file_path(fopt, file_path); + ropt = fstrm_reader_options_init(); + fstrm_reader_options_add_content_type(ropt, + (const uint8_t *) DNSTAP_CONTENT_TYPE, + strlen(DNSTAP_CONTENT_TYPE)); + reader->fr = fstrm_file_reader_init(fopt, ropt); + fstrm_file_options_destroy(&fopt); + fstrm_reader_options_destroy(&ropt); + if (reader->fr == NULL) { + goto fail; + } + res = fstrm_reader_open(reader->fr); + if (res != fstrm_res_success) { + goto fail; + } + + return reader; +fail: + dt_reader_free(reader); + return NULL; +} + +void dt_reader_free(dt_reader_t *reader) +{ + if (reader == NULL) { + return; + } + + fstrm_reader_destroy(&reader->fr); + free(reader); +} + +int dt_reader_read(dt_reader_t *reader, Dnstap__Dnstap **d) +{ + fstrm_res res; + const uint8_t *data = NULL; + size_t len = 0; + + res = fstrm_reader_read(reader->fr, &data, &len); + if (res == fstrm_res_success) { + *d = dnstap__dnstap__unpack(NULL, len, data); + if (*d == NULL) { + return KNOT_ENOMEM; + } + } else if (res == fstrm_res_failure) { + return KNOT_ERROR; + } else if (res == fstrm_res_stop) { + return KNOT_EOF; + } + + return KNOT_EOK; +} + +void dt_reader_free_frame(_unused_ dt_reader_t *reader, Dnstap__Dnstap **frame_ptr) +{ + if (!*frame_ptr) { + return; + } + + dnstap__dnstap__free_unpacked(*frame_ptr, NULL); + *frame_ptr = NULL; +} diff --git a/src/contrib/dnstap/reader.h b/src/contrib/dnstap/reader.h new file mode 100644 index 0000000..cc6385f --- /dev/null +++ b/src/contrib/dnstap/reader.h @@ -0,0 +1,73 @@ +/* Copyright (C) 2017 Farsight Security, Inc. <software@farsightsecurity.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +/*! + * \brief Dnstap file reader. + */ + +#pragma once + +#include <fstrm.h> +#include <protobuf-c/protobuf-c.h> + +#include "contrib/dnstap/dnstap.pb-c.h" + +/*! \brief Structure for dnstap file reader. */ +typedef struct { + /*!< Input reader. */ + struct fstrm_reader *fr; +} dt_reader_t; + +/*! + * \brief Creates dnstap file reader structure. + * + * \param file_path Name of file to read input from. + * + * \retval reader if success. + * \retval NULL if error. + */ +dt_reader_t* dt_reader_create(const char *file_path); + +/*! + * \brief Close dnstap file reader. + * + * \param reader dnstap file reader structure. + */ +void dt_reader_free(dt_reader_t *reader); + +/*! + * \brief Read a dnstap protobuf from a dnstap file reader. + * + * Caller must deallocate the returned protobuf with the + * dnstap__dnstap__free_unpacked() function. + * + * \param[in] reader dnstap file reader structure. + * \param[out] d Unpacked dnstap protobuf. + * + * \retval KNOT_EOK + * \retval KNOT_ERROR + * \retval KNOT_EOF + * \retval KNOT_ENOMEM + */ +int dt_reader_read(dt_reader_t *reader, Dnstap__Dnstap **d); + +/*! + * \brief free the frame allocated by dt_read_data. + * + * \param reader Dnstap reader context. + * \param d The frame to be freed. + */ +void dt_reader_free_frame(dt_reader_t *reader, Dnstap__Dnstap **d); diff --git a/src/contrib/dnstap/writer.c b/src/contrib/dnstap/writer.c new file mode 100644 index 0000000..03961c1 --- /dev/null +++ b/src/contrib/dnstap/writer.c @@ -0,0 +1,120 @@ +/* Copyright (C) 2014 Farsight Security, Inc. <software@farsightsecurity.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "libknot/errcode.h" + +#include "contrib/dnstap/dnstap.h" +#include "contrib/dnstap/writer.h" + +dt_writer_t* dt_writer_create(const char *file_path, const char *version) +{ + struct fstrm_file_options *fopt = NULL; + struct fstrm_writer_options *wopt = NULL; + dt_writer_t *writer = NULL; + fstrm_res res; + + writer = calloc(1, sizeof(dt_writer_t)); + if (writer == NULL) { + goto fail; + } + + // Set "version". + if (version != NULL) { + writer->len_version = strlen(version); + writer->version = strdup(version); + if (!writer->version) { + goto fail; + } + } + + // Open writer. + fopt = fstrm_file_options_init(); + fstrm_file_options_set_file_path(fopt, file_path); + wopt = fstrm_writer_options_init(); + fstrm_writer_options_add_content_type(wopt, + (const uint8_t *) DNSTAP_CONTENT_TYPE, + strlen(DNSTAP_CONTENT_TYPE)); + writer->fw = fstrm_file_writer_init(fopt, wopt); + fstrm_file_options_destroy(&fopt); + fstrm_writer_options_destroy(&wopt); + if (writer->fw == NULL) { + goto fail; + } + + res = fstrm_writer_open(writer->fw); + if (res != fstrm_res_success) { + goto fail; + } + + return writer; +fail: + dt_writer_free(writer); + return NULL; +} + +void dt_writer_free(dt_writer_t *writer) +{ + if (writer == NULL) { + return; + } + + fstrm_writer_destroy(&writer->fw); + free(writer->version); + free(writer); +} + +int dt_writer_write(dt_writer_t *writer, const ProtobufCMessage *msg) +{ + Dnstap__Dnstap dnstap = DNSTAP__DNSTAP__INIT; + size_t len; + uint8_t *data; + + if (writer->fw == NULL) { + return KNOT_EOK; + } + + // Only handle dnstap/Message. + assert(msg->descriptor == &dnstap__message__descriptor); + + // Fill out 'dnstap'. + if (writer->version) { + dnstap.version.data = writer->version; + dnstap.version.len = writer->len_version; + dnstap.has_version = 1; + } + dnstap.type = DNSTAP__DNSTAP__TYPE__MESSAGE; + dnstap.message = (Dnstap__Message *)msg; + + // Serialize the dnstap frame. + if (!dt_pack(&dnstap, &data, &len)) { + return KNOT_ENOMEM; + } + + // Write the dnstap frame to the output stream. + if (fstrm_writer_write(writer->fw, data, len) != fstrm_res_success) { + return KNOT_ERROR; + } + + // Cleanup. + free(data); + + return KNOT_EOK; +} diff --git a/src/contrib/dnstap/writer.h b/src/contrib/dnstap/writer.h new file mode 100644 index 0000000..e6928c5 --- /dev/null +++ b/src/contrib/dnstap/writer.h @@ -0,0 +1,71 @@ +/* Copyright (C) 2014 Farsight Security, Inc. <software@farsightsecurity.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +/*! + * \author Robert Edmonds <edmonds@fsi.io> + * + * \brief Dnstap file writer. + */ + +#pragma once + +#include <fstrm.h> +#include <protobuf-c/protobuf-c.h> + +/*! \brief Structure for dnstap file writer. */ +typedef struct { + /*!< Output writer. */ + struct fstrm_writer *fw; + + /*!< dnstap "version" field. */ + void *version; + + /*!< length of dnstap "version" field. */ + size_t len_version; +} dt_writer_t; + +/*! + * \brief Creates dnstap file writer structure. + * + * \param file_path Name of file to write output to. + * \param version Version string of software. May be NULL. + * + * \retval writer if success. + * \retval NULL if error. + */ +dt_writer_t* dt_writer_create(const char *file_path, const char *version); + +/*! + * \brief Finish writing dnstap file writer and free resources. + * + * \param writer dnstap file writer structure. + */ +void dt_writer_free(dt_writer_t *writer); + +/*! + * \brief Write a protobuf to the dnstap file writer. + * + * Supported protobuf types for the 'msg' parameter: + * \c Dnstap__Message + * + * \param writer dnstap file writer structure. + * \param msg dnstap protobuf. Must be a supported type. + * + * \retval KNOT_EOK + * \retval KNOT_EINVAL + * \retval KNOT_ENOMEM + */ +int dt_writer_write(dt_writer_t *writer, const ProtobufCMessage *msg); diff --git a/src/contrib/files.c b/src/contrib/files.c new file mode 100644 index 0000000..5b38469 --- /dev/null +++ b/src/contrib/files.c @@ -0,0 +1,255 @@ +/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <dirent.h> +#include <fcntl.h> +#include <ftw.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "contrib/files.h" +#include "contrib/string.h" +#include "libknot/errcode.h" + +#if defined(MAXBSIZE) + #define BUFSIZE MAXBSIZE +#else + #define BUFSIZE (64 * 1024) +#endif + +char* abs_path(const char *path, const char *base_dir) +{ + if (path == NULL) { + return NULL; + } else if (path[0] == '/') { + return strdup(path); + } else { + char *full_path; + if (base_dir == NULL) { + char *cwd = realpath("./", NULL); + full_path = sprintf_alloc("%s/%s", cwd, path); + free(cwd); + } else { + full_path = sprintf_alloc("%s/%s", base_dir, path); + } + return full_path; + } +} + +bool same_path(const char *path1, const char *path2) +{ + bool equal = false; + int err = 0; + + struct stat sb1; + if (stat(path1, &sb1) == 0) { + struct stat sb2; + if (stat(path2, &sb2) == 0) { + if (sb1.st_dev == sb2.st_dev && + sb1.st_ino == sb2.st_ino) { + equal = true; + } + } else { + err = errno; + } + } else { + err = errno; + } + + if (err != 0) { + // Can't compare real absolute paths, as stat() failed already. Try the best. + char *full_path1 = abs_path(path1, NULL); + char *full_path2 = abs_path(path2, NULL); + + if (strcmp(full_path1, full_path2) == 0) { + equal = true; + } + + free(full_path1); + free(full_path2); + } + + return equal; +} + +static int remove_file(const char *path, const struct stat *stat, int type, struct FTW *ftw) +{ + (void)stat; + (void)ftw; + if (type == FTW_DP) { + return rmdir(path); + } else { + return unlink(path); + } +} + +bool remove_path(const char *path) +{ + return (0 == nftw(path, remove_file, 1, FTW_DEPTH | FTW_PHYS)); +} + +int make_dir(const char *path, mode_t mode, bool ignore_existing) +{ + if (mkdir(path, mode) == 0) { + return KNOT_EOK; + } + + if (!ignore_existing || errno != EEXIST) { + return knot_map_errno(); + } + + assert(errno == EEXIST); + + struct stat st = { 0 }; + if (stat(path, &st) != 0) { + return knot_map_errno(); + } + + if (!S_ISDIR(st.st_mode)) { + return KNOT_EEXIST; + } + + return KNOT_EOK; +} + +int make_path(const char *path, mode_t mode) +{ + if (path == NULL) { + return KNOT_EINVAL; + } + + char *dir = strdup(path); + if (dir == NULL) { + return KNOT_ENOMEM; + } + + for (char *p = strchr(dir + 1, '/'); p != NULL; p = strchr(p + 1, '/')) { + *p = '\0'; + if (mkdir(dir, mode) == -1 && errno != EEXIST) { + free(dir); + return knot_map_errno(); + } + *p = '/'; + } + + free(dir); + + return KNOT_EOK; +} + +int open_tmp_file(const char *path, char **tmp_name, FILE **file, mode_t mode) +{ + int ret; + + *tmp_name = sprintf_alloc("%s.XXXXXX", path); + if (*tmp_name == NULL) { + ret = KNOT_ENOMEM; + goto open_tmp_failed; + } + + int fd = mkstemp(*tmp_name); + if (fd < 0) { + ret = knot_map_errno(); + goto open_tmp_failed; + } + + if (fchmod(fd, mode) != 0) { + ret = knot_map_errno(); + close(fd); + unlink(*tmp_name); + goto open_tmp_failed; + } + + *file = fdopen(fd, "w"); + if (*file == NULL) { + ret = knot_map_errno(); + close(fd); + unlink(*tmp_name); + goto open_tmp_failed; + } + + return KNOT_EOK; +open_tmp_failed: + free(*tmp_name); + *tmp_name = NULL; + *file = NULL; + + assert(ret != KNOT_EOK); + return ret; +} + +int copy_file(const char *dest, const char *src) +{ + if (dest == NULL || src == NULL) { + return KNOT_EINVAL; + } + + int ret = 0; + char *buf = NULL, *tmp_name = NULL; + FILE *file = NULL; + + FILE *from = fopen(src, "r"); + if (from == NULL) { + ret = errno == ENOENT ? KNOT_EFILE : knot_map_errno(); + goto done; + } + + buf = malloc(sizeof(*buf) * BUFSIZE); + if (buf == NULL) { + ret = KNOT_ENOMEM; + goto done; + } + + ret = open_tmp_file(dest, &tmp_name, &file, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + if (ret != KNOT_EOK) { + goto done; + } + + ssize_t cnt; + while ((cnt = fread(buf, sizeof(*buf), BUFSIZE, from)) != 0 && + (ret = (fwrite(buf, sizeof(*buf), cnt, file) == cnt))) { + } + + ret = !ret || ferror(from); + if (ret != 0) { + ret = knot_map_errno(); + unlink(tmp_name); + goto done; + } + + ret = rename(tmp_name, dest); + if (ret != 0) { + ret = knot_map_errno(); + unlink(tmp_name); + goto done; + } + ret = KNOT_EOK; + +done: + free(tmp_name); + if (file != NULL) { + fclose(file); + } + free(buf); + if (from != NULL) { + fclose(from); + } + return ret; +} diff --git a/src/contrib/files.h b/src/contrib/files.h new file mode 100644 index 0000000..c505ac8 --- /dev/null +++ b/src/contrib/files.h @@ -0,0 +1,80 @@ +/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stdbool.h> +#include <stdio.h> +#include <sys/types.h> + +#define LMDB_DIR_MODE 0770 +#define LMDB_FILE_MODE 0660 + +/*! + * Gets the absolute path. + * + * \note The result must be explicitly deallocated. + * + * \param[in] path Absolute path or a relative path suffix; a string. + * \param[in] base_dir Path prefix for a relative string. + * + * \return Absolute path string pointer. + */ +char* abs_path(const char *path, const char *base_dir); + +/*! + * Try to compare two paths whether they are identical. + * + * \note If any of the two paths doesn't physically exist, their identity can't + * be detected in some special corner cases. + * + * \param[in] path1 Absolute or a relative path (a file, a directory, etc.) + * \param[in] path2 Absolute or a relative path (a file, a directory, etc.) + * + * \return True if both paths are identical (if they point to the same inode), + * false otherwise. + */ +bool same_path(const char *path1, const char *path2); + +/*! + * \brief Delete file or directory (recursive). + * + * \return true on success, false when one or more files failed to be removed. + */ +bool remove_path(const char *path); + +/*! + * Equivalent to mkdir(2), can succeed if the directory already exists. + */ +int make_dir(const char *path, mode_t mode, bool ignore_existing); + +/*! + * Makes a directory part of the path with all parent directories if not exist. + */ +int make_path(const char *path, mode_t mode); + +/*! + * Creates and opens for writing a temporary file based on given path. + */ +int open_tmp_file(const char *path, char **tmp_name, FILE **file, mode_t mode); + +/*! + * Copies a file, possibly overwriting existing one, as an atomic operation. + * + * \return KNOT_EOK on success, KNOT_EFILE if the source file doesn't exist, + * \or other KNOT_E* values in case of other errors. + */ +int copy_file(const char *dest, const char *src); diff --git a/src/contrib/getline.c b/src/contrib/getline.c new file mode 100644 index 0000000..46cdff9 --- /dev/null +++ b/src/contrib/getline.c @@ -0,0 +1,60 @@ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +// FreeBSD POSIX2008 getline +#ifndef _WITH_GETLINE +#define _WITH_GETLINE +#endif + +#include "contrib/getline.h" + +#include <stdio.h> // getline or fgetln +#include <stdlib.h> // free +#include <string.h> // memcpy + +ssize_t knot_getline(char **lineptr, size_t *n, FILE *stream) +{ +#ifdef HAVE_GETLINE + return getline(lineptr, n, stream); +#else +#ifdef HAVE_FGETLN + size_t length = 0; + char *buffer = fgetln(stream, &length); + if (buffer == NULL) { + return -1; + } + + /* NOTE: Function fgetln doesn't return terminated string! + * Output buffer from the fgetln can't be freed. + */ + + // If the output buffer is not specified or is small, extend it. + if (*lineptr == NULL || *n <= length) { + char *tmp = realloc(*lineptr, length + 1); + if (tmp == NULL) { + return -1; + } + *lineptr = tmp; + *n = length + 1; + } + + memcpy(*lineptr, buffer, length); + (*lineptr)[length] = '\0'; + + return length; +#endif +#endif +} diff --git a/src/contrib/getline.h b/src/contrib/getline.h new file mode 100644 index 0000000..80f2db1 --- /dev/null +++ b/src/contrib/getline.h @@ -0,0 +1,39 @@ +/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +/*! + * \brief Multiplatform getline wrapper. + */ + +#pragma once + +#include <stdio.h> +#include <sys/types.h> + +/*! + * \brief Reads a line from a stream. + * + * This function has the same semantics as POSIX.1-2008 getline(). + * If necessary, the output buffer will be allocated/reallocated. + * + * \param lineptr Output buffer. + * \param n Output buffer size. + * \param stream Input stream. + * + * \retval Number of characters read, including new line delimiter, + * not including terminating. -1 on error or EOF. + */ +ssize_t knot_getline(char **lineptr, size_t *n, FILE *stream); diff --git a/src/contrib/json.c b/src/contrib/json.c new file mode 100644 index 0000000..3a35fc6 --- /dev/null +++ b/src/contrib/json.c @@ -0,0 +1,260 @@ +/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +#include "contrib/json.h" + +#include "contrib/string.h" + +#define MAX_DEPTH 8 + +enum { + BLOCK_INVALID = 0, + BLOCK_OBJECT, + BLOCK_LIST, +}; + +/*! One indented block of JSON. */ +struct block { + /*! Block type. */ + int type; + /*! Number of elements written. */ + int count; +}; + +struct jsonw { + /*! Output file stream. */ + FILE *out; + /*! Indentaiton string. */ + const char *indent; + /*! List to be used as a stack of blocks in progress. */ + struct block stack[MAX_DEPTH]; + /*! Index pointing to the top of the stack. */ + int top; + /*! Newline needed indication. */ + bool wrap; +}; + +static const char *DEFAULT_INDENT = "\t"; + +static void start_block(jsonw_t *w, int type) +{ + assert(w->top > 0); + + struct block b = { + .type = type, + .count = 0, + }; + + w->top -= 1; + w->stack[w->top] = b; +} + +static struct block *cur_block(jsonw_t *w) +{ + if (w && w->top < MAX_DEPTH) { + return &w->stack[w->top]; + } + return NULL; +} + +/*! Insert new line and indent for the next write. */ +static void wrap(jsonw_t *w) +{ + if (!w->wrap) { + w->wrap = true; + return; + } + + fputc('\n', w->out); + int level = MAX_DEPTH - w->top; + for (int i = 0; i < level; i++) { + fprintf(w->out, "%s", w->indent); + } +} + +static void end_block(jsonw_t *w) +{ + assert(w->top < MAX_DEPTH); + + w->top += 1; +} + +static void escaped_print(jsonw_t *w, const char *str, size_t maxlen, bool quote) +{ + if (quote) { + fputc('"', w->out); + } + for (const char *pos = str; maxlen == SIZE_MAX ? (*pos != '\0') : (pos - str < maxlen); pos++) { + char c = *pos; + if (c == '\\' || c == '\"') { + fputc('\\', w->out); + } else if (c == '\0') { + fprintf(w->out, "\\u0000"); + continue; + } + fputc(c, w->out); + } + if (quote) { + fputc('"', w->out); + } +} + +static void align_key(jsonw_t *w, const char *key) +{ + struct block *top = cur_block(w); + if (top && top->count++) { + fputc(',', w->out); + } + + wrap(w); + + if (key && key[0]) { + escaped_print(w, key, SIZE_MAX, true); + fprintf(w->out, ": "); + } +} + +jsonw_t *jsonw_new(FILE *out, const char *indent) +{ + assert(out); + + jsonw_t *w = calloc(1, sizeof(*w)); + if (w == NULL) { + return w; + } + + w->out = out; + w->indent = indent ? indent : DEFAULT_INDENT; + w->top = MAX_DEPTH; + + return w; +} + +void jsonw_free(jsonw_t **w) +{ + if (w == NULL) { + return; + } + + wrap(*w); + + free(*w); + *w = NULL; +} + +void jsonw_null(jsonw_t *w, const char *key) +{ + assert(w); + + align_key(w, key); + fprintf(w->out, "null"); +} + +void jsonw_object(jsonw_t *w, const char *key) +{ + assert(w); + + align_key(w, key); + fprintf(w->out, "{"); + start_block(w, BLOCK_OBJECT); +} + +void jsonw_list(jsonw_t *w, const char *key) +{ + assert(w); + + align_key(w, key); + fprintf(w->out, "["); + start_block(w, BLOCK_LIST); +} + +void jsonw_str(jsonw_t *w, const char *key, const char *value) +{ + assert(w); + + align_key(w, key); + escaped_print(w, value, SIZE_MAX, true); +} + +void jsonw_str_len(jsonw_t *w, const char *key, const uint8_t *value, size_t len, bool quote) +{ + assert(w); + + align_key(w, key); + escaped_print(w, (const char *)value, len, quote); +} + +void jsonw_ulong(jsonw_t *w, const char *key, unsigned long value) +{ + assert(w); + + align_key(w, key); + fprintf(w->out, "%lu", value); +} + +void jsonw_int(jsonw_t *w, const char *key, int value) +{ + assert(w); + + align_key(w, key); + fprintf(w->out, "%d", value); +} + + +void jsonw_bool(jsonw_t *w, const char *key, bool value) +{ + assert(w); + + align_key(w, key); + fprintf(w->out, "%s", value ? "true" : "false"); +} + +void jsonw_hex(jsonw_t *w, const char *key, const uint8_t *data, size_t len) +{ + assert(w); + + char *hex = bin_to_hex(data, len, true); + if (hex != NULL) { + jsonw_str(w, key, hex); + } + free(hex); +} + +void jsonw_end(jsonw_t *w) +{ + assert(w); + + struct block *top = cur_block(w); + if (top == NULL) { + return; + } + + end_block(w); + wrap(w); + + switch (top->type) { + case BLOCK_OBJECT: + fprintf(w->out, "}"); + break; + case BLOCK_LIST: + fprintf(w->out, "]"); + break; + } +} diff --git a/src/contrib/json.h b/src/contrib/json.h new file mode 100644 index 0000000..cf8abe6 --- /dev/null +++ b/src/contrib/json.h @@ -0,0 +1,97 @@ +/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include <stdio.h> +#include <stdbool.h> +#include <stdint.h> + +/*! + * Simple pretty JSON writer. + */ +struct jsonw; +typedef struct jsonw jsonw_t; + +/*! + * Create new JSON writer. + * + * @param out Output file stream. + * @param indent Indentation string. + * + * @return JSON writer or NULL for allocation error. + */ +jsonw_t *jsonw_new(FILE *out, const char *indent); + +/*! + * Free JSON writer created with jsonw_new. + */ +void jsonw_free(jsonw_t **w); + +/*! + * Write null value as JSON. + */ +void jsonw_null(jsonw_t *w, const char *key); + +/*! + * Start writing a new object. + * + * The following writes will represent key and value pairs respectively until + * jsonw_end is called. + */ +void jsonw_object(jsonw_t *w, const char *key); + +/*! + * Start writing a new list. + * + * The following writes will represent values until jsonw_end is called. + */ +void jsonw_list(jsonw_t *w, const char *key); + +/*! + * Write string as JSON. The string will be escaped properly. + */ +void jsonw_str(jsonw_t *w, const char *key, const char *value); + +/*! + * Write string with specified length as JSON. The string will be escaped properly, including \0. + */ +void jsonw_str_len(jsonw_t *w, const char *key, const uint8_t *value, size_t len, bool quote); + +/*! + * Write unsigned long value as JSON. + */ +void jsonw_ulong(jsonw_t *w, const char *key, unsigned long value); + +/*! + * Write integer as JSON. + */ +void jsonw_int(jsonw_t *w, const char *key, int value); + +/*! + * Write boolean value as JSON. + */ +void jsonw_bool(jsonw_t *w, const char *key, bool value); + +/*! + * Write binary data encoded to HEX as JSON. + */ +void jsonw_hex(jsonw_t *w, const char *key, const uint8_t *data, size_t len); + +/*! + * Terminate in-progress object or list. + */ +void jsonw_end(jsonw_t *w); diff --git a/src/contrib/libngtcp2/LICENSE b/src/contrib/libngtcp2/LICENSE new file mode 100644 index 0000000..0161703 --- /dev/null +++ b/src/contrib/libngtcp2/LICENSE @@ -0,0 +1 @@ +../licenses/MIT
\ No newline at end of file diff --git a/src/contrib/libngtcp2/ngtcp2/crypto/gnutls.c b/src/contrib/libngtcp2/ngtcp2/crypto/gnutls.c new file mode 100644 index 0000000..b4e08a2 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/crypto/gnutls.c @@ -0,0 +1,674 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 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. + */ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <assert.h> + +#include <ngtcp2/ngtcp2_crypto.h> +#include <ngtcp2/ngtcp2_crypto_gnutls.h> + +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> +#include <string.h> + +#include "shared.h" + +ngtcp2_crypto_aead *ngtcp2_crypto_aead_aes_128_gcm(ngtcp2_crypto_aead *aead) { + return ngtcp2_crypto_aead_init(aead, (void *)GNUTLS_CIPHER_AES_128_GCM); +} + +ngtcp2_crypto_md *ngtcp2_crypto_md_sha256(ngtcp2_crypto_md *md) { + md->native_handle = (void *)GNUTLS_DIG_SHA256; + return md; +} + +ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_initial(ngtcp2_crypto_ctx *ctx) { + ngtcp2_crypto_aead_init(&ctx->aead, (void *)GNUTLS_CIPHER_AES_128_GCM); + ctx->md.native_handle = (void *)GNUTLS_DIG_SHA256; + ctx->hp.native_handle = (void *)GNUTLS_CIPHER_AES_128_CBC; + ctx->max_encryption = 0; + ctx->max_decryption_failure = 0; + return ctx; +} + +ngtcp2_crypto_aead *ngtcp2_crypto_aead_init(ngtcp2_crypto_aead *aead, + void *aead_native_handle) { + aead->native_handle = aead_native_handle; + aead->max_overhead = gnutls_cipher_get_tag_size( + (gnutls_cipher_algorithm_t)(intptr_t)aead_native_handle); + return aead; +} + +ngtcp2_crypto_aead *ngtcp2_crypto_aead_retry(ngtcp2_crypto_aead *aead) { + return ngtcp2_crypto_aead_init(aead, (void *)GNUTLS_CIPHER_AES_128_GCM); +} + +static gnutls_cipher_algorithm_t +crypto_get_hp(gnutls_cipher_algorithm_t cipher) { + switch (cipher) { + case GNUTLS_CIPHER_AES_128_GCM: + case GNUTLS_CIPHER_AES_128_CCM: + return GNUTLS_CIPHER_AES_128_CBC; + case GNUTLS_CIPHER_AES_256_GCM: + case GNUTLS_CIPHER_AES_256_CCM: + return GNUTLS_CIPHER_AES_256_CBC; + case GNUTLS_CIPHER_CHACHA20_POLY1305: + return GNUTLS_CIPHER_CHACHA20_32; + default: + return GNUTLS_CIPHER_UNKNOWN; + } +} + +static uint64_t +crypto_get_aead_max_encryption(gnutls_cipher_algorithm_t cipher) { + switch (cipher) { + case GNUTLS_CIPHER_AES_128_GCM: + case GNUTLS_CIPHER_AES_256_GCM: + return NGTCP2_CRYPTO_MAX_ENCRYPTION_AES_GCM; + case GNUTLS_CIPHER_CHACHA20_POLY1305: + return NGTCP2_CRYPTO_MAX_ENCRYPTION_CHACHA20_POLY1305; + case GNUTLS_CIPHER_AES_128_CCM: + case GNUTLS_CIPHER_AES_256_CCM: + return NGTCP2_CRYPTO_MAX_ENCRYPTION_AES_CCM; + default: + return 0; + } +} + +static uint64_t +crypto_get_aead_max_decryption_failure(gnutls_cipher_algorithm_t cipher) { + switch (cipher) { + case GNUTLS_CIPHER_AES_128_GCM: + case GNUTLS_CIPHER_AES_256_GCM: + return NGTCP2_CRYPTO_MAX_DECRYPTION_FAILURE_AES_GCM; + case GNUTLS_CIPHER_CHACHA20_POLY1305: + return NGTCP2_CRYPTO_MAX_DECRYPTION_FAILURE_CHACHA20_POLY1305; + case GNUTLS_CIPHER_AES_128_CCM: + case GNUTLS_CIPHER_AES_256_CCM: + return NGTCP2_CRYPTO_MAX_DECRYPTION_FAILURE_AES_CCM; + default: + return 0; + } +} + +static int supported_cipher(gnutls_cipher_algorithm_t cipher) { + switch (cipher) { + case GNUTLS_CIPHER_AES_128_GCM: + case GNUTLS_CIPHER_AES_256_GCM: + case GNUTLS_CIPHER_CHACHA20_POLY1305: + case GNUTLS_CIPHER_AES_128_CCM: + case GNUTLS_CIPHER_AES_256_CCM: + return 1; + default: + return 0; + } +} + +ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_tls(ngtcp2_crypto_ctx *ctx, + void *tls_native_handle) { + gnutls_session_t session = tls_native_handle; + gnutls_cipher_algorithm_t cipher; + gnutls_digest_algorithm_t hash; + gnutls_cipher_algorithm_t hp_cipher; + + cipher = gnutls_cipher_get(session); + if (cipher == GNUTLS_CIPHER_UNKNOWN || cipher == GNUTLS_CIPHER_NULL) { + return NULL; + } + + if (!supported_cipher(cipher)) { + return NULL; + } + + hash = gnutls_prf_hash_get(session); + if (hash == GNUTLS_DIG_UNKNOWN || hash == GNUTLS_DIG_NULL) { + return NULL; + } + + hp_cipher = crypto_get_hp(cipher); + if (hp_cipher == GNUTLS_CIPHER_UNKNOWN || hp_cipher == GNUTLS_CIPHER_NULL) { + return NULL; + } + + ngtcp2_crypto_aead_init(&ctx->aead, (void *)cipher); + ctx->md.native_handle = (void *)hash; + ctx->hp.native_handle = (void *)hp_cipher; + ctx->max_encryption = crypto_get_aead_max_encryption(cipher); + ctx->max_decryption_failure = crypto_get_aead_max_decryption_failure(cipher); + + return ctx; +} + +ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_tls_early(ngtcp2_crypto_ctx *ctx, + void *tls_native_handle) { + gnutls_session_t session = tls_native_handle; + gnutls_cipher_algorithm_t cipher; + gnutls_digest_algorithm_t hash; + gnutls_cipher_algorithm_t hp_cipher; + + cipher = gnutls_early_cipher_get(session); + if (cipher == GNUTLS_CIPHER_UNKNOWN || cipher == GNUTLS_CIPHER_NULL) { + return NULL; + } + + if (!supported_cipher(cipher)) { + return NULL; + } + + hash = gnutls_early_prf_hash_get(session); + if (hash == GNUTLS_DIG_UNKNOWN || hash == GNUTLS_DIG_NULL) { + return NULL; + } + + hp_cipher = crypto_get_hp(cipher); + if (hp_cipher == GNUTLS_CIPHER_UNKNOWN || hp_cipher == GNUTLS_CIPHER_NULL) { + return NULL; + } + + ngtcp2_crypto_aead_init(&ctx->aead, (void *)cipher); + ctx->md.native_handle = (void *)hash; + ctx->hp.native_handle = (void *)hp_cipher; + ctx->max_encryption = crypto_get_aead_max_encryption(cipher); + ctx->max_decryption_failure = crypto_get_aead_max_decryption_failure(cipher); + + return ctx; +} + +size_t ngtcp2_crypto_md_hashlen(const ngtcp2_crypto_md *md) { + return gnutls_hash_get_len( + (gnutls_digest_algorithm_t)(intptr_t)md->native_handle); +} + +size_t ngtcp2_crypto_aead_keylen(const ngtcp2_crypto_aead *aead) { + return gnutls_cipher_get_key_size( + (gnutls_cipher_algorithm_t)(intptr_t)aead->native_handle); +} + +size_t ngtcp2_crypto_aead_noncelen(const ngtcp2_crypto_aead *aead) { + return gnutls_cipher_get_iv_size( + (gnutls_cipher_algorithm_t)(intptr_t)aead->native_handle); +} + +int ngtcp2_crypto_aead_ctx_encrypt_init(ngtcp2_crypto_aead_ctx *aead_ctx, + const ngtcp2_crypto_aead *aead, + const uint8_t *key, size_t noncelen) { + 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) { + return -1; + } + + aead_ctx->native_handle = hd; + + return 0; +} + +int ngtcp2_crypto_aead_ctx_decrypt_init(ngtcp2_crypto_aead_ctx *aead_ctx, + const ngtcp2_crypto_aead *aead, + const uint8_t *key, size_t noncelen) { + 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) { + return -1; + } + + aead_ctx->native_handle = hd; + + return 0; +} + +void ngtcp2_crypto_aead_ctx_free(ngtcp2_crypto_aead_ctx *aead_ctx) { + if (aead_ctx->native_handle) { + gnutls_aead_cipher_deinit(aead_ctx->native_handle); + } +} + +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)(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) { + return -1; + } + + cipher_ctx->native_handle = hd; + + return 0; +} + +void ngtcp2_crypto_cipher_ctx_free(ngtcp2_crypto_cipher_ctx *cipher_ctx) { + if (cipher_ctx->native_handle) { + gnutls_cipher_deinit(cipher_ctx->native_handle); + } +} + +int ngtcp2_crypto_hkdf_extract(uint8_t *dest, const ngtcp2_crypto_md *md, + const uint8_t *secret, size_t secretlen, + 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) { + return -1; + } + + return 0; +} + +int ngtcp2_crypto_hkdf_expand(uint8_t *dest, size_t destlen, + const ngtcp2_crypto_md *md, const uint8_t *secret, + size_t secretlen, const uint8_t *info, + 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) { + return -1; + } + + return 0; +} + +int ngtcp2_crypto_hkdf(uint8_t *dest, size_t destlen, + const ngtcp2_crypto_md *md, const uint8_t *secret, + size_t secretlen, const uint8_t *salt, size_t saltlen, + const uint8_t *info, size_t infolen) { + gnutls_mac_algorithm_t prf = + (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) { + return -1; + } + + if (gnutls_hkdf_expand(prf, &_key, &_info, dest, destlen) != 0) { + return -1; + } + + return 0; +} + +int ngtcp2_crypto_encrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *plaintext, size_t plaintextlen, + const uint8_t *nonce, size_t noncelen, + const uint8_t *aad, size_t aadlen) { + gnutls_cipher_algorithm_t cipher = + (gnutls_cipher_algorithm_t)(intptr_t)aead->native_handle; + gnutls_aead_cipher_hd_t hd = aead_ctx->native_handle; + size_t taglen = gnutls_cipher_get_tag_size(cipher); + size_t ciphertextlen = plaintextlen + taglen; + + if (gnutls_aead_cipher_encrypt(hd, nonce, noncelen, aad, aadlen, taglen, + plaintext, plaintextlen, dest, + &ciphertextlen) != 0) { + return -1; + } + + return 0; +} + +int ngtcp2_crypto_decrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *ciphertext, size_t ciphertextlen, + const uint8_t *nonce, size_t noncelen, + const uint8_t *aad, size_t aadlen) { + gnutls_cipher_algorithm_t cipher = + (gnutls_cipher_algorithm_t)(intptr_t)aead->native_handle; + gnutls_aead_cipher_hd_t hd = aead_ctx->native_handle; + size_t taglen = gnutls_cipher_get_tag_size(cipher); + size_t plaintextlen; + + if (taglen > ciphertextlen) { + return -1; + } + + plaintextlen = ciphertextlen - taglen; + + if (gnutls_aead_cipher_decrypt(hd, nonce, noncelen, aad, aadlen, taglen, + ciphertext, ciphertextlen, dest, + &plaintextlen) != 0) { + return -1; + } + + return 0; +} + +int ngtcp2_crypto_hp_mask(uint8_t *dest, const ngtcp2_crypto_cipher *hp, + const ngtcp2_crypto_cipher_ctx *hp_ctx, + const uint8_t *sample) { + gnutls_cipher_algorithm_t cipher = + (gnutls_cipher_algorithm_t)(intptr_t)hp->native_handle; + gnutls_cipher_hd_t hd = hp_ctx->native_handle; + + 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)); + + gnutls_cipher_set_iv(hd, iv, sizeof(iv)); + + if (gnutls_cipher_encrypt2(hd, sample, 16, buf, sizeof(buf)) != 0) { + return -1; + } + + memcpy(dest, buf, 5); + } break; + + case GNUTLS_CIPHER_CHACHA20_32: { + static const uint8_t PLAINTEXT[] = "\x00\x00\x00\x00\x00"; + 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) { + return -1; + } + + memcpy(dest, buf, 5); + } break; + default: + assert(0); + } + + return 0; +} + +ngtcp2_encryption_level +ngtcp2_crypto_gnutls_from_gnutls_record_encryption_level( + gnutls_record_encryption_level_t gtls_level) { + switch (gtls_level) { + case GNUTLS_ENCRYPTION_LEVEL_INITIAL: + return NGTCP2_ENCRYPTION_LEVEL_INITIAL; + case GNUTLS_ENCRYPTION_LEVEL_HANDSHAKE: + return NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE; + case GNUTLS_ENCRYPTION_LEVEL_APPLICATION: + return NGTCP2_ENCRYPTION_LEVEL_1RTT; + case GNUTLS_ENCRYPTION_LEVEL_EARLY: + return NGTCP2_ENCRYPTION_LEVEL_0RTT; + default: + assert(0); + abort(); + } +} + +gnutls_record_encryption_level_t +ngtcp2_crypto_gnutls_from_ngtcp2_encryption_level( + ngtcp2_encryption_level encryption_level) { + switch (encryption_level) { + case NGTCP2_ENCRYPTION_LEVEL_INITIAL: + return GNUTLS_ENCRYPTION_LEVEL_INITIAL; + case NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE: + return GNUTLS_ENCRYPTION_LEVEL_HANDSHAKE; + case NGTCP2_ENCRYPTION_LEVEL_1RTT: + return GNUTLS_ENCRYPTION_LEVEL_APPLICATION; + case NGTCP2_ENCRYPTION_LEVEL_0RTT: + return GNUTLS_ENCRYPTION_LEVEL_EARLY; + default: + assert(0); + abort(); + } +} + +int ngtcp2_crypto_read_write_crypto_data( + ngtcp2_conn *conn, ngtcp2_encryption_level encryption_level, + const uint8_t *data, size_t datalen) { + gnutls_session_t session = ngtcp2_conn_get_tls_native_handle(conn); + int rv; + + if (datalen > 0) { + rv = gnutls_handshake_write( + session, + ngtcp2_crypto_gnutls_from_ngtcp2_encryption_level(encryption_level), + data, datalen); + if (rv != 0) { + if (!gnutls_error_is_fatal(rv)) { + return 0; + } + gnutls_alert_send_appropriate(session, rv); + return -1; + } + } + + if (!ngtcp2_conn_get_handshake_completed(conn)) { + rv = gnutls_handshake(session); + if (rv < 0) { + if (!gnutls_error_is_fatal(rv)) { + return 0; + } + gnutls_alert_send_appropriate(session, rv); + return -1; + } + + ngtcp2_conn_tls_handshake_completed(conn); + } + + return 0; +} + +int ngtcp2_crypto_set_remote_transport_params(ngtcp2_conn *conn, void *tls) { + (void)conn; + (void)tls; + /* Nothing to do; GnuTLS applications are supposed to register the + quic_transport_parameters extension with + gnutls_session_ext_register. */ + return 0; +} + +int ngtcp2_crypto_set_local_transport_params(void *tls, const uint8_t *buf, + size_t len) { + (void)tls; + (void)buf; + (void)len; + /* Nothing to do; GnuTLS applications are supposed to register the + quic_transport_parameters extension with + gnutls_session_ext_register. */ + return 0; +} + +int ngtcp2_crypto_get_path_challenge_data_cb(ngtcp2_conn *conn, uint8_t *data, + void *user_data) { + (void)conn; + (void)user_data; + + if (gnutls_rnd(GNUTLS_RND_RANDOM, 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; + } + + return 0; +} + +static int secret_func(gnutls_session_t session, + gnutls_record_encryption_level_t gtls_level, + const void *rx_secret, const void *tx_secret, + size_t secretlen) { + ngtcp2_crypto_conn_ref *conn_ref = gnutls_session_get_ptr(session); + ngtcp2_conn *conn = conn_ref->get_conn(conn_ref); + ngtcp2_encryption_level level = + ngtcp2_crypto_gnutls_from_gnutls_record_encryption_level(gtls_level); + + if (rx_secret && + ngtcp2_crypto_derive_and_install_rx_key(conn, NULL, NULL, NULL, level, + rx_secret, secretlen) != 0) { + return -1; + } + + if (tx_secret && + ngtcp2_crypto_derive_and_install_tx_key(conn, NULL, NULL, NULL, level, + tx_secret, secretlen) != 0) { + return -1; + } + + return 0; +} + +static int read_func(gnutls_session_t session, + gnutls_record_encryption_level_t gtls_level, + gnutls_handshake_description_t htype, const void *data, + size_t datalen) { + ngtcp2_crypto_conn_ref *conn_ref = gnutls_session_get_ptr(session); + ngtcp2_conn *conn = conn_ref->get_conn(conn_ref); + ngtcp2_encryption_level level = + ngtcp2_crypto_gnutls_from_gnutls_record_encryption_level(gtls_level); + int rv; + + if (htype == GNUTLS_HANDSHAKE_CHANGE_CIPHER_SPEC) { + return 0; + } + + rv = ngtcp2_conn_submit_crypto_data(conn, level, data, datalen); + if (rv != 0) { + ngtcp2_conn_set_tls_error(conn, rv); + return -1; + } + + return 0; +} + +static int alert_read_func(gnutls_session_t session, + gnutls_record_encryption_level_t gtls_level, + gnutls_alert_level_t alert_level, + gnutls_alert_description_t alert_desc) { + ngtcp2_crypto_conn_ref *conn_ref = gnutls_session_get_ptr(session); + ngtcp2_conn *conn = conn_ref->get_conn(conn_ref); + (void)gtls_level; + (void)alert_level; + + ngtcp2_conn_set_tls_alert(conn, (uint8_t)alert_desc); + + return 0; +} + +static int tp_recv_func(gnutls_session_t session, const uint8_t *data, + size_t datalen) { + ngtcp2_crypto_conn_ref *conn_ref = gnutls_session_get_ptr(session); + ngtcp2_conn *conn = conn_ref->get_conn(conn_ref); + int rv; + + rv = ngtcp2_conn_decode_and_set_remote_transport_params(conn, data, datalen); + if (rv != 0) { + ngtcp2_conn_set_tls_error(conn, rv); + return -1; + } + + return 0; +} + +static int tp_send_func(gnutls_session_t session, gnutls_buffer_t extdata) { + ngtcp2_crypto_conn_ref *conn_ref = gnutls_session_get_ptr(session); + ngtcp2_conn *conn = conn_ref->get_conn(conn_ref); + uint8_t buf[256]; + ngtcp2_ssize nwrite; + int rv; + + nwrite = ngtcp2_conn_encode_local_transport_params(conn, buf, sizeof(buf)); + if (nwrite < 0) { + return -1; + } + + rv = gnutls_buffer_append_data(extdata, buf, (size_t)nwrite); + if (rv != 0) { + return -1; + } + + return 0; +} + +static int crypto_gnutls_configure_session(gnutls_session_t session) { + int rv; + + gnutls_handshake_set_secret_function(session, secret_func); + gnutls_handshake_set_read_function(session, read_func); + gnutls_alert_set_read_function(session, alert_read_func); + + rv = gnutls_session_ext_register( + session, "QUIC Transport Parameters", + NGTCP2_TLSEXT_QUIC_TRANSPORT_PARAMETERS_V1, GNUTLS_EXT_TLS, tp_recv_func, + tp_send_func, NULL, NULL, NULL, + GNUTLS_EXT_FLAG_TLS | GNUTLS_EXT_FLAG_CLIENT_HELLO | GNUTLS_EXT_FLAG_EE); + if (rv != 0) { + return -1; + } + + return 0; +} + +int ngtcp2_crypto_gnutls_configure_server_session(gnutls_session_t session) { + return crypto_gnutls_configure_session(session); +} + +int ngtcp2_crypto_gnutls_configure_client_session(gnutls_session_t session) { + return crypto_gnutls_configure_session(session); +} diff --git a/src/contrib/libngtcp2/ngtcp2/crypto/shared.c b/src/contrib/libngtcp2/ngtcp2/crypto/shared.c new file mode 100644 index 0000000..162094a --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/crypto/shared.c @@ -0,0 +1,1448 @@ +/* + * 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. + */ +#include "shared.h" + +#ifdef WIN32 +# include <winsock2.h> +# include <ws2tcpip.h> +#else +# include <netinet/in.h> +#endif + +#include <string.h> +#include <assert.h> + +#include "ngtcp2_macro.h" +#include "ngtcp2_net.h" + +ngtcp2_crypto_md *ngtcp2_crypto_md_init(ngtcp2_crypto_md *md, + void *md_native_handle) { + md->native_handle = md_native_handle; + return md; +} + +int ngtcp2_crypto_hkdf_expand_label(uint8_t *dest, size_t destlen, + const ngtcp2_crypto_md *md, + const uint8_t *secret, size_t secretlen, + const uint8_t *label, size_t labellen) { + static const uint8_t LABEL[] = "tls13 "; + uint8_t info[256]; + uint8_t *p = info; + + *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; + memcpy(p, label, labellen); + p += labellen; + *p++ = 0; + + return ngtcp2_crypto_hkdf_expand(dest, destlen, md, secret, secretlen, info, + (size_t)(p - info)); +} + +int ngtcp2_crypto_derive_initial_secrets(uint8_t *rx_secret, uint8_t *tx_secret, + uint8_t *initial_secret, + uint32_t version, + const ngtcp2_cid *client_dcid, + ngtcp2_crypto_side side) { + static const uint8_t CLABEL[] = "client in"; + static const uint8_t SLABEL[] = "server in"; + uint8_t initial_secret_buf[NGTCP2_CRYPTO_INITIAL_SECRETLEN]; + uint8_t *client_secret; + uint8_t *server_secret; + ngtcp2_crypto_ctx ctx; + const uint8_t *salt; + size_t saltlen; + + if (!initial_secret) { + initial_secret = initial_secret_buf; + } + + ngtcp2_crypto_ctx_initial(&ctx); + + switch (version) { + case NGTCP2_PROTO_VER_V1: + default: + salt = (const uint8_t *)NGTCP2_INITIAL_SALT_V1; + saltlen = sizeof(NGTCP2_INITIAL_SALT_V1) - 1; + break; + case NGTCP2_PROTO_VER_V2: + salt = (const uint8_t *)NGTCP2_INITIAL_SALT_V2; + saltlen = sizeof(NGTCP2_INITIAL_SALT_V2) - 1; + break; + } + + if (ngtcp2_crypto_hkdf_extract(initial_secret, &ctx.md, client_dcid->data, + client_dcid->datalen, salt, saltlen) != 0) { + return -1; + } + + if (side == NGTCP2_CRYPTO_SIDE_SERVER) { + client_secret = rx_secret; + server_secret = tx_secret; + } else { + client_secret = tx_secret; + server_secret = rx_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_hkdf_expand_label( + server_secret, NGTCP2_CRYPTO_INITIAL_SECRETLEN, &ctx.md, + initial_secret, NGTCP2_CRYPTO_INITIAL_SECRETLEN, SLABEL, + sizeof(SLABEL) - 1) != 0) { + return -1; + } + + return 0; +} + +size_t ngtcp2_crypto_packet_protection_ivlen(const ngtcp2_crypto_aead *aead) { + size_t noncelen = ngtcp2_crypto_aead_noncelen(aead); + return ngtcp2_max(8, noncelen); +} + +int ngtcp2_crypto_derive_packet_protection_key( + uint8_t *key, uint8_t *iv, uint8_t *hp_key, uint32_t version, + const ngtcp2_crypto_aead *aead, const ngtcp2_crypto_md *md, + const uint8_t *secret, size_t secretlen) { + static const uint8_t KEY_LABEL_V1[] = "quic key"; + static const uint8_t IV_LABEL_V1[] = "quic iv"; + static const uint8_t HP_KEY_LABEL_V1[] = "quic hp"; + static const uint8_t KEY_LABEL_V2[] = "quicv2 key"; + static const uint8_t IV_LABEL_V2[] = "quicv2 iv"; + static const uint8_t HP_KEY_LABEL_V2[] = "quicv2 hp"; + size_t keylen = ngtcp2_crypto_aead_keylen(aead); + size_t ivlen = ngtcp2_crypto_packet_protection_ivlen(aead); + const uint8_t *key_label; + size_t key_labellen; + const uint8_t *iv_label; + size_t iv_labellen; + const uint8_t *hp_key_label; + size_t hp_key_labellen; + + switch (version) { + case NGTCP2_PROTO_VER_V2: + key_label = KEY_LABEL_V2; + key_labellen = sizeof(KEY_LABEL_V2) - 1; + iv_label = IV_LABEL_V2; + iv_labellen = sizeof(IV_LABEL_V2) - 1; + hp_key_label = HP_KEY_LABEL_V2; + hp_key_labellen = sizeof(HP_KEY_LABEL_V2) - 1; + break; + default: + key_label = KEY_LABEL_V1; + key_labellen = sizeof(KEY_LABEL_V1) - 1; + iv_label = IV_LABEL_V1; + iv_labellen = sizeof(IV_LABEL_V1) - 1; + hp_key_label = HP_KEY_LABEL_V1; + hp_key_labellen = sizeof(HP_KEY_LABEL_V1) - 1; + } + + if (ngtcp2_crypto_hkdf_expand_label(key, keylen, md, secret, secretlen, + key_label, key_labellen) != 0) { + return -1; + } + + if (ngtcp2_crypto_hkdf_expand_label(iv, ivlen, md, secret, secretlen, + iv_label, iv_labellen) != 0) { + return -1; + } + + if (hp_key != NULL && + ngtcp2_crypto_hkdf_expand_label(hp_key, keylen, md, secret, secretlen, + hp_key_label, hp_key_labellen) != 0) { + return -1; + } + + return 0; +} + +int ngtcp2_crypto_update_traffic_secret(uint8_t *dest, uint32_t version, + const ngtcp2_crypto_md *md, + const uint8_t *secret, + size_t secretlen) { + static const uint8_t LABEL[] = "quic ku"; + static const uint8_t LABEL_V2[] = "quicv2 ku"; + const uint8_t *label; + size_t labellen; + + switch (version) { + case NGTCP2_PROTO_VER_V2: + label = LABEL_V2; + labellen = sizeof(LABEL_V2) - 1; + break; + default: + label = LABEL; + labellen = sizeof(LABEL) - 1; + } + + if (ngtcp2_crypto_hkdf_expand_label(dest, secretlen, md, secret, secretlen, + label, labellen) != 0) { + return -1; + } + + return 0; +} + +int ngtcp2_crypto_derive_and_install_rx_key(ngtcp2_conn *conn, uint8_t *key, + uint8_t *iv, uint8_t *hp_key, + ngtcp2_encryption_level level, + const uint8_t *secret, + size_t secretlen) { + const ngtcp2_crypto_ctx *ctx; + const ngtcp2_crypto_aead *aead; + const ngtcp2_crypto_md *md; + const ngtcp2_crypto_cipher *hp; + ngtcp2_crypto_aead_ctx aead_ctx = {0}; + ngtcp2_crypto_cipher_ctx hp_ctx = {0}; + void *tls = ngtcp2_conn_get_tls_native_handle(conn); + uint8_t keybuf[64], ivbuf[64], hp_keybuf[64]; + size_t ivlen; + int rv; + ngtcp2_crypto_ctx cctx; + uint32_t version; + + if (level == NGTCP2_ENCRYPTION_LEVEL_0RTT && !ngtcp2_conn_is_server(conn)) { + return 0; + } + + if (!key) { + key = keybuf; + } + if (!iv) { + iv = ivbuf; + } + if (!hp_key) { + hp_key = hp_keybuf; + } + + switch (level) { + case NGTCP2_ENCRYPTION_LEVEL_0RTT: + if (ngtcp2_crypto_ctx_tls_early(&cctx, tls) == NULL) { + return -1; + } + + ngtcp2_conn_set_0rtt_crypto_ctx(conn, &cctx); + ctx = ngtcp2_conn_get_0rtt_crypto_ctx(conn); + version = ngtcp2_conn_get_client_chosen_version(conn); + break; + case NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE: + if (ngtcp2_conn_is_server(conn) && + !ngtcp2_conn_get_negotiated_version(conn)) { + rv = ngtcp2_crypto_set_remote_transport_params(conn, tls); + if (rv != 0) { + return -1; + } + } + /* fall through */ + case NGTCP2_ENCRYPTION_LEVEL_1RTT: + ctx = ngtcp2_conn_get_crypto_ctx(conn); + version = ngtcp2_conn_get_negotiated_version(conn); + + if (!ctx->aead.native_handle) { + if (ngtcp2_crypto_ctx_tls(&cctx, tls) == NULL) { + return -1; + } + + ngtcp2_conn_set_crypto_ctx(conn, &cctx); + ctx = ngtcp2_conn_get_crypto_ctx(conn); + } + break; + default: + return -1; + } + + aead = &ctx->aead; + md = &ctx->md; + hp = &ctx->hp; + ivlen = ngtcp2_crypto_packet_protection_ivlen(aead); + + if (ngtcp2_crypto_derive_packet_protection_key(key, iv, hp_key, version, aead, + md, secret, secretlen) != 0) { + return -1; + } + + if (ngtcp2_crypto_aead_ctx_decrypt_init(&aead_ctx, aead, key, ivlen) != 0) { + goto fail; + } + + if (ngtcp2_crypto_cipher_ctx_encrypt_init(&hp_ctx, hp, hp_key) != 0) { + goto fail; + } + + switch (level) { + case NGTCP2_ENCRYPTION_LEVEL_0RTT: + rv = ngtcp2_conn_install_0rtt_key(conn, &aead_ctx, iv, ivlen, &hp_ctx); + if (rv != 0) { + goto fail; + } + break; + case NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE: + rv = ngtcp2_conn_install_rx_handshake_key(conn, &aead_ctx, iv, ivlen, + &hp_ctx); + if (rv != 0) { + goto fail; + } + break; + case NGTCP2_ENCRYPTION_LEVEL_1RTT: + if (!ngtcp2_conn_is_server(conn)) { + rv = ngtcp2_crypto_set_remote_transport_params(conn, tls); + if (rv != 0) { + goto fail; + } + } + + rv = ngtcp2_conn_install_rx_key(conn, secret, secretlen, &aead_ctx, iv, + ivlen, &hp_ctx); + if (rv != 0) { + goto fail; + } + + break; + default: + goto fail; + } + + return 0; + +fail: + ngtcp2_crypto_cipher_ctx_free(&hp_ctx); + ngtcp2_crypto_aead_ctx_free(&aead_ctx); + + return -1; +} + +/* + * crypto_set_local_transport_params gets local QUIC transport + * parameters from |conn| and sets it to |tls|. + * + * This function returns 0 if it succeeds, or -1. + */ +static int crypto_set_local_transport_params(ngtcp2_conn *conn, void *tls) { + ngtcp2_ssize nwrite; + uint8_t buf[256]; + + nwrite = ngtcp2_conn_encode_local_transport_params(conn, buf, sizeof(buf)); + if (nwrite < 0) { + return -1; + } + + if (ngtcp2_crypto_set_local_transport_params(tls, buf, (size_t)nwrite) != 0) { + return -1; + } + + return 0; +} + +int ngtcp2_crypto_derive_and_install_tx_key(ngtcp2_conn *conn, uint8_t *key, + uint8_t *iv, uint8_t *hp_key, + ngtcp2_encryption_level level, + const uint8_t *secret, + size_t secretlen) { + const ngtcp2_crypto_ctx *ctx; + const ngtcp2_crypto_aead *aead; + const ngtcp2_crypto_md *md; + const ngtcp2_crypto_cipher *hp; + ngtcp2_crypto_aead_ctx aead_ctx = {0}; + ngtcp2_crypto_cipher_ctx hp_ctx = {0}; + void *tls = ngtcp2_conn_get_tls_native_handle(conn); + uint8_t keybuf[64], ivbuf[64], hp_keybuf[64]; + size_t ivlen; + int rv; + ngtcp2_crypto_ctx cctx; + uint32_t version; + + if (level == NGTCP2_ENCRYPTION_LEVEL_0RTT && ngtcp2_conn_is_server(conn)) { + return 0; + } + + if (!key) { + key = keybuf; + } + if (!iv) { + iv = ivbuf; + } + if (!hp_key) { + hp_key = hp_keybuf; + } + + switch (level) { + case NGTCP2_ENCRYPTION_LEVEL_0RTT: + if (ngtcp2_crypto_ctx_tls_early(&cctx, tls) == NULL) { + return -1; + } + + ngtcp2_conn_set_0rtt_crypto_ctx(conn, &cctx); + ctx = ngtcp2_conn_get_0rtt_crypto_ctx(conn); + version = ngtcp2_conn_get_client_chosen_version(conn); + break; + case NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE: + if (ngtcp2_conn_is_server(conn) && + !ngtcp2_conn_get_negotiated_version(conn)) { + rv = ngtcp2_crypto_set_remote_transport_params(conn, tls); + if (rv != 0) { + return -1; + } + } + /* fall through */ + case NGTCP2_ENCRYPTION_LEVEL_1RTT: + ctx = ngtcp2_conn_get_crypto_ctx(conn); + version = ngtcp2_conn_get_negotiated_version(conn); + + if (!ctx->aead.native_handle) { + if (ngtcp2_crypto_ctx_tls(&cctx, tls) == NULL) { + return -1; + } + + ngtcp2_conn_set_crypto_ctx(conn, &cctx); + ctx = ngtcp2_conn_get_crypto_ctx(conn); + } + break; + default: + return -1; + } + + aead = &ctx->aead; + md = &ctx->md; + hp = &ctx->hp; + ivlen = ngtcp2_crypto_packet_protection_ivlen(aead); + + if (ngtcp2_crypto_derive_packet_protection_key(key, iv, hp_key, version, aead, + md, secret, secretlen) != 0) { + return -1; + } + + if (ngtcp2_crypto_aead_ctx_encrypt_init(&aead_ctx, aead, key, ivlen) != 0) { + goto fail; + } + + if (ngtcp2_crypto_cipher_ctx_encrypt_init(&hp_ctx, hp, hp_key) != 0) { + goto fail; + } + + switch (level) { + case NGTCP2_ENCRYPTION_LEVEL_0RTT: + rv = ngtcp2_conn_install_0rtt_key(conn, &aead_ctx, iv, ivlen, &hp_ctx); + if (rv != 0) { + goto fail; + } + break; + case NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE: + rv = ngtcp2_conn_install_tx_handshake_key(conn, &aead_ctx, iv, ivlen, + &hp_ctx); + if (rv != 0) { + goto fail; + } + + if (ngtcp2_conn_is_server(conn) && + crypto_set_local_transport_params(conn, tls) != 0) { + goto fail; + } + + break; + case NGTCP2_ENCRYPTION_LEVEL_1RTT: + rv = ngtcp2_conn_install_tx_key(conn, secret, secretlen, &aead_ctx, iv, + ivlen, &hp_ctx); + if (rv != 0) { + goto fail; + } + + break; + default: + goto fail; + } + + return 0; + +fail: + ngtcp2_crypto_cipher_ctx_free(&hp_ctx); + ngtcp2_crypto_aead_ctx_free(&aead_ctx); + + return -1; +} + +int ngtcp2_crypto_derive_and_install_initial_key( + ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret, + uint8_t *initial_secret, uint8_t *rx_key, uint8_t *rx_iv, + uint8_t *rx_hp_key, uint8_t *tx_key, uint8_t *tx_iv, uint8_t *tx_hp_key, + uint32_t version, const ngtcp2_cid *client_dcid) { + uint8_t rx_secretbuf[NGTCP2_CRYPTO_INITIAL_SECRETLEN]; + uint8_t tx_secretbuf[NGTCP2_CRYPTO_INITIAL_SECRETLEN]; + uint8_t initial_secretbuf[NGTCP2_CRYPTO_INITIAL_SECRETLEN]; + uint8_t rx_keybuf[NGTCP2_CRYPTO_INITIAL_KEYLEN]; + uint8_t rx_ivbuf[NGTCP2_CRYPTO_INITIAL_IVLEN]; + uint8_t rx_hp_keybuf[NGTCP2_CRYPTO_INITIAL_KEYLEN]; + uint8_t tx_keybuf[NGTCP2_CRYPTO_INITIAL_KEYLEN]; + uint8_t tx_ivbuf[NGTCP2_CRYPTO_INITIAL_IVLEN]; + uint8_t tx_hp_keybuf[NGTCP2_CRYPTO_INITIAL_KEYLEN]; + ngtcp2_crypto_ctx ctx; + ngtcp2_crypto_aead retry_aead; + ngtcp2_crypto_aead_ctx rx_aead_ctx = {0}; + ngtcp2_crypto_cipher_ctx rx_hp_ctx = {0}; + ngtcp2_crypto_aead_ctx tx_aead_ctx = {0}; + ngtcp2_crypto_cipher_ctx tx_hp_ctx = {0}; + ngtcp2_crypto_aead_ctx retry_aead_ctx = {0}; + int rv; + int server = ngtcp2_conn_is_server(conn); + const uint8_t *retry_key; + size_t retry_noncelen; + + ngtcp2_crypto_ctx_initial(&ctx); + + if (!rx_secret) { + rx_secret = rx_secretbuf; + } + if (!tx_secret) { + tx_secret = tx_secretbuf; + } + if (!initial_secret) { + initial_secret = initial_secretbuf; + } + + if (!rx_key) { + rx_key = rx_keybuf; + } + if (!rx_iv) { + rx_iv = rx_ivbuf; + } + if (!rx_hp_key) { + rx_hp_key = rx_hp_keybuf; + } + if (!tx_key) { + tx_key = tx_keybuf; + } + if (!tx_iv) { + tx_iv = tx_ivbuf; + } + if (!tx_hp_key) { + tx_hp_key = tx_hp_keybuf; + } + + ngtcp2_conn_set_initial_crypto_ctx(conn, &ctx); + + if (ngtcp2_crypto_derive_initial_secrets( + rx_secret, tx_secret, initial_secret, version, client_dcid, + server ? NGTCP2_CRYPTO_SIDE_SERVER : NGTCP2_CRYPTO_SIDE_CLIENT) != + 0) { + return -1; + } + + if (ngtcp2_crypto_derive_packet_protection_key( + rx_key, rx_iv, rx_hp_key, version, &ctx.aead, &ctx.md, rx_secret, + NGTCP2_CRYPTO_INITIAL_SECRETLEN) != 0) { + return -1; + } + + if (ngtcp2_crypto_derive_packet_protection_key( + tx_key, tx_iv, tx_hp_key, version, &ctx.aead, &ctx.md, tx_secret, + NGTCP2_CRYPTO_INITIAL_SECRETLEN) != 0) { + return -1; + } + + if (ngtcp2_crypto_aead_ctx_decrypt_init(&rx_aead_ctx, &ctx.aead, rx_key, + NGTCP2_CRYPTO_INITIAL_IVLEN) != 0) { + goto fail; + } + + if (ngtcp2_crypto_cipher_ctx_encrypt_init(&rx_hp_ctx, &ctx.hp, rx_hp_key) != + 0) { + goto fail; + } + + if (ngtcp2_crypto_aead_ctx_encrypt_init(&tx_aead_ctx, &ctx.aead, tx_key, + NGTCP2_CRYPTO_INITIAL_IVLEN) != 0) { + goto fail; + } + + if (ngtcp2_crypto_cipher_ctx_encrypt_init(&tx_hp_ctx, &ctx.hp, tx_hp_key) != + 0) { + goto fail; + } + + if (!server && !ngtcp2_conn_after_retry(conn)) { + ngtcp2_crypto_aead_retry(&retry_aead); + + switch (version) { + case NGTCP2_PROTO_VER_V1: + default: + retry_key = (const uint8_t *)NGTCP2_RETRY_KEY_V1; + retry_noncelen = sizeof(NGTCP2_RETRY_NONCE_V1) - 1; + break; + case NGTCP2_PROTO_VER_V2: + retry_key = (const uint8_t *)NGTCP2_RETRY_KEY_V2; + retry_noncelen = sizeof(NGTCP2_RETRY_NONCE_V2) - 1; + break; + } + + if (ngtcp2_crypto_aead_ctx_encrypt_init(&retry_aead_ctx, &retry_aead, + retry_key, retry_noncelen) != 0) { + goto fail; + } + } + + rv = ngtcp2_conn_install_initial_key(conn, &rx_aead_ctx, rx_iv, &rx_hp_ctx, + &tx_aead_ctx, tx_iv, &tx_hp_ctx, + NGTCP2_CRYPTO_INITIAL_IVLEN); + if (rv != 0) { + goto fail; + } + + if (retry_aead_ctx.native_handle) { + ngtcp2_conn_set_retry_aead(conn, &retry_aead, &retry_aead_ctx); + } + + return 0; + +fail: + ngtcp2_crypto_aead_ctx_free(&retry_aead_ctx); + ngtcp2_crypto_cipher_ctx_free(&tx_hp_ctx); + ngtcp2_crypto_aead_ctx_free(&tx_aead_ctx); + ngtcp2_crypto_cipher_ctx_free(&rx_hp_ctx); + ngtcp2_crypto_aead_ctx_free(&rx_aead_ctx); + + return -1; +} + +int ngtcp2_crypto_derive_and_install_vneg_initial_key( + ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret, + uint8_t *initial_secret, uint8_t *rx_key, uint8_t *rx_iv, + uint8_t *rx_hp_key, uint8_t *tx_key, uint8_t *tx_iv, uint8_t *tx_hp_key, + uint32_t version, const ngtcp2_cid *client_dcid) { + uint8_t rx_secretbuf[NGTCP2_CRYPTO_INITIAL_SECRETLEN]; + uint8_t tx_secretbuf[NGTCP2_CRYPTO_INITIAL_SECRETLEN]; + uint8_t initial_secretbuf[NGTCP2_CRYPTO_INITIAL_SECRETLEN]; + uint8_t rx_keybuf[NGTCP2_CRYPTO_INITIAL_KEYLEN]; + uint8_t rx_ivbuf[NGTCP2_CRYPTO_INITIAL_IVLEN]; + uint8_t rx_hp_keybuf[NGTCP2_CRYPTO_INITIAL_KEYLEN]; + uint8_t tx_keybuf[NGTCP2_CRYPTO_INITIAL_KEYLEN]; + uint8_t tx_ivbuf[NGTCP2_CRYPTO_INITIAL_IVLEN]; + uint8_t tx_hp_keybuf[NGTCP2_CRYPTO_INITIAL_KEYLEN]; + const ngtcp2_crypto_ctx *ctx = ngtcp2_conn_get_initial_crypto_ctx(conn); + ngtcp2_crypto_aead_ctx rx_aead_ctx = {0}; + ngtcp2_crypto_cipher_ctx rx_hp_ctx = {0}; + ngtcp2_crypto_aead_ctx tx_aead_ctx = {0}; + ngtcp2_crypto_cipher_ctx tx_hp_ctx = {0}; + int rv; + int server = ngtcp2_conn_is_server(conn); + + if (!rx_secret) { + rx_secret = rx_secretbuf; + } + if (!tx_secret) { + tx_secret = tx_secretbuf; + } + if (!initial_secret) { + initial_secret = initial_secretbuf; + } + + if (!rx_key) { + rx_key = rx_keybuf; + } + if (!rx_iv) { + rx_iv = rx_ivbuf; + } + if (!rx_hp_key) { + rx_hp_key = rx_hp_keybuf; + } + if (!tx_key) { + tx_key = tx_keybuf; + } + if (!tx_iv) { + tx_iv = tx_ivbuf; + } + if (!tx_hp_key) { + tx_hp_key = tx_hp_keybuf; + } + + if (ngtcp2_crypto_derive_initial_secrets( + rx_secret, tx_secret, initial_secret, version, client_dcid, + server ? NGTCP2_CRYPTO_SIDE_SERVER : NGTCP2_CRYPTO_SIDE_CLIENT) != + 0) { + return -1; + } + + if (ngtcp2_crypto_derive_packet_protection_key( + rx_key, rx_iv, rx_hp_key, version, &ctx->aead, &ctx->md, rx_secret, + NGTCP2_CRYPTO_INITIAL_SECRETLEN) != 0) { + return -1; + } + + if (ngtcp2_crypto_derive_packet_protection_key( + tx_key, tx_iv, tx_hp_key, version, &ctx->aead, &ctx->md, tx_secret, + NGTCP2_CRYPTO_INITIAL_SECRETLEN) != 0) { + return -1; + } + + if (ngtcp2_crypto_aead_ctx_decrypt_init(&rx_aead_ctx, &ctx->aead, rx_key, + NGTCP2_CRYPTO_INITIAL_IVLEN) != 0) { + goto fail; + } + + if (ngtcp2_crypto_cipher_ctx_encrypt_init(&rx_hp_ctx, &ctx->hp, rx_hp_key) != + 0) { + goto fail; + } + + if (ngtcp2_crypto_aead_ctx_encrypt_init(&tx_aead_ctx, &ctx->aead, tx_key, + NGTCP2_CRYPTO_INITIAL_IVLEN) != 0) { + goto fail; + } + + if (ngtcp2_crypto_cipher_ctx_encrypt_init(&tx_hp_ctx, &ctx->hp, tx_hp_key) != + 0) { + goto fail; + } + + rv = ngtcp2_conn_install_vneg_initial_key( + conn, version, &rx_aead_ctx, rx_iv, &rx_hp_ctx, &tx_aead_ctx, tx_iv, + &tx_hp_ctx, NGTCP2_CRYPTO_INITIAL_IVLEN); + if (rv != 0) { + goto fail; + } + + return 0; + +fail: + ngtcp2_crypto_cipher_ctx_free(&tx_hp_ctx); + ngtcp2_crypto_aead_ctx_free(&tx_aead_ctx); + ngtcp2_crypto_cipher_ctx_free(&rx_hp_ctx); + ngtcp2_crypto_aead_ctx_free(&rx_aead_ctx); + + return -1; +} + +int ngtcp2_crypto_update_key( + ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret, + ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_key, uint8_t *rx_iv, + ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_key, uint8_t *tx_iv, + const uint8_t *current_rx_secret, const uint8_t *current_tx_secret, + size_t secretlen) { + const ngtcp2_crypto_ctx *ctx = ngtcp2_conn_get_crypto_ctx(conn); + const ngtcp2_crypto_aead *aead = &ctx->aead; + const ngtcp2_crypto_md *md = &ctx->md; + size_t ivlen = ngtcp2_crypto_packet_protection_ivlen(aead); + uint32_t version = ngtcp2_conn_get_negotiated_version(conn); + + if (ngtcp2_crypto_update_traffic_secret(rx_secret, version, md, + current_rx_secret, secretlen) != 0) { + return -1; + } + + if (ngtcp2_crypto_derive_packet_protection_key( + rx_key, rx_iv, NULL, version, aead, md, rx_secret, secretlen) != 0) { + return -1; + } + + if (ngtcp2_crypto_update_traffic_secret(tx_secret, version, md, + current_tx_secret, secretlen) != 0) { + return -1; + } + + if (ngtcp2_crypto_derive_packet_protection_key( + tx_key, tx_iv, NULL, version, aead, md, tx_secret, secretlen) != 0) { + return -1; + } + + if (ngtcp2_crypto_aead_ctx_decrypt_init(rx_aead_ctx, aead, rx_key, ivlen) != + 0) { + return -1; + } + + if (ngtcp2_crypto_aead_ctx_encrypt_init(tx_aead_ctx, aead, tx_key, ivlen) != + 0) { + ngtcp2_crypto_aead_ctx_free(rx_aead_ctx); + rx_aead_ctx->native_handle = NULL; + return -1; + } + + return 0; +} + +int ngtcp2_crypto_encrypt_cb(uint8_t *dest, const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *plaintext, size_t plaintextlen, + const uint8_t *nonce, size_t noncelen, + const uint8_t *aad, size_t aadlen) { + if (ngtcp2_crypto_encrypt(dest, aead, aead_ctx, plaintext, plaintextlen, + nonce, noncelen, aad, aadlen) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + return 0; +} + +int ngtcp2_crypto_decrypt_cb(uint8_t *dest, const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *ciphertext, size_t ciphertextlen, + const uint8_t *nonce, size_t noncelen, + const uint8_t *aad, size_t aadlen) { + if (ngtcp2_crypto_decrypt(dest, aead, aead_ctx, ciphertext, ciphertextlen, + nonce, noncelen, aad, aadlen) != 0) { + return NGTCP2_ERR_DECRYPT; + } + return 0; +} + +int ngtcp2_crypto_hp_mask_cb(uint8_t *dest, const ngtcp2_crypto_cipher *hp, + const ngtcp2_crypto_cipher_ctx *hp_ctx, + const uint8_t *sample) { + if (ngtcp2_crypto_hp_mask(dest, hp, hp_ctx, sample) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + return 0; +} + +int ngtcp2_crypto_update_key_cb( + ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret, + ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv, + ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv, + const uint8_t *current_rx_secret, const uint8_t *current_tx_secret, + size_t secretlen, void *user_data) { + uint8_t rx_key[64]; + uint8_t tx_key[64]; + (void)conn; + (void)user_data; + + if (ngtcp2_crypto_update_key(conn, rx_secret, tx_secret, rx_aead_ctx, rx_key, + rx_iv, tx_aead_ctx, tx_key, tx_iv, + current_rx_secret, current_tx_secret, + secretlen) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + return 0; +} + +int ngtcp2_crypto_generate_stateless_reset_token(uint8_t *token, + const uint8_t *secret, + size_t secretlen, + const ngtcp2_cid *cid) { + static const uint8_t info[] = "stateless_reset"; + ngtcp2_crypto_md md; + + 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) { + return -1; + } + + return 0; +} + +static int crypto_derive_token_key(uint8_t *key, size_t keylen, uint8_t *iv, + size_t ivlen, const ngtcp2_crypto_md *md, + const uint8_t *secret, size_t secretlen, + const uint8_t *salt, size_t saltlen, + const uint8_t *info_prefix, + size_t info_prefixlen) { + static const uint8_t key_info_suffix[] = " key"; + static const uint8_t iv_info_suffix[] = " iv"; + uint8_t intsecret[32]; + uint8_t info[32]; + 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)); + + if (ngtcp2_crypto_hkdf_extract(intsecret, md, secret, secretlen, salt, + saltlen) != 0) { + return -1; + } + + 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; + + if (ngtcp2_crypto_hkdf_expand(key, keylen, md, intsecret, sizeof(intsecret), + info, (size_t)(p - info)) != 0) { + return -1; + } + + p = info + info_prefixlen; + + memcpy(p, iv_info_suffix, sizeof(iv_info_suffix) - 1); + p += sizeof(iv_info_suffix) - 1; + + if (ngtcp2_crypto_hkdf_expand(iv, ivlen, md, intsecret, sizeof(intsecret), + info, (size_t)(p - info)) != 0) { + return -1; + } + + return 0; +} + +static size_t crypto_generate_retry_token_aad(uint8_t *dest, uint32_t version, + const ngtcp2_sockaddr *sa, + ngtcp2_socklen salen, + const ngtcp2_cid *retry_scid) { + uint8_t *p = dest; + + version = ngtcp2_htonl(version); + memcpy(p, &version, sizeof(version)); + p += sizeof(version); + memcpy(p, sa, (size_t)salen); + p += salen; + memcpy(p, retry_scid->data, retry_scid->datalen); + p += retry_scid->datalen; + + return (size_t)(p - dest); +} + +static const uint8_t retry_token_info_prefix[] = "retry_token"; + +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[NGTCP2_CRYPTO_MAX_RETRY_TOKENLEN]; + uint8_t rand_data[NGTCP2_CRYPTO_TOKEN_RAND_DATALEN]; + uint8_t key[32]; + uint8_t iv[32]; + size_t keylen; + size_t ivlen; + ngtcp2_crypto_aead aead; + ngtcp2_crypto_md md; + ngtcp2_crypto_aead_ctx aead_ctx; + size_t plaintextlen; + uint8_t + aad[sizeof(version) + sizeof(ngtcp2_sockaddr_union) + NGTCP2_MAX_CIDLEN]; + size_t aadlen; + uint8_t *p = plaintext; + ngtcp2_tstamp ts_be = ngtcp2_htonl64(ts); + int rv; + + 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; + memcpy(p, &ts_be, sizeof(ts_be)); + p += sizeof(ts_be); + + plaintextlen = (size_t)(p - plaintext); + + if (ngtcp2_crypto_random(rand_data, sizeof(rand_data)) != 0) { + return -1; + } + + ngtcp2_crypto_aead_aes_128_gcm(&aead); + ngtcp2_crypto_md_sha256(&md); + + 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, sizeof(rand_data), + retry_token_info_prefix, + sizeof(retry_token_info_prefix) - 1) != 0) { + return -1; + } + + aadlen = crypto_generate_retry_token_aad(aad, version, remote_addr, + remote_addrlen, retry_scid); + + p = token; + *p++ = NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY; + + if (ngtcp2_crypto_aead_ctx_encrypt_init(&aead_ctx, &aead, key, ivlen) != 0) { + return -1; + } + + rv = ngtcp2_crypto_encrypt(p, &aead, &aead_ctx, plaintext, plaintextlen, iv, + ivlen, aad, aadlen); + + ngtcp2_crypto_aead_ctx_free(&aead_ctx); + + if (rv != 0) { + return -1; + } + + p += plaintextlen + aead.max_overhead; + memcpy(p, rand_data, sizeof(rand_data)); + p += sizeof(rand_data); + + return p - token; +} + +int ngtcp2_crypto_verify_retry_token( + ngtcp2_cid *odcid, const uint8_t *token, size_t tokenlen, + const uint8_t *secret, size_t secretlen, uint32_t version, + const ngtcp2_sockaddr *remote_addr, ngtcp2_socklen remote_addrlen, + const ngtcp2_cid *dcid, ngtcp2_duration timeout, ngtcp2_tstamp ts) { + uint8_t + plaintext[/* cid len = */ 1 + NGTCP2_MAX_CIDLEN + sizeof(ngtcp2_tstamp)]; + uint8_t key[32]; + uint8_t iv[32]; + size_t keylen; + size_t ivlen; + ngtcp2_crypto_aead_ctx aead_ctx; + ngtcp2_crypto_aead aead; + ngtcp2_crypto_md md; + uint8_t + aad[sizeof(version) + sizeof(ngtcp2_sockaddr_union) + NGTCP2_MAX_CIDLEN]; + size_t aadlen; + const uint8_t *rand_data; + const uint8_t *ciphertext; + size_t ciphertextlen; + size_t cil; + int rv; + ngtcp2_tstamp gen_ts; + + assert((size_t)remote_addrlen <= sizeof(ngtcp2_sockaddr_union)); + + if (tokenlen != NGTCP2_CRYPTO_MAX_RETRY_TOKENLEN || + token[0] != NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY) { + return -1; + } + + rand_data = token + tokenlen - NGTCP2_CRYPTO_TOKEN_RAND_DATALEN; + ciphertext = token + 1; + ciphertextlen = tokenlen - 1 - NGTCP2_CRYPTO_TOKEN_RAND_DATALEN; + + ngtcp2_crypto_aead_aes_128_gcm(&aead); + ngtcp2_crypto_md_sha256(&md); + + keylen = ngtcp2_crypto_aead_keylen(&aead); + ivlen = ngtcp2_crypto_aead_noncelen(&aead); + + 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) { + return -1; + } + + aadlen = crypto_generate_retry_token_aad(aad, version, remote_addr, + remote_addrlen, dcid); + + if (ngtcp2_crypto_aead_ctx_decrypt_init(&aead_ctx, &aead, key, ivlen) != 0) { + return -1; + } + + rv = ngtcp2_crypto_decrypt(plaintext, &aead, &aead_ctx, ciphertext, + ciphertextlen, iv, ivlen, aad, aadlen); + + ngtcp2_crypto_aead_ctx_free(&aead_ctx); + + if (rv != 0) { + return -1; + } + + cil = plaintext[0]; + + if (cil != 0 && (cil < NGTCP2_MIN_CIDLEN || cil > NGTCP2_MAX_CIDLEN)) { + return -1; + } + + memcpy(&gen_ts, plaintext + /* cid len = */ 1 + NGTCP2_MAX_CIDLEN, + sizeof(gen_ts)); + + gen_ts = ngtcp2_ntohl64(gen_ts); + if (gen_ts + timeout <= ts) { + return -1; + } + + ngtcp2_cid_init(odcid, plaintext + /* cid len = */ 1, cil); + + return 0; +} + +static size_t crypto_generate_regular_token_aad(uint8_t *dest, + const ngtcp2_sockaddr *sa) { + const uint8_t *addr; + size_t addrlen; + + switch (sa->sa_family) { + case NGTCP2_AF_INET: + addr = (const uint8_t *)&((const ngtcp2_sockaddr_in *)(void *)sa)->sin_addr; + addrlen = sizeof(((const ngtcp2_sockaddr_in *)(void *)sa)->sin_addr); + break; + case NGTCP2_AF_INET6: + addr = + (const uint8_t *)&((const ngtcp2_sockaddr_in6 *)(void *)sa)->sin6_addr; + addrlen = sizeof(((const ngtcp2_sockaddr_in6 *)(void *)sa)->sin6_addr); + break; + default: + assert(0); + abort(); + } + + memcpy(dest, addr, addrlen); + + return addrlen; +} + +static const uint8_t regular_token_info_prefix[] = "regular_token"; + +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) { + uint8_t plaintext[sizeof(ngtcp2_tstamp)]; + uint8_t rand_data[NGTCP2_CRYPTO_TOKEN_RAND_DATALEN]; + uint8_t key[32]; + uint8_t iv[32]; + size_t keylen; + size_t ivlen; + ngtcp2_crypto_aead aead; + ngtcp2_crypto_md md; + ngtcp2_crypto_aead_ctx aead_ctx; + size_t plaintextlen; + uint8_t aad[sizeof(ngtcp2_sockaddr_in6)]; + size_t aadlen; + uint8_t *p = plaintext; + ngtcp2_tstamp ts_be = ngtcp2_htonl64(ts); + int rv; + (void)remote_addrlen; + + memcpy(p, &ts_be, sizeof(ts_be)); + p += sizeof(ts_be); + + plaintextlen = (size_t)(p - plaintext); + + if (ngtcp2_crypto_random(rand_data, sizeof(rand_data)) != 0) { + return -1; + } + + ngtcp2_crypto_aead_aes_128_gcm(&aead); + ngtcp2_crypto_md_sha256(&md); + + 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, sizeof(rand_data), + regular_token_info_prefix, + sizeof(regular_token_info_prefix) - 1) != 0) { + return -1; + } + + aadlen = crypto_generate_regular_token_aad(aad, remote_addr); + + p = token; + *p++ = NGTCP2_CRYPTO_TOKEN_MAGIC_REGULAR; + + if (ngtcp2_crypto_aead_ctx_encrypt_init(&aead_ctx, &aead, key, ivlen) != 0) { + return -1; + } + + rv = ngtcp2_crypto_encrypt(p, &aead, &aead_ctx, plaintext, plaintextlen, iv, + ivlen, aad, aadlen); + + ngtcp2_crypto_aead_ctx_free(&aead_ctx); + + if (rv != 0) { + return -1; + } + + p += plaintextlen + aead.max_overhead; + memcpy(p, rand_data, sizeof(rand_data)); + p += sizeof(rand_data); + + 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)]; + uint8_t key[32]; + uint8_t iv[32]; + size_t keylen; + size_t ivlen; + ngtcp2_crypto_aead_ctx aead_ctx; + ngtcp2_crypto_aead aead; + ngtcp2_crypto_md md; + uint8_t aad[sizeof(ngtcp2_sockaddr_in6)]; + size_t aadlen; + const uint8_t *rand_data; + const uint8_t *ciphertext; + size_t ciphertextlen; + int rv; + ngtcp2_tstamp gen_ts; + (void)remote_addrlen; + + if (tokenlen != NGTCP2_CRYPTO_MAX_REGULAR_TOKENLEN || + token[0] != NGTCP2_CRYPTO_TOKEN_MAGIC_REGULAR) { + return -1; + } + + rand_data = token + tokenlen - NGTCP2_CRYPTO_TOKEN_RAND_DATALEN; + ciphertext = token + 1; + ciphertextlen = tokenlen - 1 - NGTCP2_CRYPTO_TOKEN_RAND_DATALEN; + + ngtcp2_crypto_aead_aes_128_gcm(&aead); + ngtcp2_crypto_md_sha256(&md); + + keylen = ngtcp2_crypto_aead_keylen(&aead); + ivlen = ngtcp2_crypto_aead_noncelen(&aead); + + 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; + } + + aadlen = crypto_generate_regular_token_aad(aad, remote_addr); + + if (ngtcp2_crypto_aead_ctx_decrypt_init(&aead_ctx, &aead, key, ivlen) != 0) { + return -1; + } + + rv = ngtcp2_crypto_decrypt(plaintext, &aead, &aead_ctx, ciphertext, + ciphertextlen, iv, ivlen, aad, aadlen); + + ngtcp2_crypto_aead_ctx_free(&aead_ctx); + + if (rv != 0) { + return -1; + } + + memcpy(&gen_ts, plaintext, sizeof(gen_ts)); + + gen_ts = ngtcp2_ntohl64(gen_ts); + if (gen_ts + timeout <= ts) { + return -1; + } + + return 0; +} + +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, + size_t reasonlen) { + uint8_t rx_secret[NGTCP2_CRYPTO_INITIAL_SECRETLEN]; + uint8_t tx_secret[NGTCP2_CRYPTO_INITIAL_SECRETLEN]; + uint8_t initial_secret[NGTCP2_CRYPTO_INITIAL_SECRETLEN]; + uint8_t tx_key[NGTCP2_CRYPTO_INITIAL_KEYLEN]; + uint8_t tx_iv[NGTCP2_CRYPTO_INITIAL_IVLEN]; + uint8_t tx_hp_key[NGTCP2_CRYPTO_INITIAL_KEYLEN]; + ngtcp2_crypto_ctx ctx; + ngtcp2_ssize spktlen; + ngtcp2_crypto_aead_ctx aead_ctx = {0}; + ngtcp2_crypto_cipher_ctx hp_ctx = {0}; + + ngtcp2_crypto_ctx_initial(&ctx); + + if (ngtcp2_crypto_derive_initial_secrets(rx_secret, tx_secret, initial_secret, + version, scid, + NGTCP2_CRYPTO_SIDE_SERVER) != 0) { + return -1; + } + + if (ngtcp2_crypto_derive_packet_protection_key( + tx_key, tx_iv, tx_hp_key, version, &ctx.aead, &ctx.md, tx_secret, + NGTCP2_CRYPTO_INITIAL_SECRETLEN) != 0) { + return -1; + } + + if (ngtcp2_crypto_aead_ctx_encrypt_init(&aead_ctx, &ctx.aead, tx_key, + NGTCP2_CRYPTO_INITIAL_IVLEN) != 0) { + spktlen = -1; + goto end; + } + + if (ngtcp2_crypto_cipher_ctx_encrypt_init(&hp_ctx, &ctx.hp, tx_hp_key) != 0) { + spktlen = -1; + goto end; + } + + spktlen = ngtcp2_pkt_write_connection_close( + dest, destlen, version, dcid, scid, error_code, reason, reasonlen, + ngtcp2_crypto_encrypt_cb, &ctx.aead, &aead_ctx, tx_iv, + ngtcp2_crypto_hp_mask_cb, &ctx.hp, &hp_ctx); + if (spktlen < 0) { + spktlen = -1; + } + +end: + ngtcp2_crypto_cipher_ctx_free(&hp_ctx); + ngtcp2_crypto_aead_ctx_free(&aead_ctx); + + return spktlen; +} + +ngtcp2_ssize ngtcp2_crypto_write_retry(uint8_t *dest, size_t destlen, + uint32_t version, const ngtcp2_cid *dcid, + const ngtcp2_cid *scid, + const ngtcp2_cid *odcid, + const uint8_t *token, size_t tokenlen) { + ngtcp2_crypto_aead aead; + ngtcp2_ssize spktlen; + ngtcp2_crypto_aead_ctx aead_ctx = {0}; + const uint8_t *key; + size_t noncelen; + + ngtcp2_crypto_aead_retry(&aead); + + switch (version) { + case NGTCP2_PROTO_VER_V1: + default: + key = (const uint8_t *)NGTCP2_RETRY_KEY_V1; + noncelen = sizeof(NGTCP2_RETRY_NONCE_V1) - 1; + break; + case NGTCP2_PROTO_VER_V2: + key = (const uint8_t *)NGTCP2_RETRY_KEY_V2; + noncelen = sizeof(NGTCP2_RETRY_NONCE_V2) - 1; + break; + } + + if (ngtcp2_crypto_aead_ctx_encrypt_init(&aead_ctx, &aead, key, noncelen) != + 0) { + return -1; + } + + spktlen = ngtcp2_pkt_write_retry(dest, destlen, version, dcid, scid, odcid, + token, tokenlen, ngtcp2_crypto_encrypt_cb, + &aead, &aead_ctx); + if (spktlen < 0) { + spktlen = -1; + } + + ngtcp2_crypto_aead_ctx_free(&aead_ctx); + + return spktlen; +} + +int ngtcp2_crypto_client_initial_cb(ngtcp2_conn *conn, void *user_data) { + const ngtcp2_cid *dcid = ngtcp2_conn_get_dcid(conn); + void *tls = ngtcp2_conn_get_tls_native_handle(conn); + (void)user_data; + + if (ngtcp2_crypto_derive_and_install_initial_key( + conn, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + ngtcp2_conn_get_client_chosen_version(conn), dcid) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + if (crypto_set_local_transport_params(conn, tls) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + if (ngtcp2_crypto_read_write_crypto_data( + conn, NGTCP2_ENCRYPTION_LEVEL_INITIAL, NULL, 0) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +int ngtcp2_crypto_recv_retry_cb(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd, + void *user_data) { + (void)user_data; + + if (ngtcp2_crypto_derive_and_install_initial_key( + conn, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + ngtcp2_conn_get_client_chosen_version(conn), &hd->scid) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +int ngtcp2_crypto_recv_client_initial_cb(ngtcp2_conn *conn, + const ngtcp2_cid *dcid, + void *user_data) { + (void)user_data; + + if (ngtcp2_crypto_derive_and_install_initial_key( + conn, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + ngtcp2_conn_get_client_chosen_version(conn), dcid) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +int ngtcp2_crypto_version_negotiation_cb(ngtcp2_conn *conn, uint32_t version, + const ngtcp2_cid *client_dcid, + void *user_data) { + (void)user_data; + + if (ngtcp2_crypto_derive_and_install_vneg_initial_key( + conn, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, version, + client_dcid) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +void ngtcp2_crypto_delete_crypto_aead_ctx_cb(ngtcp2_conn *conn, + ngtcp2_crypto_aead_ctx *aead_ctx, + void *user_data) { + (void)conn; + (void)user_data; + + ngtcp2_crypto_aead_ctx_free(aead_ctx); +} + +void ngtcp2_crypto_delete_crypto_cipher_ctx_cb( + ngtcp2_conn *conn, ngtcp2_crypto_cipher_ctx *cipher_ctx, void *user_data) { + (void)conn; + (void)user_data; + + ngtcp2_crypto_cipher_ctx_free(cipher_ctx); +} + +int ngtcp2_crypto_recv_crypto_data_cb(ngtcp2_conn *conn, + ngtcp2_encryption_level encryption_level, + uint64_t offset, const uint8_t *data, + size_t datalen, void *user_data) { + int rv; + (void)offset; + (void)user_data; + + if (ngtcp2_crypto_read_write_crypto_data(conn, encryption_level, data, + datalen) != 0) { + rv = ngtcp2_conn_get_tls_error(conn); + if (rv) { + return rv; + } + return NGTCP2_ERR_CRYPTO; + } + + return 0; +} diff --git a/src/contrib/libngtcp2/ngtcp2/crypto/shared.h b/src/contrib/libngtcp2/ngtcp2/crypto/shared.h new file mode 100644 index 0000000..d69fd21 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/crypto/shared.h @@ -0,0 +1,397 @@ +/* + * 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. + */ +#ifndef NGTCP2_SHARED_H +#define NGTCP2_SHARED_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2_crypto.h> + +/** + * @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) +#define NGTCP2_CRYPTO_MAX_ENCRYPTION_AES_CCM (2965820ULL) + +/* Maximum authentication failure (decryption) limits during the + lifetime of a connection. */ +#define NGTCP2_CRYPTO_MAX_DECRYPTION_FAILURE_AES_GCM (1ULL << 52) +#define NGTCP2_CRYPTO_MAX_DECRYPTION_FAILURE_CHACHA20_POLY1305 (1ULL << 36) +#define NGTCP2_CRYPTO_MAX_DECRYPTION_FAILURE_AES_CCM (2965820ULL) + +/** + * @macro + * + * :macro:`NGTCP2_CRYPTO_INITIAL_SECRETLEN` is the length of secret + * for Initial packets. + */ +#define NGTCP2_CRYPTO_INITIAL_SECRETLEN 32 + +/** + * @macro + * + * :macro:`NGTCP2_CRYPTO_INITIAL_KEYLEN` is the length of key for + * Initial packets. + */ +#define NGTCP2_CRYPTO_INITIAL_KEYLEN 16 + +/** + * @macro + * + * :macro:`NGTCP2_CRYPTO_INITIAL_IVLEN` is the length of IV for + * Initial packets. + */ +#define NGTCP2_CRYPTO_INITIAL_IVLEN 12 + +/** + * @function + * + * `ngtcp2_crypto_ctx_initial` initializes |ctx| for Initial packet + * encryption and decryption. + */ +ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_initial(ngtcp2_crypto_ctx *ctx); + +/** + * @function + * + * `ngtcp2_crypto_aead_init` initializes |aead| with the provided + * |aead_native_handle| which is an underlying AEAD object. + * + * If libngtcp2_crypto_quictls is linked, |aead_native_handle| must be + * a pointer to EVP_CIPHER. + * + * If libngtcp2_crypto_gnutls is linked, |aead_native_handle| must be + * gnutls_cipher_algorithm_t casted to ``void *``. + * + * If libngtcp2_crypto_boringssl is linked, |aead_native_handle| must + * be a pointer to EVP_AEAD. + */ +ngtcp2_crypto_aead *ngtcp2_crypto_aead_init(ngtcp2_crypto_aead *aead, + void *aead_native_handle); + +/** + * @function + * + * `ngtcp2_crypto_aead_retry` initializes |aead| with the AEAD cipher + * AEAD_AES_128_GCM for Retry packet integrity protection. + */ +ngtcp2_crypto_aead *ngtcp2_crypto_aead_retry(ngtcp2_crypto_aead *aead); + +/** + * @enum + * + * :type:`ngtcp2_crypto_side` indicates which side the application + * implements; client or server. + */ +typedef enum ngtcp2_crypto_side { + /** + * :enum:`NGTCP2_CRYPTO_SIDE_CLIENT` indicates that the application + * is client. + */ + NGTCP2_CRYPTO_SIDE_CLIENT, + /** + * :enum:`NGTCP2_CRYPTO_SIDE_SERVER` indicates that the application + * is server. + */ + NGTCP2_CRYPTO_SIDE_SERVER +} ngtcp2_crypto_side; + +/** + * @function + * + * `ngtcp2_crypto_derive_initial_secrets` derives initial secrets. + * |rx_secret| and |tx_secret| must point to the buffer of at least 32 + * bytes capacity. rx for read and tx for write. This function + * writes rx and tx secrets into |rx_secret| and |tx_secret| + * respectively. The length of secret is 32 bytes long. + * |client_dcid| is the destination connection ID in first Initial + * packet of client. If |initial_secret| is not NULL, the initial + * secret is written to it. It must point to the buffer which has at + * least 32 bytes capacity. The initial secret is 32 bytes long. + * |side| specifies the side of application. + * + * This function returns 0 if it succeeds, or -1. + */ +int ngtcp2_crypto_derive_initial_secrets(uint8_t *rx_secret, uint8_t *tx_secret, + uint8_t *initial_secret, + uint32_t version, + const ngtcp2_cid *client_dcid, + ngtcp2_crypto_side side); + +/** + * @function + * + * `ngtcp2_crypto_derive_packet_protection_key` derives packet + * protection key. This function writes packet protection key into + * the buffer pointed by |key|. The length of derived key is + * `ngtcp2_crypto_aead_keylen(aead) <ngtcp2_crypto_aead_keylen>` + * bytes. |key| must have enough capacity to store the key. This + * function writes packet protection IV into |iv|. The length of + * derived IV is `ngtcp2_crypto_packet_protection_ivlen(aead) + * <ngtcp2_crypto_packet_protection_ivlen>` bytes. |iv| must have + * enough capacity to store the IV. + * + * If |hp| is not NULL, this function also derives packet header + * protection key and writes the key into the buffer pointed by |hp|. + * The length of derived key is `ngtcp2_crypto_aead_keylen(aead) + * <ngtcp2_crypto_aead_keylen>` bytes. |hp|, if not NULL, must have + * enough capacity to store the key. + * + * This function returns 0 if it succeeds, or -1. + */ +int ngtcp2_crypto_derive_packet_protection_key(uint8_t *key, uint8_t *iv, + uint8_t *hp, uint32_t version, + const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_md *md, + const uint8_t *secret, + size_t secretlen); + +/** + * @function + * + * `ngtcp2_crypto_update_traffic_secret` derives the next generation + * of the traffic secret. |secret| specifies the current secret and + * its length is given in |secretlen|. The length of new key is the + * same as the current key. This function writes new key into the + * buffer pointed by |dest|. |dest| must have the enough capacity to + * store the new key. + * + * This function returns 0 if it succeeds, or -1. + */ +int ngtcp2_crypto_update_traffic_secret(uint8_t *dest, uint32_t version, + const ngtcp2_crypto_md *md, + const uint8_t *secret, + size_t secretlen); + +/** + * @function + * + * `ngtcp2_crypto_set_local_transport_params` sets QUIC transport + * parameter, which is encoded in wire format and stored in the buffer + * pointed by |buf| of length |len|, to the native handle |tls|. + * + * |tls| points to a implementation dependent TLS session object. If + * libngtcp2_crypto_quictls is linked, |tls| must be a pointer to SSL + * object. + * + * This function returns 0 if it succeeds, or -1. + */ +int ngtcp2_crypto_set_local_transport_params(void *tls, const uint8_t *buf, + size_t len); + +/** + * @function + * + * `ngtcp2_crypto_set_remote_transport_params` retrieves a remote QUIC + * transport parameters from |tls| and sets it to |conn| using + * `ngtcp2_conn_set_remote_transport_params`. + * + * |tls| points to a implementation dependent TLS session object. If + * libngtcp2_crypto_quictls is linked, |tls| must be a pointer to SSL + * object. + * + * This function returns 0 if it succeeds, or -1. + */ +int ngtcp2_crypto_set_remote_transport_params(ngtcp2_conn *conn, void *tls); + +/** + * @function + * + * `ngtcp2_crypto_derive_and_install_initial_key` derives initial + * keying materials and installs keys to |conn|. + * + * If |rx_secret| is not NULL, the secret for decryption is written to + * the buffer pointed by |rx_secret|. The length of secret is 32 + * bytes, and |rx_secret| must point to the buffer which has enough + * capacity. + * + * If |tx_secret| is not NULL, the secret for encryption is written to + * the buffer pointed by |tx_secret|. The length of secret is 32 + * bytes, and |tx_secret| must point to the buffer which has enough + * capacity. + * + * If |initial_secret| is not NULL, the initial secret is written to + * the buffer pointed by |initial_secret|. The length of secret is 32 + * bytes, and |initial_secret| must point to the buffer which has + * enough capacity. + * + * |client_dcid| is the destination connection ID in first Initial + * packet of client. + * + * If |rx_key| is not NULL, the derived packet protection key for + * decryption is written to the buffer pointed by |rx_key|. If + * |rx_iv| is not NULL, the derived packet protection IV for + * decryption is written to the buffer pointed by |rx_iv|. If |rx_hp| + * is not NULL, the derived header protection key for decryption is + * written to the buffer pointed by |rx_hp|. + * + * If |tx_key| is not NULL, the derived packet protection key for + * encryption is written to the buffer pointed by |tx_key|. If + * |tx_iv| is not NULL, the derived packet protection IV for + * encryption is written to the buffer pointed by |tx_iv|. If |tx_hp| + * is not NULL, the derived header protection key for encryption is + * written to the buffer pointed by |tx_hp|. + * + * The length of packet protection key and header protection key is 16 + * bytes long. The length of packet protection IV is 12 bytes long. + * + * This function calls `ngtcp2_conn_set_initial_crypto_ctx` to set + * initial AEAD and message digest algorithm. After the successful + * call of this function, application can use + * `ngtcp2_conn_get_initial_crypto_ctx` to get the object. + * + * This function returns 0 if it succeeds, or -1. + */ +int ngtcp2_crypto_derive_and_install_initial_key( + ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret, + uint8_t *initial_secret, uint8_t *rx_key, uint8_t *rx_iv, uint8_t *rx_hp, + uint8_t *tx_key, uint8_t *tx_iv, uint8_t *tx_hp, uint32_t version, + const ngtcp2_cid *client_dcid); + +/** + * @function + * + * `ngtcp2_crypto_derive_and_install_vneg_initial_key` derives initial + * keying materials and installs keys to |conn|. This function is + * dedicated to install keys for |version| which is negotiated, or + * being negotiated. + * + * If |rx_secret| is not NULL, the secret for decryption is written to + * the buffer pointed by |rx_secret|. The length of secret is 32 + * bytes, and |rx_secret| must point to the buffer which has enough + * capacity. + * + * If |tx_secret| is not NULL, the secret for encryption is written to + * the buffer pointed by |tx_secret|. The length of secret is 32 + * bytes, and |tx_secret| must point to the buffer which has enough + * capacity. + * + * If |initial_secret| is not NULL, the initial secret is written to + * the buffer pointed by |initial_secret|. The length of secret is 32 + * bytes, and |initial_secret| must point to the buffer which has + * enough capacity. + * + * |client_dcid| is the destination connection ID in first Initial + * packet of client. + * + * If |rx_key| is not NULL, the derived packet protection key for + * decryption is written to the buffer pointed by |rx_key|. If + * |rx_iv| is not NULL, the derived packet protection IV for + * decryption is written to the buffer pointed by |rx_iv|. If |rx_hp| + * is not NULL, the derived header protection key for decryption is + * written to the buffer pointed by |rx_hp|. + * + * If |tx_key| is not NULL, the derived packet protection key for + * encryption is written to the buffer pointed by |tx_key|. If + * |tx_iv| is not NULL, the derived packet protection IV for + * encryption is written to the buffer pointed by |tx_iv|. If |tx_hp| + * is not NULL, the derived header protection key for encryption is + * written to the buffer pointed by |tx_hp|. + * + * The length of packet protection key and header protection key is 16 + * bytes long. The length of packet protection IV is 12 bytes long. + * + * This function returns 0 if it succeeds, or -1. + */ +int ngtcp2_crypto_derive_and_install_vneg_initial_key( + ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret, + uint8_t *initial_secret, uint8_t *rx_key, uint8_t *rx_iv, uint8_t *rx_hp, + uint8_t *tx_key, uint8_t *tx_iv, uint8_t *tx_hp, uint32_t version, + const ngtcp2_cid *client_dcid); + +/** + * @function + * + * `ngtcp2_crypto_cipher_ctx_encrypt_init` initializes |cipher_ctx| + * with new cipher context object for encryption which is constructed + * to use |key| as encryption key. |cipher| specifies cipher to use. + * + * This function returns 0 if it succeeds, or -1. + */ +int ngtcp2_crypto_cipher_ctx_encrypt_init(ngtcp2_crypto_cipher_ctx *cipher_ctx, + const ngtcp2_crypto_cipher *cipher, + const uint8_t *key); + +/** + * @function + * + * `ngtcp2_crypto_cipher_ctx_free` frees up resources used by + * |cipher_ctx|. This function does not free the memory pointed by + * |cipher_ctx| itself. + */ +void ngtcp2_crypto_cipher_ctx_free(ngtcp2_crypto_cipher_ctx *cipher_ctx); + +/* + * `ngtcp2_crypto_md_sha256` initializes |md| with SHA256 message + * digest algorithm and returns |md|. + */ +ngtcp2_crypto_md *ngtcp2_crypto_md_sha256(ngtcp2_crypto_md *md); + +ngtcp2_crypto_aead *ngtcp2_crypto_aead_aes_128_gcm(ngtcp2_crypto_aead *aead); + +/* + * `ngtcp2_crypto_random` writes cryptographically-secure random + * |datalen| bytes into the buffer pointed by |data|. + * + * This function returns 0 if it succeeds, or -1. + */ +int ngtcp2_crypto_random(uint8_t *data, size_t datalen); + +/** + * @function + * + * `ngtcp2_crypto_hkdf_expand_label` performs HKDF expand label. The + * result is |destlen| bytes long, and is stored to the buffer pointed + * by |dest|. + * + * This function returns 0 if it succeeds, or -1. + */ +int ngtcp2_crypto_hkdf_expand_label(uint8_t *dest, size_t destlen, + const ngtcp2_crypto_md *md, + const uint8_t *secret, size_t secretlen, + const uint8_t *label, size_t labellen); + +#endif /* NGTCP2_SHARED_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_acktr.c b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_acktr.c new file mode 100644 index 0000000..d4778d6 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_acktr.c @@ -0,0 +1,341 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 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_acktr.h" + +#include <assert.h> + +#include "ngtcp2_macro.h" +#include "ngtcp2_tstamp.h" + +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; +} + +int ngtcp2_acktr_entry_objalloc_new(ngtcp2_acktr_entry **ent, int64_t pkt_num, + ngtcp2_tstamp tstamp, + ngtcp2_objalloc *objalloc) { + *ent = ngtcp2_objalloc_acktr_entry_get(objalloc); + if (*ent == NULL) { + return NGTCP2_ERR_NOMEM; + } + + acktr_entry_init(*ent, pkt_num, tstamp); + + return 0; +} + +void ngtcp2_acktr_entry_objalloc_del(ngtcp2_acktr_entry *ent, + ngtcp2_objalloc *objalloc) { + ngtcp2_objalloc_acktr_entry_release(objalloc, ent); +} + +static int greater(const ngtcp2_ksl_key *lhs, const ngtcp2_ksl_key *rhs) { + return *(int64_t *)lhs > *(int64_t *)rhs; +} + +int ngtcp2_acktr_init(ngtcp2_acktr *acktr, ngtcp2_log *log, + const ngtcp2_mem *mem) { + int rv; + + ngtcp2_objalloc_acktr_entry_init(&acktr->objalloc, 32, mem); + + rv = ngtcp2_ringbuf_init(&acktr->acks, 32, sizeof(ngtcp2_acktr_ack_entry), + mem); + if (rv != 0) { + goto fail_acks_init; + } + + ngtcp2_ksl_init(&acktr->ents, greater, sizeof(int64_t), mem); + + acktr->log = log; + acktr->mem = mem; + acktr->flags = NGTCP2_ACKTR_FLAG_NONE; + acktr->first_unacked_ts = UINT64_MAX; + acktr->rx_npkt = 0; + + return 0; + +fail_acks_init: + ngtcp2_objalloc_free(&acktr->objalloc); + return rv; +} + +void ngtcp2_acktr_free(ngtcp2_acktr *acktr) { +#ifdef NOMEMPOOL + ngtcp2_ksl_it it; +#endif /* NOMEMPOOL */ + + if (acktr == NULL) { + return; + } + +#ifdef NOMEMPOOL + for (it = ngtcp2_ksl_begin(&acktr->ents); !ngtcp2_ksl_it_end(&it); + ngtcp2_ksl_it_next(&it)) { + ngtcp2_acktr_entry_objalloc_del(ngtcp2_ksl_it_get(&it), &acktr->objalloc); + } +#endif /* NOMEMPOOL */ + + ngtcp2_ksl_free(&acktr->ents); + + ngtcp2_ringbuf_free(&acktr->acks); + + ngtcp2_objalloc_free(&acktr->objalloc); +} + +int ngtcp2_acktr_add(ngtcp2_acktr *acktr, int64_t pkt_num, int active_ack, + ngtcp2_tstamp ts) { + ngtcp2_ksl_it it, prev_it; + ngtcp2_acktr_entry *ent, *prev_ent, *delent; + int rv; + int added = 0; + + if (ngtcp2_ksl_len(&acktr->ents)) { + it = ngtcp2_ksl_lower_bound(&acktr->ents, &pkt_num); + if (ngtcp2_ksl_it_end(&it)) { + ngtcp2_ksl_it_prev(&it); + ent = ngtcp2_ksl_it_get(&it); + + assert(ent->pkt_num >= pkt_num + (int64_t)ent->len); + + if (ent->pkt_num == pkt_num + (int64_t)ent->len) { + ++ent->len; + added = 1; + } + } else { + ent = ngtcp2_ksl_it_get(&it); + + assert(ent->pkt_num != pkt_num); + + if (ngtcp2_ksl_it_begin(&it)) { + if (ent->pkt_num + 1 == pkt_num) { + ngtcp2_ksl_update_key(&acktr->ents, &ent->pkt_num, &pkt_num); + ent->pkt_num = pkt_num; + ent->tstamp = ts; + ++ent->len; + added = 1; + } + } else { + prev_it = it; + ngtcp2_ksl_it_prev(&prev_it); + prev_ent = ngtcp2_ksl_it_get(&prev_it); + + assert(prev_ent->pkt_num >= pkt_num + (int64_t)prev_ent->len); + + if (ent->pkt_num + 1 == pkt_num) { + if (prev_ent->pkt_num == pkt_num + (int64_t)prev_ent->len) { + prev_ent->len += ent->len + 1; + ngtcp2_ksl_remove_hint(&acktr->ents, NULL, &it, &ent->pkt_num); + ngtcp2_acktr_entry_objalloc_del(ent, &acktr->objalloc); + added = 1; + } else { + ngtcp2_ksl_update_key(&acktr->ents, &ent->pkt_num, &pkt_num); + ent->pkt_num = pkt_num; + ent->tstamp = ts; + ++ent->len; + added = 1; + } + } else if (prev_ent->pkt_num == pkt_num + (int64_t)prev_ent->len) { + ++prev_ent->len; + added = 1; + } + } + } + } + + if (!added) { + rv = ngtcp2_acktr_entry_objalloc_new(&ent, pkt_num, ts, &acktr->objalloc); + if (rv != 0) { + return rv; + } + rv = ngtcp2_ksl_insert(&acktr->ents, NULL, &ent->pkt_num, ent); + if (rv != 0) { + ngtcp2_acktr_entry_objalloc_del(ent, &acktr->objalloc); + return rv; + } + } + + if (active_ack) { + acktr->flags |= NGTCP2_ACKTR_FLAG_ACTIVE_ACK; + if (acktr->first_unacked_ts == UINT64_MAX) { + acktr->first_unacked_ts = ts; + } + } + + if (ngtcp2_ksl_len(&acktr->ents) > NGTCP2_ACKTR_MAX_ENT) { + it = ngtcp2_ksl_end(&acktr->ents); + ngtcp2_ksl_it_prev(&it); + delent = ngtcp2_ksl_it_get(&it); + ngtcp2_ksl_remove_hint(&acktr->ents, NULL, &it, &delent->pkt_num); + ngtcp2_acktr_entry_objalloc_del(delent, &acktr->objalloc); + } + + return 0; +} + +void ngtcp2_acktr_forget(ngtcp2_acktr *acktr, ngtcp2_acktr_entry *ent) { + ngtcp2_ksl_it it; + + it = ngtcp2_ksl_lower_bound(&acktr->ents, &ent->pkt_num); + assert(*(int64_t *)ngtcp2_ksl_it_key(&it) == (int64_t)ent->pkt_num); + + for (; !ngtcp2_ksl_it_end(&it);) { + ent = ngtcp2_ksl_it_get(&it); + ngtcp2_ksl_remove_hint(&acktr->ents, &it, &it, &ent->pkt_num); + ngtcp2_acktr_entry_objalloc_del(ent, &acktr->objalloc); + } +} + +ngtcp2_ksl_it ngtcp2_acktr_get(ngtcp2_acktr *acktr) { + return ngtcp2_ksl_begin(&acktr->ents); +} + +int ngtcp2_acktr_empty(ngtcp2_acktr *acktr) { + ngtcp2_ksl_it it = ngtcp2_ksl_begin(&acktr->ents); + return ngtcp2_ksl_it_end(&it); +} + +ngtcp2_acktr_ack_entry *ngtcp2_acktr_add_ack(ngtcp2_acktr *acktr, + int64_t pkt_num, + int64_t largest_ack) { + ngtcp2_acktr_ack_entry *ent = ngtcp2_ringbuf_push_front(&acktr->acks); + + ent->largest_ack = largest_ack; + ent->pkt_num = pkt_num; + + return ent; +} + +/* + * acktr_remove removes |ent| from |acktr|. |it| must point to the + * node whose key identifies |ent|. The iterator which points to the + * entry next to |ent| is assigned to |it|. + */ +static void acktr_remove(ngtcp2_acktr *acktr, ngtcp2_ksl_it *it, + ngtcp2_acktr_entry *ent) { + ngtcp2_ksl_remove_hint(&acktr->ents, it, it, &ent->pkt_num); + ngtcp2_acktr_entry_objalloc_del(ent, &acktr->objalloc); +} + +static void acktr_on_ack(ngtcp2_acktr *acktr, ngtcp2_ringbuf *rb, + size_t ack_ent_offset) { + ngtcp2_acktr_ack_entry *ack_ent; + ngtcp2_acktr_entry *ent; + ngtcp2_ksl_it it; + + assert(ngtcp2_ringbuf_len(rb)); + + ack_ent = ngtcp2_ringbuf_get(rb, ack_ent_offset); + + /* Assume that ngtcp2_pkt_validate_ack(fr) returns 0 */ + it = ngtcp2_ksl_lower_bound(&acktr->ents, &ack_ent->largest_ack); + for (; !ngtcp2_ksl_it_end(&it);) { + ent = ngtcp2_ksl_it_get(&it); + acktr_remove(acktr, &it, ent); + } + + if (ngtcp2_ksl_len(&acktr->ents)) { + assert(ngtcp2_ksl_it_end(&it)); + + ngtcp2_ksl_it_prev(&it); + ent = ngtcp2_ksl_it_get(&it); + if (ent->pkt_num > ack_ent->largest_ack && + ack_ent->largest_ack >= ent->pkt_num - (int64_t)(ent->len - 1)) { + ent->len = (size_t)(ent->pkt_num - ack_ent->largest_ack); + } + } + + ngtcp2_ringbuf_resize(rb, ack_ent_offset); +} + +void ngtcp2_acktr_recv_ack(ngtcp2_acktr *acktr, const ngtcp2_ack *fr) { + ngtcp2_acktr_ack_entry *ent; + int64_t largest_ack = fr->largest_ack, min_ack; + size_t i, j; + ngtcp2_ringbuf *rb = &acktr->acks; + size_t nacks = ngtcp2_ringbuf_len(rb); + + /* Assume that ngtcp2_pkt_validate_ack(fr) returns 0 */ + for (j = 0; j < nacks; ++j) { + ent = ngtcp2_ringbuf_get(rb, j); + if (largest_ack >= ent->pkt_num) { + break; + } + } + if (j == nacks) { + return; + } + + min_ack = largest_ack - (int64_t)fr->first_ack_range; + + if (min_ack <= ent->pkt_num && ent->pkt_num <= largest_ack) { + acktr_on_ack(acktr, rb, j); + return; + } + + for (i = 0; i < fr->rangecnt && j < nacks; ++i) { + largest_ack = min_ack - (int64_t)fr->ranges[i].gap - 2; + min_ack = largest_ack - (int64_t)fr->ranges[i].len; + + for (;;) { + if (ent->pkt_num > largest_ack) { + ++j; + if (j == nacks) { + return; + } + ent = ngtcp2_ringbuf_get(rb, j); + continue; + } + if (ent->pkt_num < min_ack) { + break; + } + acktr_on_ack(acktr, rb, j); + return; + } + } +} + +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->first_unacked_ts = UINT64_MAX; + acktr->rx_npkt = 0; +} + +int ngtcp2_acktr_require_active_ack(ngtcp2_acktr *acktr, + ngtcp2_duration max_ack_delay, + ngtcp2_tstamp ts) { + return ngtcp2_tstamp_elapsed(acktr->first_unacked_ts, max_ack_delay, ts); +} + +void ngtcp2_acktr_immediate_ack(ngtcp2_acktr *acktr) { + acktr->flags |= NGTCP2_ACKTR_FLAG_IMMEDIATE_ACK; +} diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_acktr.h b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_acktr.h new file mode 100644 index 0000000..809fb69 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_acktr.h @@ -0,0 +1,221 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 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_ACKTR_H +#define NGTCP2_ACKTR_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_mem.h" +#include "ngtcp2_ringbuf.h" +#include "ngtcp2_ksl.h" +#include "ngtcp2_pkt.h" +#include "ngtcp2_objalloc.h" + +/* NGTCP2_ACKTR_MAX_ENT is the maximum number of ngtcp2_acktr_entry + which ngtcp2_acktr stores. */ +#define NGTCP2_ACKTR_MAX_ENT 1024 + +typedef struct ngtcp2_log ngtcp2_log; + +/* + * ngtcp2_acktr_entry is a range of packets which need to be acked. + */ +typedef struct ngtcp2_acktr_entry { + union { + struct { + /* pkt_num is the largest packet number to acknowledge in this + range. */ + int64_t pkt_num; + /* len is the consecutive packets started from pkt_num which + includes pkt_num itself counting in decreasing order. So pkt_num + = 987 and len = 2, this entry includes packet 987 and 986. */ + size_t len; + /* tstamp is the timestamp when a packet denoted by pkt_num is + received. */ + ngtcp2_tstamp tstamp; + }; + + ngtcp2_opl_entry oplent; + }; +} ngtcp2_acktr_entry; + +ngtcp2_objalloc_decl(acktr_entry, ngtcp2_acktr_entry, oplent); + +/* + * ngtcp2_acktr_entry_objalloc_new allocates memory for ent, and + * initializes it with the given parameters. The pointer to the + * allocated object is stored to |*ent|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +int ngtcp2_acktr_entry_objalloc_new(ngtcp2_acktr_entry **ent, int64_t pkt_num, + ngtcp2_tstamp tstamp, + ngtcp2_objalloc *objalloc); + +/* + * ngtcp2_acktr_entry_objalloc_del deallocates memory allocated for + * |ent|. + */ +void ngtcp2_acktr_entry_objalloc_del(ngtcp2_acktr_entry *ent, + ngtcp2_objalloc *objalloc); + +typedef struct ngtcp2_acktr_ack_entry { + /* largest_ack is the largest packet number in outgoing ACK frame */ + int64_t largest_ack; + /* pkt_num is the packet number that ACK frame is included. */ + int64_t pkt_num; +} ngtcp2_acktr_ack_entry; + +/* NGTCP2_ACKTR_FLAG_NONE indicates that no flag set. */ +#define NGTCP2_ACKTR_FLAG_NONE 0x00u +/* NGTCP2_ACKTR_FLAG_IMMEDIATE_ACK indicates that immediate + acknowledgement is required. */ +#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 +/* NGTCP2_ACKTR_FLAG_CANCEL_TIMER is set when ACK delay timer is + expired and canceled. */ +#define NGTCP2_ACKTR_FLAG_CANCEL_TIMER 0x0100u + +/* + * ngtcp2_acktr tracks received packets which we have to send ack. + */ +typedef struct ngtcp2_acktr { + ngtcp2_objalloc objalloc; + ngtcp2_ringbuf acks; + /* ents includes ngtcp2_acktr_entry sorted by decreasing order of + packet number. */ + ngtcp2_ksl ents; + ngtcp2_log *log; + const ngtcp2_mem *mem; + /* 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; + /* rx_npkt is the number of ACK eliciting packets received without + sending ACK. */ + size_t rx_npkt; +} ngtcp2_acktr; + +/* + * ngtcp2_acktr_init initializes |acktr|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +int ngtcp2_acktr_init(ngtcp2_acktr *acktr, ngtcp2_log *log, + const ngtcp2_mem *mem); + +/* + * ngtcp2_acktr_free frees resources allocated for |acktr|. It frees + * any ngtcp2_acktr_entry added to |acktr|. + */ +void ngtcp2_acktr_free(ngtcp2_acktr *acktr); + +/* + * ngtcp2_acktr_add adds packet number |pkt_num| to |acktr|. + * |active_ack| is nonzero if |pkt_num| is retransmittable packet. + * + * This function assumes that |acktr| does not contain |pkt_num|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * OUt of memory. + */ +int ngtcp2_acktr_add(ngtcp2_acktr *acktr, int64_t pkt_num, int active_ack, + ngtcp2_tstamp ts); + +/* + * ngtcp2_acktr_forget removes all entries which have the packet + * number that is equal to or less than ent->pkt_num. This function + * assumes that |acktr| includes |ent|. + */ +void ngtcp2_acktr_forget(ngtcp2_acktr *acktr, ngtcp2_acktr_entry *ent); + +/* + * ngtcp2_acktr_get returns the pointer to pointer to the entry which + * has the largest packet number to be acked. If there is no entry, + * returned value satisfies ngtcp2_ksl_it_end(&it) != 0. + */ +ngtcp2_ksl_it ngtcp2_acktr_get(ngtcp2_acktr *acktr); + +/* + * ngtcp2_acktr_empty returns nonzero if it has no packet to + * acknowledge. + */ +int ngtcp2_acktr_empty(ngtcp2_acktr *acktr); + +/* + * ngtcp2_acktr_add_ack records outgoing ACK frame whose largest + * acknowledged packet number is |largest_ack|. |pkt_num| is the + * packet number of a packet in which ACK frame is included. This + * function returns a pointer to the object it adds. + */ +ngtcp2_acktr_ack_entry * +ngtcp2_acktr_add_ack(ngtcp2_acktr *acktr, int64_t pkt_num, int64_t largest_ack); + +/* + * ngtcp2_acktr_recv_ack processes the incoming ACK frame |fr|. + * |pkt_num| is a packet number which includes |fr|. If we receive + * ACK which acknowledges the ACKs added by ngtcp2_acktr_add_ack, + * ngtcp2_acktr_entry which the outgoing ACK acknowledges is removed. + */ +void ngtcp2_acktr_recv_ack(ngtcp2_acktr *acktr, const ngtcp2_ack *fr); + +/* + * ngtcp2_acktr_commit_ack tells |acktr| that ACK frame is generated. + */ +void ngtcp2_acktr_commit_ack(ngtcp2_acktr *acktr); + +/* + * ngtcp2_acktr_require_active_ack returns nonzero if ACK frame should + * be generated actively. + */ +int ngtcp2_acktr_require_active_ack(ngtcp2_acktr *acktr, + ngtcp2_duration max_ack_delay, + ngtcp2_tstamp ts); + +/* + * ngtcp2_acktr_immediate_ack tells |acktr| that immediate + * acknowledgement is required. + */ +void ngtcp2_acktr_immediate_ack(ngtcp2_acktr *acktr); + +#endif /* NGTCP2_ACKTR_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_addr.c b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_addr.c new file mode 100644 index 0000000..f389abe --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_addr.c @@ -0,0 +1,117 @@ +/* + * 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. + */ +#include "ngtcp2_addr.h" + +#include <string.h> +#include <assert.h> + +#include "ngtcp2_unreachable.h" + +ngtcp2_addr *ngtcp2_addr_init(ngtcp2_addr *dest, const ngtcp2_sockaddr *addr, + ngtcp2_socklen addrlen) { + dest->addrlen = addrlen; + dest->addr = (ngtcp2_sockaddr *)addr; + return dest; +} + +void ngtcp2_addr_copy(ngtcp2_addr *dest, const ngtcp2_addr *src) { + dest->addrlen = src->addrlen; + if (src->addrlen) { + memcpy(dest->addr, src->addr, (size_t)src->addrlen); + } +} + +void ngtcp2_addr_copy_byte(ngtcp2_addr *dest, const ngtcp2_sockaddr *addr, + ngtcp2_socklen addrlen) { + dest->addrlen = addrlen; + if (addrlen) { + memcpy(dest->addr, addr, (size_t)addrlen); + } +} + +static int sockaddr_eq(const ngtcp2_sockaddr *a, const ngtcp2_sockaddr *b) { + assert(a->sa_family == b->sa_family); + + 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; + 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; + return ai->sin6_port == bi->sin6_port && + memcmp(&ai->sin6_addr, &bi->sin6_addr, sizeof(ai->sin6_addr)) == 0; + } + default: + ngtcp2_unreachable(); + } +} + +int ngtcp2_addr_eq(const ngtcp2_addr *a, const ngtcp2_addr *b) { + return a->addr->sa_family == b->addr->sa_family && + sockaddr_eq(a->addr, b->addr); +} + +uint32_t ngtcp2_addr_compare(const ngtcp2_addr *aa, const ngtcp2_addr *bb) { + uint32_t flags = NGTCP2_ADDR_COMPARE_FLAG_NONE; + const ngtcp2_sockaddr *a = aa->addr; + const ngtcp2_sockaddr *b = bb->addr; + + if (a->sa_family != b->sa_family) { + return NGTCP2_ADDR_COMPARE_FLAG_FAMILY; + } + + 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; + if (memcmp(&ai->sin_addr, &bi->sin_addr, sizeof(ai->sin_addr))) { + flags |= NGTCP2_ADDR_COMPARE_FLAG_ADDR; + } + if (ai->sin_port != bi->sin_port) { + flags |= NGTCP2_ADDR_COMPARE_FLAG_PORT; + } + return flags; + } + case NGTCP2_AF_INET6: { + const ngtcp2_sockaddr_in6 *ai = (const ngtcp2_sockaddr_in6 *)(void *)a, + *bi = (const ngtcp2_sockaddr_in6 *)(void *)b; + if (memcmp(&ai->sin6_addr, &bi->sin6_addr, sizeof(ai->sin6_addr))) { + flags |= NGTCP2_ADDR_COMPARE_FLAG_ADDR; + } + if (ai->sin6_port != bi->sin6_port) { + flags |= NGTCP2_ADDR_COMPARE_FLAG_PORT; + } + return flags; + } + default: + ngtcp2_unreachable(); + } +} + +int ngtcp2_addr_empty(const ngtcp2_addr *addr) { return addr->addrlen == 0; } diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_addr.h b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_addr.h new file mode 100644 index 0000000..8e3a9f5 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_addr.h @@ -0,0 +1,71 @@ +/* + * 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. + */ +#ifndef NGTCP2_ADDR_H +#define NGTCP2_ADDR_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +/* + * ngtcp2_addr_copy copies |src| to |dest|. This function assumes + * that dest->addr points to a buffer which have sufficient size to + * store the copy. + */ +void ngtcp2_addr_copy(ngtcp2_addr *dest, const ngtcp2_addr *src); + +/** + * @function + * + * `ngtcp2_addr_eq` returns nonzero if |a| equals |b|. + */ +int ngtcp2_addr_eq(const ngtcp2_addr *a, const ngtcp2_addr *b); + +/* NGTCP2_ADDR_COMPARE_FLAG_NONE indicates that no flag set. */ +#define NGTCP2_ADDR_COMPARE_FLAG_NONE 0x0u +/* NGTCP2_ADDR_COMPARE_FLAG_ADDR indicates IP addresses do not + match. */ +#define NGTCP2_ADDR_COMPARE_FLAG_ADDR 0x1u +/* NGTCP2_ADDR_COMPARE_FLAG_PORT indicates ports do not match. */ +#define NGTCP2_ADDR_COMPARE_FLAG_PORT 0x2u +/* NGTCP2_ADDR_COMPARE_FLAG_FAMILY indicates address families do not + match. */ +#define NGTCP2_ADDR_COMPARE_FLAG_FAMILY 0x4u + +/* + * ngtcp2_addr_compare compares address and port between |a| and |b|, + * and returns zero or more of NGTCP2_ADDR_COMPARE_FLAG_*. + */ +uint32_t ngtcp2_addr_compare(const ngtcp2_addr *a, const ngtcp2_addr *b); + +/* + * ngtcp2_addr_empty returns nonzero if |addr| has zero length + * address. + */ +int ngtcp2_addr_empty(const ngtcp2_addr *addr); + +#endif /* NGTCP2_ADDR_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_balloc.c b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_balloc.c new file mode 100644 index 0000000..5cc39ee --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_balloc.c @@ -0,0 +1,90 @@ +/* + * ngtcp2 + * + * Copyright (c) 2022 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_balloc.h" + +#include <assert.h> + +#include "ngtcp2_mem.h" + +void ngtcp2_balloc_init(ngtcp2_balloc *balloc, size_t blklen, + const ngtcp2_mem *mem) { + assert((blklen & 0xfu) == 0); + + balloc->mem = mem; + balloc->blklen = blklen; + balloc->head = NULL; + ngtcp2_buf_init(&balloc->buf, (void *)"", 0); +} + +void ngtcp2_balloc_free(ngtcp2_balloc *balloc) { + if (balloc == NULL) { + return; + } + + ngtcp2_balloc_clear(balloc); +} + +void ngtcp2_balloc_clear(ngtcp2_balloc *balloc) { + ngtcp2_memblock_hd *p, *next; + + for (p = balloc->head; p; p = next) { + next = p->next; + ngtcp2_mem_free(balloc->mem, p); + } + + balloc->head = NULL; + ngtcp2_buf_init(&balloc->buf, (void *)"", 0); +} + +int ngtcp2_balloc_get(ngtcp2_balloc *balloc, void **pbuf, size_t n) { + uint8_t *p; + ngtcp2_memblock_hd *hd; + + assert(n <= balloc->blklen); + + if (ngtcp2_buf_left(&balloc->buf) < n) { + p = ngtcp2_mem_malloc(balloc->mem, + sizeof(ngtcp2_memblock_hd) + 0x10u + balloc->blklen); + if (p == NULL) { + return NGTCP2_ERR_NOMEM; + } + + hd = (ngtcp2_memblock_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), + balloc->blklen); + } + + assert(((uintptr_t)balloc->buf.last & 0xfu) == 0); + + *pbuf = balloc->buf.last; + balloc->buf.last += (n + 0xfu) & ~(uintptr_t)0xfu; + + return 0; +} diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_balloc.h b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_balloc.h new file mode 100644 index 0000000..1fb1632 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_balloc.h @@ -0,0 +1,91 @@ +/* + * ngtcp2 + * + * Copyright (c) 2022 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_BALLOC_H +#define NGTCP2_BALLOC_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_buf.h" + +typedef struct ngtcp2_memblock_hd ngtcp2_memblock_hd; + +/* + * ngtcp2_memblock_hd is the header of memory block. + */ +struct ngtcp2_memblock_hd { + ngtcp2_memblock_hd *next; +}; + +/* + * ngtcp2_balloc is a custom memory allocator. It allocates |blklen| + * bytes of memory at once on demand, and returns its slice when the + * allocation is requested. + */ +typedef struct ngtcp2_balloc { + /* mem is the underlying memory allocator. */ + const ngtcp2_mem *mem; + /* blklen is the size of memory block. */ + size_t blklen; + /* head points to the list of memory block allocated so far. */ + ngtcp2_memblock_hd *head; + /* buf wraps the current memory block for allocation requests. */ + ngtcp2_buf buf; +} ngtcp2_balloc; + +/* + * ngtcp2_balloc_init initializes |balloc| with |blklen| which is the + * size of memory block. + */ +void ngtcp2_balloc_init(ngtcp2_balloc *balloc, size_t blklen, + const ngtcp2_mem *mem); + +/* + * ngtcp2_balloc_free releases all allocated memory blocks. + */ +void ngtcp2_balloc_free(ngtcp2_balloc *balloc); + +/* + * ngtcp2_balloc_get allocates |n| bytes of memory and assigns its + * pointer to |*pbuf|. + * + * It returns 0 if it succeeds, or one of the following negative error + * codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +int ngtcp2_balloc_get(ngtcp2_balloc *balloc, void **pbuf, size_t n); + +/* + * ngtcp2_balloc_clear releases all allocated memory blocks and + * initializes its state. + */ +void ngtcp2_balloc_clear(ngtcp2_balloc *balloc); + +#endif /* NGTCP2_BALLOC_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_bbr.c b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_bbr.c new file mode 100644 index 0000000..27c4667 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_bbr.c @@ -0,0 +1,1450 @@ +/* + * ngtcp2 + * + * Copyright (c) 2021 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_bbr.h" + +#include <assert.h> +#include <string.h> + +#include "ngtcp2_log.h" +#include "ngtcp2_macro.h" +#include "ngtcp2_mem.h" +#include "ngtcp2_rcvry.h" +#include "ngtcp2_rst.h" +#include "ngtcp2_conn_stat.h" + +#define NGTCP2_BBR_MAX_BW_FILTERLEN 2 + +#define NGTCP2_BBR_EXTRA_ACKED_FILTERLEN 10 + +#define NGTCP2_BBR_STARTUP_PACING_GAIN_H 277 + +#define NGTCP2_BBR_STARTUP_CWND_GAIN_H 200 + +#define NGTCP2_BBR_PROBE_RTT_CWND_GAIN_H 50 + +#define NGTCP2_BBR_BETA_NUMER 7 +#define NGTCP2_BBR_BETA_DENOM 10 + +#define NGTCP2_BBR_LOSS_THRESH_NUMER 2 +#define NGTCP2_BBR_LOSS_THRESH_DENOM 100 + +#define NGTCP2_BBR_HEADROOM_NUMER 15 +#define NGTCP2_BBR_HEADROOM_DENOM 100 + +#define NGTCP2_BBR_PROBE_RTT_INTERVAL (5 * NGTCP2_SECONDS) +#define NGTCP2_BBR_MIN_RTT_FILTERLEN (10 * NGTCP2_SECONDS) + +#define NGTCP2_BBR_PROBE_RTT_DURATION (200 * NGTCP2_MILLISECONDS) + +#define NGTCP2_BBR_PACING_MARGIN_PERCENT 1 + +static void bbr_on_init(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp initial_ts); + +static void bbr_on_transmit(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); + +static void bbr_reset_congestion_signals(ngtcp2_cc_bbr *bbr); + +static void bbr_reset_lower_bounds(ngtcp2_cc_bbr *bbr); + +static void bbr_init_round_counting(ngtcp2_cc_bbr *bbr); + +static void bbr_init_full_pipe(ngtcp2_cc_bbr *bbr); + +static void bbr_init_pacing_rate(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat); + +static void bbr_set_pacing_rate_with_gain(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_enter_startup(ngtcp2_cc_bbr *bbr); + +static void bbr_check_startup_done(ngtcp2_cc_bbr *bbr, + const ngtcp2_cc_ack *ack); + +static void bbr_update_on_ack(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, ngtcp2_tstamp ts); + +static void bbr_update_model_and_state(ngtcp2_cc_bbr *cc, + ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, + ngtcp2_tstamp ts); + +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); + +static void bbr_advance_latest_delivery_signals(ngtcp2_cc_bbr *bbr, + ngtcp2_conn_stat *cstat); + +static void bbr_update_congestion_signals(ngtcp2_cc_bbr *bbr, + 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_init_lower_bounds(ngtcp2_cc_bbr *bbr, 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, + 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 void bbr_update_ack_aggregation(ngtcp2_cc_bbr *bbr, + 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(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); + +static void bbr_enter_probe_bw(ngtcp2_cc_bbr *bbr, ngtcp2_tstamp ts); + +static void bbr_start_probe_bw_down(ngtcp2_cc_bbr *bbr, ngtcp2_tstamp ts); + +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, + ngtcp2_tstamp ts); + +static void bbr_update_probe_bw_cycle_phase(ngtcp2_cc_bbr *bbr, + ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, + ngtcp2_tstamp ts); + +static int bbr_check_time_to_cruise(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); + +static int bbr_has_elapsed_in_phase(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 void bbr_raise_inflight_hi_slope(ngtcp2_cc_bbr *bbr, + ngtcp2_conn_stat *cstat); + +static void bbr_probe_inflight_hi_upward(ngtcp2_cc_bbr *bbr, + 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, ngtcp2_tstamp ts); + +static int bbr_check_time_to_probe_bw(ngtcp2_cc_bbr *bbr, + 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 uint64_t bbr_target_inflight(ngtcp2_cc_bbr *bbr, + ngtcp2_conn_stat *cstat); + +static int bbr_check_inflight_too_high(ngtcp2_cc_bbr *bbr, + ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); + +static int is_inflight_too_high(const ngtcp2_rs *rs); + +static void bbr_handle_inflight_too_high(ngtcp2_cc_bbr *bbr, + ngtcp2_conn_stat *cstat, + const ngtcp2_rs *rs, ngtcp2_tstamp ts); + +static void bbr_handle_lost_packet(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_pkt *pkt, ngtcp2_tstamp ts); + +static uint64_t bbr_inflight_hi_from_lost_packet(ngtcp2_cc_bbr *bbr, + const ngtcp2_rs *rs, + const ngtcp2_cc_pkt *pkt); + +static void bbr_update_min_rtt(ngtcp2_cc_bbr *bbr, const ngtcp2_cc_ack *ack, + ngtcp2_tstamp ts); + +static void bbr_check_probe_rtt(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); + +static void bbr_enter_probe_rtt(ngtcp2_cc_bbr *bbr); + +static void bbr_handle_probe_rtt(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); + +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); + +static void bbr_exit_probe_rtt(ngtcp2_cc_bbr *bbr, ngtcp2_tstamp ts); + +static void bbr_handle_restart_from_idle(ngtcp2_cc_bbr *bbr, + ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); + +static uint64_t bbr_bdp_multiple(ngtcp2_cc_bbr *bbr, uint64_t bw, + uint64_t gain_h); + +static uint64_t bbr_quantization_budget(ngtcp2_cc_bbr *bbr, + ngtcp2_conn_stat *cstat, + uint64_t inflight); + +static uint64_t bbr_inflight(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, + uint64_t bw, uint64_t gain_h); + +static void bbr_update_max_inflight(ngtcp2_cc_bbr *bbr, + ngtcp2_conn_stat *cstat); + +static void bbr_update_offload_budget(ngtcp2_cc_bbr *bbr, + 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_modulate_cwnd_for_recovery(ngtcp2_cc_bbr *bbr, + ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack); + +static void bbr_save_cwnd(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat); + +static void bbr_restore_cwnd(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat); + +static uint64_t bbr_probe_rtt_cwnd(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat); + +static void bbr_bound_cwnd_for_probe_rtt(ngtcp2_cc_bbr *bbr, + ngtcp2_conn_stat *cstat); + +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, + ngtcp2_conn_stat *cstat); + +static void bbr_set_send_quantum(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat); + +static int in_congestion_recovery(const ngtcp2_conn_stat *cstat, + ngtcp2_tstamp sent_time); + +static void bbr_handle_recovery(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, ngtcp2_tstamp ts); + +static void bbr_on_init(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp initial_ts) { + ngtcp2_window_filter_init(&bbr->max_bw_filter, NGTCP2_BBR_MAX_BW_FILTERLEN); + ngtcp2_window_filter_init(&bbr->extra_acked_filter, + NGTCP2_BBR_EXTRA_ACKED_FILTERLEN); + + bbr->min_rtt = UINT64_MAX; + bbr->min_rtt_stamp = initial_ts; + /* remark: Use UINT64_MAX instead of 0 for consistency. */ + bbr->probe_rtt_done_stamp = UINT64_MAX; + bbr->probe_rtt_round_done = 0; + bbr->prior_cwnd = 0; + bbr->idle_restart = 0; + bbr->extra_acked_interval_start = initial_ts; + bbr->extra_acked_delivered = 0; + + bbr_reset_congestion_signals(bbr); + bbr_reset_lower_bounds(bbr); + bbr_init_round_counting(bbr); + bbr_init_full_pipe(bbr); + bbr_init_pacing_rate(bbr, cstat); + bbr_enter_startup(bbr); + + cstat->send_quantum = cstat->max_tx_udp_payload_size * 10; + + /* Missing in documentation */ + bbr->loss_round_start = 0; + bbr->loss_round_delivered = UINT64_MAX; + + bbr->rounds_since_bw_probe = 0; + + bbr->max_bw = 0; + bbr->bw = 0; + + bbr->cycle_count = 0; + + bbr->extra_acked = 0; + + bbr->bytes_lost_in_round = 0; + bbr->loss_events_in_round = 0; + + bbr->offload_budget = 0; + + bbr->probe_up_cnt = UINT64_MAX; + bbr->cycle_stamp = UINT64_MAX; + bbr->ack_phase = 0; + bbr->bw_probe_wait = 0; + bbr->bw_probe_samples = 0; + bbr->bw_probe_up_rounds = 0; + bbr->bw_probe_up_acks = 0; + + bbr->inflight_hi = UINT64_MAX; + bbr->bw_hi = UINT64_MAX; + + bbr->probe_rtt_expired = 0; + bbr->probe_rtt_min_delay = UINT64_MAX; + bbr->probe_rtt_min_stamp = initial_ts; + + bbr->in_loss_recovery = 0; + bbr->packet_conservation = 0; + + bbr->max_inflight = 0; + + bbr->congestion_recovery_start_ts = UINT64_MAX; + bbr->congestion_recovery_next_round_delivered = 0; + + bbr->prior_inflight_lo = 0; + bbr->prior_inflight_hi = 0; + bbr->prior_bw_lo = 0; +} + +static void bbr_reset_congestion_signals(ngtcp2_cc_bbr *bbr) { + bbr->loss_in_round = 0; + bbr->bw_latest = 0; + bbr->inflight_latest = 0; +} + +static void bbr_reset_lower_bounds(ngtcp2_cc_bbr *bbr) { + bbr->bw_lo = UINT64_MAX; + bbr->inflight_lo = UINT64_MAX; +} + +static void bbr_init_round_counting(ngtcp2_cc_bbr *bbr) { + bbr->next_round_delivered = 0; + bbr->round_start = 0; + bbr->round_count = 0; +} + +static void bbr_init_full_pipe(ngtcp2_cc_bbr *bbr) { + bbr->filled_pipe = 0; + bbr->full_bw = 0; + bbr->full_bw_count = 0; +} + +static void bbr_check_startup_full_bandwidth(ngtcp2_cc_bbr *bbr) { + if (bbr->filled_pipe || !bbr->round_start || bbr->rst->rs.is_app_limited) { + return; + } + + if (bbr->max_bw * 100 >= bbr->full_bw * 125) { + bbr->full_bw = bbr->max_bw; + bbr->full_bw_count = 0; + } + + ++bbr->full_bw_count; + + if (bbr->full_bw_count >= 3) { + bbr->filled_pipe = 1; + + ngtcp2_log_info(bbr->cc.log, NGTCP2_LOG_EVENT_CCA, + "bbr filled pipe, full_bw=%" PRIu64, bbr->full_bw); + } +} + +static void bbr_check_startup_high_loss(ngtcp2_cc_bbr *bbr, + const ngtcp2_cc_ack *ack) { + if (bbr->filled_pipe || !bbr->round_start || bbr->rst->rs.is_app_limited) { + return; + } + + if (bbr->loss_events_in_round <= 3) { + return; + } + + /* loss_thresh = 2% */ + if (bbr->bytes_lost_in_round * 100 <= ack->prior_bytes_in_flight * 2) { + return; + } + + bbr->filled_pipe = 1; +} + +static void bbr_init_pacing_rate(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat) { + cstat->pacing_interval = NGTCP2_MILLISECONDS * 100 / + NGTCP2_BBR_STARTUP_PACING_GAIN_H / bbr->initial_cwnd; +} + +static void bbr_set_pacing_rate_with_gain(ngtcp2_cc_bbr *bbr, + ngtcp2_conn_stat *cstat, + uint64_t pacing_gain_h) { + ngtcp2_duration interval; + + if (bbr->bw == 0) { + return; + } + + interval = NGTCP2_SECONDS * 100 * 100 / pacing_gain_h / bbr->bw / + (100 - NGTCP2_BBR_PACING_MARGIN_PERCENT); + + if (bbr->filled_pipe || interval < cstat->pacing_interval) { + cstat->pacing_interval = interval; + } +} + +static void bbr_set_pacing_rate(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat) { + bbr_set_pacing_rate_with_gain(bbr, cstat, bbr->pacing_gain_h); +} + +static void bbr_enter_startup(ngtcp2_cc_bbr *bbr) { + ngtcp2_log_info(bbr->cc.log, NGTCP2_LOG_EVENT_CCA, "bbr enter Startup"); + + bbr->state = NGTCP2_BBR_STATE_STARTUP; + bbr->pacing_gain_h = NGTCP2_BBR_STARTUP_PACING_GAIN_H; + bbr->cwnd_gain_h = NGTCP2_BBR_STARTUP_CWND_GAIN_H; +} + +static void bbr_check_startup_done(ngtcp2_cc_bbr *bbr, + const ngtcp2_cc_ack *ack) { + bbr_check_startup_full_bandwidth(bbr); + bbr_check_startup_high_loss(bbr, ack); + + if (bbr->state == NGTCP2_BBR_STATE_STARTUP && bbr->filled_pipe) { + bbr_enter_drain(bbr); + } +} + +static void bbr_on_transmit(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + bbr_handle_restart_from_idle(bbr, cstat, ts); +} + +static void bbr_update_on_ack(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, ngtcp2_tstamp ts) { + bbr_update_model_and_state(bbr, cstat, ack, ts); + bbr_update_control_parameters(bbr, cstat, ack); +} + +static void bbr_update_model_and_state(ngtcp2_cc_bbr *bbr, + ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, + ngtcp2_tstamp ts) { + bbr_update_latest_delivery_signals(bbr, cstat); + bbr_update_congestion_signals(bbr, cstat, ack); + bbr_update_ack_aggregation(bbr, cstat, ack, ts); + bbr_check_startup_done(bbr, ack); + bbr_check_drain(bbr, cstat, ts); + bbr_update_probe_bw_cycle_phase(bbr, cstat, ack, ts); + bbr_update_min_rtt(bbr, ack, ts); + bbr_check_probe_rtt(bbr, cstat, ts); + bbr_advance_latest_delivery_signals(bbr, cstat); + bbr_bound_bw_for_model(bbr); +} + +static void bbr_update_control_parameters(ngtcp2_cc_bbr *bbr, + ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack) { + bbr_set_pacing_rate(bbr, cstat); + bbr_set_send_quantum(bbr, cstat); + 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) { + bbr->loss_round_start = 0; + bbr->bw_latest = ngtcp2_max(bbr->bw_latest, cstat->delivery_rate_sec); + bbr->inflight_latest = + ngtcp2_max(bbr->inflight_latest, bbr->rst->rs.delivered); + + if (bbr->rst->rs.prior_delivered >= bbr->loss_round_delivered) { + bbr->loss_round_delivered = bbr->rst->delivered; + bbr->loss_round_start = 1; + } +} + +static void bbr_advance_latest_delivery_signals(ngtcp2_cc_bbr *bbr, + ngtcp2_conn_stat *cstat) { + if (bbr->loss_round_start) { + bbr->bw_latest = cstat->delivery_rate_sec; + bbr->inflight_latest = bbr->rst->rs.delivered; + } +} + +static void bbr_update_congestion_signals(ngtcp2_cc_bbr *bbr, + ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack) { + bbr_update_max_bw(bbr, cstat, ack); + + if (ack->bytes_lost) { + bbr->bytes_lost_in_round += ack->bytes_lost; + ++bbr->loss_events_in_round; + + if (!bbr->loss_in_round) { + bbr->loss_in_round = 1; + bbr->loss_round_delivered = bbr->rst->delivered; + } + } + + if (!bbr->loss_round_start) { + return; + } + + bbr_adapt_lower_bounds_from_congestion(bbr, cstat); + + bbr->loss_in_round = 0; +} + +static void bbr_adapt_lower_bounds_from_congestion(ngtcp2_cc_bbr *bbr, + ngtcp2_conn_stat *cstat) { + if (bbr_is_in_probe_bw_state(bbr)) { + return; + } + + if (bbr->loss_in_round) { + bbr_init_lower_bounds(bbr, cstat); + bbr_loss_lower_bounds(bbr); + } +} + +static void bbr_init_lower_bounds(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat) { + if (bbr->bw_lo == UINT64_MAX) { + bbr->bw_lo = bbr->max_bw; + } + + if (bbr->inflight_lo == UINT64_MAX) { + bbr->inflight_lo = cstat->cwnd; + } +} + +static void bbr_loss_lower_bounds(ngtcp2_cc_bbr *bbr) { + bbr->bw_lo = ngtcp2_max(bbr->bw_latest, bbr->bw_lo * NGTCP2_BBR_BETA_NUMER / + NGTCP2_BBR_BETA_DENOM); + bbr->inflight_lo = ngtcp2_max(bbr->inflight_latest, + bbr->inflight_lo * NGTCP2_BBR_BETA_NUMER / + NGTCP2_BBR_BETA_DENOM); +} + +static void bbr_bound_bw_for_model(ngtcp2_cc_bbr *bbr) { + bbr->bw = ngtcp2_min(bbr->max_bw, bbr->bw_lo); + bbr->bw = ngtcp2_min(bbr->bw, bbr->bw_hi); +} + +static void bbr_update_max_bw(ngtcp2_cc_bbr *bbr, 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) { + ngtcp2_window_filter_update(&bbr->max_bw_filter, cstat->delivery_rate_sec, + bbr->cycle_count); + + bbr->max_bw = ngtcp2_window_filter_get_best(&bbr->max_bw_filter); + } +} + +static void bbr_update_round(ngtcp2_cc_bbr *bbr, const ngtcp2_cc_ack *ack) { + if (ack->pkt_delivered >= bbr->next_round_delivered) { + bbr_start_round(bbr); + + ++bbr->round_count; + ++bbr->rounds_since_bw_probe; + bbr->round_start = 1; + + bbr->bytes_lost_in_round = 0; + bbr->loss_events_in_round = 0; + + bbr->rst->is_cwnd_limited = 0; + + return; + } + + bbr->round_start = 0; +} + +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) { + switch (bbr->state) { + case NGTCP2_BBR_STATE_PROBE_BW_DOWN: + case NGTCP2_BBR_STATE_PROBE_BW_CRUISE: + case NGTCP2_BBR_STATE_PROBE_BW_REFILL: + case NGTCP2_BBR_STATE_PROBE_BW_UP: + return 1; + default: + return 0; + } +} + +static void bbr_update_ack_aggregation(ngtcp2_cc_bbr *bbr, + ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, + ngtcp2_tstamp ts) { + ngtcp2_duration interval = ts - bbr->extra_acked_interval_start; + uint64_t expected_delivered = bbr->bw * interval / NGTCP2_SECONDS; + uint64_t extra; + + if (bbr->extra_acked_delivered <= expected_delivered) { + bbr->extra_acked_delivered = 0; + bbr->extra_acked_interval_start = ts; + expected_delivered = 0; + } + + bbr->extra_acked_delivered += ack->bytes_delivered; + extra = bbr->extra_acked_delivered - expected_delivered; + extra = ngtcp2_min(extra, cstat->cwnd); + + ngtcp2_window_filter_update(&bbr->extra_acked_filter, extra, + bbr->round_count); + + bbr->extra_acked = ngtcp2_window_filter_get_best(&bbr->extra_acked_filter); +} + +static void bbr_enter_drain(ngtcp2_cc_bbr *bbr) { + ngtcp2_log_info(bbr->cc.log, NGTCP2_LOG_EVENT_CCA, "bbr enter Drain"); + + bbr->state = NGTCP2_BBR_STATE_DRAIN; + bbr->pacing_gain_h = 100 * 100 / NGTCP2_BBR_STARTUP_CWND_GAIN_H; + bbr->cwnd_gain_h = NGTCP2_BBR_STARTUP_CWND_GAIN_H; +} + +static void bbr_check_drain(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + if (bbr->state == NGTCP2_BBR_STATE_DRAIN && + cstat->bytes_in_flight <= bbr_inflight(bbr, cstat, bbr->bw, 100)) { + bbr_enter_probe_bw(bbr, ts); + } +} + +static void bbr_enter_probe_bw(ngtcp2_cc_bbr *bbr, ngtcp2_tstamp ts) { + bbr_start_probe_bw_down(bbr, ts); +} + +static void bbr_start_probe_bw_down(ngtcp2_cc_bbr *bbr, ngtcp2_tstamp ts) { + ngtcp2_log_info(bbr->cc.log, NGTCP2_LOG_EVENT_CCA, "bbr start ProbeBW_DOWN"); + + bbr_reset_congestion_signals(bbr); + + bbr->probe_up_cnt = UINT64_MAX; + + bbr_pick_probe_wait(bbr); + + bbr->cycle_stamp = ts; + bbr->ack_phase = NGTCP2_BBR_ACK_PHASE_ACKS_PROBE_STOPPING; + + bbr_start_round(bbr); + + bbr->state = NGTCP2_BBR_STATE_PROBE_BW_DOWN; + bbr->pacing_gain_h = 90; + bbr->cwnd_gain_h = 200; +} + +static void bbr_start_probe_bw_cruise(ngtcp2_cc_bbr *bbr) { + ngtcp2_log_info(bbr->cc.log, NGTCP2_LOG_EVENT_CCA, + "bbr start ProbeBW_CRUISE"); + + bbr->state = NGTCP2_BBR_STATE_PROBE_BW_CRUISE; + bbr->pacing_gain_h = 100; + bbr->cwnd_gain_h = 200; +} + +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->bw_probe_up_rounds = 0; + bbr->bw_probe_up_acks = 0; + bbr->ack_phase = NGTCP2_BBR_ACK_PHASE_ACKS_REFILLING; + + bbr_start_round(bbr); + + bbr->state = NGTCP2_BBR_STATE_PROBE_BW_REFILL; + bbr->pacing_gain_h = 100; + bbr->cwnd_gain_h = 200; +} + +static void bbr_start_probe_bw_up(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + ngtcp2_log_info(bbr->cc.log, NGTCP2_LOG_EVENT_CCA, "bbr start ProbeBW_UP"); + + bbr->ack_phase = NGTCP2_BBR_ACK_PHASE_ACKS_PROBE_STARTING; + + bbr_start_round(bbr); + + bbr->cycle_stamp = ts; + bbr->state = NGTCP2_BBR_STATE_PROBE_BW_UP; + bbr->pacing_gain_h = 125; + bbr->cwnd_gain_h = 225; + + bbr_raise_inflight_hi_slope(bbr, cstat); +} + +static void bbr_update_probe_bw_cycle_phase(ngtcp2_cc_bbr *bbr, + ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, + ngtcp2_tstamp ts) { + if (!bbr->filled_pipe) { + return; + } + + bbr_adapt_upper_bounds(bbr, cstat, ack, ts); + + if (!bbr_is_in_probe_bw_state(bbr)) { + return; + } + + switch (bbr->state) { + case NGTCP2_BBR_STATE_PROBE_BW_DOWN: + if (bbr_check_time_to_probe_bw(bbr, cstat, ts)) { + return; + } + + if (bbr_check_time_to_cruise(bbr, cstat, ts)) { + bbr_start_probe_bw_cruise(bbr); + } + + break; + case NGTCP2_BBR_STATE_PROBE_BW_CRUISE: + if (bbr_check_time_to_probe_bw(bbr, cstat, ts)) { + return; + } + + break; + case NGTCP2_BBR_STATE_PROBE_BW_REFILL: + if (bbr->round_start) { + bbr->bw_probe_samples = 1; + bbr_start_probe_bw_up(bbr, cstat, ts); + } + + break; + case NGTCP2_BBR_STATE_PROBE_BW_UP: + if (bbr_has_elapsed_in_phase(bbr, bbr->min_rtt, ts) && + cstat->bytes_in_flight > bbr_inflight(bbr, cstat, bbr->max_bw, 125)) { + bbr_start_probe_bw_down(bbr, ts); + } + + break; + default: + break; + } +} + +static int bbr_check_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, bbr->max_bw, 100)) { + return 1; + } + + return 0; +} + +static int bbr_has_elapsed_in_phase(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) { + uint64_t headroom; + uint64_t mpcwnd; + if (bbr->inflight_hi == UINT64_MAX) { + return UINT64_MAX; + } + + headroom = ngtcp2_max(cstat->max_tx_udp_payload_size, + bbr->inflight_hi * NGTCP2_BBR_HEADROOM_NUMER / + NGTCP2_BBR_HEADROOM_DENOM); + mpcwnd = min_pipe_cwnd(cstat->max_tx_udp_payload_size); + + if (bbr->inflight_hi > headroom) { + return ngtcp2_max(bbr->inflight_hi - headroom, mpcwnd); + } + + return mpcwnd; +} + +static void bbr_raise_inflight_hi_slope(ngtcp2_cc_bbr *bbr, + ngtcp2_conn_stat *cstat) { + uint64_t growth_this_round = cstat->max_tx_udp_payload_size + << bbr->bw_probe_up_rounds; + + bbr->bw_probe_up_rounds = ngtcp2_min(bbr->bw_probe_up_rounds + 1, 30); + bbr->probe_up_cnt = ngtcp2_max(cstat->cwnd / growth_this_round, 1) * + cstat->max_tx_udp_payload_size; +} + +static void bbr_probe_inflight_hi_upward(ngtcp2_cc_bbr *bbr, + ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack) { + uint64_t delta; + + if (!bbr->rst->is_cwnd_limited || cstat->cwnd < bbr->inflight_hi) { + return; + } + + bbr->bw_probe_up_acks += ack->bytes_delivered; + + if (bbr->bw_probe_up_acks >= bbr->probe_up_cnt) { + delta = bbr->bw_probe_up_acks / bbr->probe_up_cnt; + bbr->bw_probe_up_acks -= delta * bbr->probe_up_cnt; + bbr->inflight_hi += delta * cstat->max_tx_udp_payload_size; + } + + if (bbr->round_start) { + bbr_raise_inflight_hi_slope(bbr, cstat); + } +} + +static void bbr_adapt_upper_bounds(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, ngtcp2_tstamp ts) { + if (bbr->ack_phase == NGTCP2_BBR_ACK_PHASE_ACKS_PROBE_STARTING && + bbr->round_start) { + bbr->ack_phase = NGTCP2_BBR_ACK_PHASE_ACKS_PROBE_FEEDBACK; + } + + if (bbr->ack_phase == NGTCP2_BBR_ACK_PHASE_ACKS_PROBE_STOPPING && + bbr->round_start) { + if (bbr_is_in_probe_bw_state(bbr) && !bbr->rst->rs.is_app_limited) { + bbr_advance_max_bw_filter(bbr); + } + } + + if (!bbr_check_inflight_too_high(bbr, cstat, ts)) { + /* bbr->bw_hi never be updated */ + if (bbr->inflight_hi == UINT64_MAX /* || bbr->bw_hi == UINT64_MAX */) { + return; + } + + if (bbr->rst->rs.tx_in_flight > bbr->inflight_hi) { + bbr->inflight_hi = bbr->rst->rs.tx_in_flight; + } + + if (cstat->delivery_rate_sec > bbr->bw_hi) { + bbr->bw_hi = cstat->delivery_rate_sec; + } + + if (bbr->state == NGTCP2_BBR_STATE_PROBE_BW_UP) { + bbr_probe_inflight_hi_upward(bbr, cstat, ack); + } + } +} + +static int bbr_check_time_to_probe_bw(ngtcp2_cc_bbr *bbr, + 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)) { + bbr_start_probe_bw_refill(bbr); + + return 1; + } + + return 0; +} + +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_tstamp)(NGTCP2_SECONDS * rand / 255); +} + +static int bbr_is_reno_coexistence_probe_time(ngtcp2_cc_bbr *bbr, + 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(reno_rounds, 63); +} + +static uint64_t bbr_target_inflight(ngtcp2_cc_bbr *bbr, + ngtcp2_conn_stat *cstat) { + uint64_t bdp = bbr_inflight(bbr, cstat, bbr->bw, 100); + + return ngtcp2_min(bdp, cstat->cwnd); +} + +static int bbr_check_inflight_too_high(ngtcp2_cc_bbr *bbr, + ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + if (is_inflight_too_high(&bbr->rst->rs)) { + if (bbr->bw_probe_samples) { + bbr_handle_inflight_too_high(bbr, cstat, &bbr->rst->rs, ts); + } + + return 1; + } + + return 0; +} + +static int is_inflight_too_high(const ngtcp2_rs *rs) { + 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_rs *rs, + ngtcp2_tstamp ts) { + bbr->bw_probe_samples = 0; + + if (!rs->is_app_limited) { + bbr->inflight_hi = ngtcp2_max( + rs->tx_in_flight, bbr_target_inflight(bbr, cstat) * + NGTCP2_BBR_BETA_NUMER / NGTCP2_BBR_BETA_DENOM); + } + + if (bbr->state == NGTCP2_BBR_STATE_PROBE_BW_UP) { + bbr_start_probe_bw_down(bbr, ts); + } +} + +static void bbr_handle_lost_packet(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_pkt *pkt, ngtcp2_tstamp ts) { + ngtcp2_rs rs = {0}; + + if (!bbr->bw_probe_samples) { + return; + } + + rs.tx_in_flight = pkt->tx_in_flight; + /* bbr->rst->lost is not incremented for pkt yet */ + rs.lost = bbr->rst->lost + pkt->pktlen - pkt->lost; + rs.is_app_limited = pkt->is_app_limited; + + if (is_inflight_too_high(&rs)) { + rs.tx_in_flight = bbr_inflight_hi_from_lost_packet(bbr, &rs, pkt); + + bbr_handle_inflight_too_high(bbr, cstat, &rs, ts); + } +} + +static uint64_t bbr_inflight_hi_from_lost_packet(ngtcp2_cc_bbr *bbr, + const ngtcp2_rs *rs, + const ngtcp2_cc_pkt *pkt) { + uint64_t inflight_prev, lost_prev, lost_prefix; + (void)bbr; + + assert(rs->tx_in_flight >= pkt->pktlen); + + inflight_prev = rs->tx_in_flight - pkt->pktlen; + + assert(rs->lost >= pkt->pktlen); + + lost_prev = rs->lost - pkt->pktlen; + + if (inflight_prev * NGTCP2_BBR_LOSS_THRESH_NUMER < + lost_prev * NGTCP2_BBR_LOSS_THRESH_DENOM) { + return inflight_prev; + } + + lost_prefix = (inflight_prev * NGTCP2_BBR_LOSS_THRESH_NUMER - + lost_prev * NGTCP2_BBR_LOSS_THRESH_DENOM) / + (NGTCP2_BBR_LOSS_THRESH_DENOM - NGTCP2_BBR_LOSS_THRESH_NUMER); + + return inflight_prev + lost_prefix; +} + +static void bbr_update_min_rtt(ngtcp2_cc_bbr *bbr, const ngtcp2_cc_ack *ack, + ngtcp2_tstamp ts) { + int min_rtt_expired; + + bbr->probe_rtt_expired = + ts > bbr->probe_rtt_min_stamp + NGTCP2_BBR_PROBE_RTT_INTERVAL; + + if (ack->rtt != UINT64_MAX && + (ack->rtt < bbr->probe_rtt_min_delay || bbr->probe_rtt_expired)) { + bbr->probe_rtt_min_delay = ack->rtt; + bbr->probe_rtt_min_stamp = ts; + } + + min_rtt_expired = ts > bbr->min_rtt_stamp + NGTCP2_BBR_MIN_RTT_FILTERLEN; + + if (bbr->probe_rtt_min_delay < bbr->min_rtt || min_rtt_expired) { + 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); + } +} + +static void bbr_check_probe_rtt(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + if (bbr->state != NGTCP2_BBR_STATE_PROBE_RTT && bbr->probe_rtt_expired && + !bbr->idle_restart) { + bbr_enter_probe_rtt(bbr); + bbr_save_cwnd(bbr, cstat); + + bbr->probe_rtt_done_stamp = UINT64_MAX; + bbr->ack_phase = NGTCP2_BBR_ACK_PHASE_ACKS_PROBE_STOPPING; + + bbr_start_round(bbr); + } + + if (bbr->state == NGTCP2_BBR_STATE_PROBE_RTT) { + bbr_handle_probe_rtt(bbr, cstat, ts); + } + + if (bbr->rst->rs.delivered) { + bbr->idle_restart = 0; + } +} + +static void bbr_enter_probe_rtt(ngtcp2_cc_bbr *bbr) { + ngtcp2_log_info(bbr->cc.log, NGTCP2_LOG_EVENT_CCA, "bbr enter ProbeRTT"); + + bbr->state = NGTCP2_BBR_STATE_PROBE_RTT; + bbr->pacing_gain_h = 100; + bbr->cwnd_gain_h = NGTCP2_BBR_PROBE_RTT_CWND_GAIN_H; +} + +static void bbr_handle_probe_rtt(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + bbr_mark_connection_app_limited(bbr, cstat); + + if (bbr->probe_rtt_done_stamp == UINT64_MAX && + cstat->bytes_in_flight <= bbr_probe_rtt_cwnd(bbr, cstat)) { + bbr->probe_rtt_done_stamp = ts + NGTCP2_BBR_PROBE_RTT_DURATION; + bbr->probe_rtt_round_done = 0; + + bbr_start_round(bbr); + + return; + } + + if (bbr->probe_rtt_done_stamp != UINT64_MAX) { + if (bbr->round_start) { + bbr->probe_rtt_round_done = 1; + } + + if (bbr->probe_rtt_round_done) { + bbr_check_probe_rtt_done(bbr, cstat, ts); + } + } +} + +static void bbr_check_probe_rtt_done(ngtcp2_cc_bbr *bbr, + ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + if (bbr->probe_rtt_done_stamp != UINT64_MAX && + ts > bbr->probe_rtt_done_stamp) { + bbr->probe_rtt_min_stamp = ts; + bbr_restore_cwnd(bbr, cstat); + bbr_exit_probe_rtt(bbr, ts); + } +} + +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; + } +} + +static void bbr_exit_probe_rtt(ngtcp2_cc_bbr *bbr, ngtcp2_tstamp ts) { + bbr_reset_lower_bounds(bbr); + + if (bbr->filled_pipe) { + bbr_start_probe_bw_down(bbr, ts); + bbr_start_probe_bw_cruise(bbr); + } else { + bbr_enter_startup(bbr); + } +} + +static void bbr_handle_restart_from_idle(ngtcp2_cc_bbr *bbr, + ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + if (cstat->bytes_in_flight == 0 && bbr->rst->app_limited) { + ngtcp2_log_info(bbr->cc.log, NGTCP2_LOG_EVENT_CCA, "bbr restart from idle"); + + bbr->idle_restart = 1; + bbr->extra_acked_interval_start = ts; + + if (bbr_is_in_probe_bw_state(bbr)) { + bbr_set_pacing_rate_with_gain(bbr, cstat, 100); + } else if (bbr->state == NGTCP2_BBR_STATE_PROBE_RTT) { + bbr_check_probe_rtt_done(bbr, cstat, ts); + } + } +} + +static uint64_t bbr_bdp_multiple(ngtcp2_cc_bbr *bbr, uint64_t bw, + uint64_t gain_h) { + uint64_t bdp; + + if (bbr->min_rtt == UINT64_MAX) { + return bbr->initial_cwnd; + } + + bdp = bw * bbr->min_rtt / NGTCP2_SECONDS; + + return (uint64_t)(bdp * gain_h / 100); +} + +static uint64_t min_pipe_cwnd(size_t max_udp_payload_size) { + return max_udp_payload_size * 4; +} + +static uint64_t bbr_quantization_budget(ngtcp2_cc_bbr *bbr, + ngtcp2_conn_stat *cstat, + uint64_t inflight) { + bbr_update_offload_budget(bbr, cstat); + + inflight = ngtcp2_max(inflight, bbr->offload_budget); + inflight = + ngtcp2_max(inflight, min_pipe_cwnd(cstat->max_tx_udp_payload_size)); + + if (bbr->state == NGTCP2_BBR_STATE_PROBE_BW_UP) { + inflight += 2 * cstat->max_tx_udp_payload_size; + } + + return inflight; +} + +static uint64_t bbr_inflight(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, + uint64_t bw, uint64_t gain_h) { + uint64_t inflight = bbr_bdp_multiple(bbr, bw, gain_h); + + return bbr_quantization_budget(bbr, cstat, inflight); +} + +static void bbr_update_max_inflight(ngtcp2_cc_bbr *bbr, + ngtcp2_conn_stat *cstat) { + uint64_t inflight; + + /* Not documented */ + /* bbr_update_aggregation_budget(bbr); */ + + inflight = + bbr_bdp_multiple(bbr, bbr->bw, bbr->cwnd_gain_h) + bbr->extra_acked; + bbr->max_inflight = bbr_quantization_budget(bbr, cstat, inflight); +} + +static void bbr_update_offload_budget(ngtcp2_cc_bbr *bbr, + ngtcp2_conn_stat *cstat) { + bbr->offload_budget = 3 * cstat->send_quantum; +} + +static void bbr_advance_max_bw_filter(ngtcp2_cc_bbr *bbr) { + ++bbr->cycle_count; +} + +static void bbr_modulate_cwnd_for_recovery(ngtcp2_cc_bbr *bbr, + ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack) { + if (ack->bytes_lost > 0) { + if (cstat->cwnd > ack->bytes_lost) { + cstat->cwnd -= ack->bytes_lost; + cstat->cwnd = ngtcp2_max(cstat->cwnd, 2 * cstat->max_tx_udp_payload_size); + } else { + cstat->cwnd = 2 * cstat->max_tx_udp_payload_size; + } + } + + if (bbr->packet_conservation) { + cstat->cwnd = + ngtcp2_max(cstat->cwnd, cstat->bytes_in_flight + ack->bytes_delivered); + } +} + +static void bbr_save_cwnd(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat) { + if (!bbr->in_loss_recovery && bbr->state != NGTCP2_BBR_STATE_PROBE_RTT) { + bbr->prior_cwnd = cstat->cwnd; + return; + } + + bbr->prior_cwnd = ngtcp2_max(bbr->prior_cwnd, cstat->cwnd); +} + +static void bbr_restore_cwnd(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat) { + cstat->cwnd = ngtcp2_max(cstat->cwnd, bbr->prior_cwnd); +} + +static uint64_t bbr_probe_rtt_cwnd(ngtcp2_cc_bbr *bbr, + ngtcp2_conn_stat *cstat) { + uint64_t probe_rtt_cwnd = + bbr_bdp_multiple(bbr, bbr->bw, NGTCP2_BBR_PROBE_RTT_CWND_GAIN_H); + uint64_t mpcwnd = min_pipe_cwnd(cstat->max_tx_udp_payload_size); + + return ngtcp2_max(probe_rtt_cwnd, mpcwnd); +} + +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(cstat->cwnd, probe_rtt_cwnd); + } +} + +static void bbr_set_cwnd(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack) { + uint64_t mpcwnd; + + bbr_update_max_inflight(bbr, cstat); + bbr_modulate_cwnd_for_recovery(bbr, cstat, ack); + + if (!bbr->packet_conservation) { + if (bbr->filled_pipe) { + cstat->cwnd += ack->bytes_delivered; + cstat->cwnd = ngtcp2_min(cstat->cwnd, bbr->max_inflight); + } else if (cstat->cwnd < bbr->max_inflight || + bbr->rst->delivered < bbr->initial_cwnd) { + cstat->cwnd += ack->bytes_delivered; + } + + mpcwnd = min_pipe_cwnd(cstat->max_tx_udp_payload_size); + cstat->cwnd = ngtcp2_max(cstat->cwnd, mpcwnd); + } + + bbr_bound_cwnd_for_probe_rtt(bbr, cstat); + bbr_bound_cwnd_for_model(bbr, cstat); +} + +static void bbr_bound_cwnd_for_model(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); + + if (bbr_is_in_probe_bw_state(bbr) && + bbr->state != NGTCP2_BBR_STATE_PROBE_BW_CRUISE) { + cap = bbr->inflight_hi; + } else if (bbr->state == NGTCP2_BBR_STATE_PROBE_RTT || + bbr->state == NGTCP2_BBR_STATE_PROBE_BW_CRUISE) { + cap = bbr_inflight_with_headroom(bbr, cstat); + } + + cap = ngtcp2_min(cap, bbr->inflight_lo); + cap = ngtcp2_max(cap, mpcwnd); + + cstat->cwnd = ngtcp2_min(cstat->cwnd, cap); +} + +static void bbr_set_send_quantum(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat) { + size_t floor, send_quantum; + (void)bbr; + + if (cstat->pacing_interval > (NGTCP2_SECONDS * 8 * 10 / 12) >> 20) { + floor = cstat->max_tx_udp_payload_size; + } else { + floor = 2 * cstat->max_tx_udp_payload_size; + } + + if (cstat->pacing_interval) { + send_quantum = (size_t)(NGTCP2_MILLISECONDS / cstat->pacing_interval); + send_quantum = ngtcp2_min(send_quantum, 64 * 1024); + } else { + send_quantum = 64 * 1024; + } + + cstat->send_quantum = ngtcp2_max(send_quantum, floor); +} + +static int in_congestion_recovery(const ngtcp2_conn_stat *cstat, + ngtcp2_tstamp sent_time) { + return cstat->congestion_recovery_start_ts != UINT64_MAX && + sent_time <= cstat->congestion_recovery_start_ts; +} + +static void bbr_handle_recovery(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, ngtcp2_tstamp ts) { + if (bbr->in_loss_recovery) { + if (ts - cstat->congestion_recovery_start_ts >= cstat->smoothed_rtt) { + bbr->packet_conservation = 0; + } + + if (ack->largest_pkt_sent_ts != UINT64_MAX && + !in_congestion_recovery(cstat, ack->largest_pkt_sent_ts)) { + bbr->in_loss_recovery = 0; + bbr->packet_conservation = 0; + bbr_restore_cwnd(bbr, cstat); + } + + return; + } + + if (bbr->congestion_recovery_start_ts != UINT64_MAX) { + bbr->in_loss_recovery = 1; + bbr_save_cwnd(bbr, cstat); + cstat->cwnd = + cstat->bytes_in_flight + + ngtcp2_max(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; + bbr->packet_conservation = 1; + bbr->congestion_recovery_next_round_delivered = bbr->rst->delivered; + bbr->prior_inflight_hi = bbr->inflight_hi; + bbr->prior_inflight_lo = bbr->inflight_lo; + bbr->prior_bw_lo = bbr->bw_lo; + } +} + +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); +} + +static void bbr_cc_congestion_event(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp sent_ts, ngtcp2_tstamp ts) { + ngtcp2_cc_bbr *bbr = ngtcp2_struct_of(cc, ngtcp2_cc_bbr, cc); + + if (bbr->in_loss_recovery || + bbr->congestion_recovery_start_ts != UINT64_MAX || + in_congestion_recovery(cstat, sent_ts)) { + return; + } + + bbr->congestion_recovery_start_ts = ts; +} + +static void bbr_cc_on_spurious_congestion(ngtcp2_cc *cc, + ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + 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->packet_conservation = 0; + bbr_restore_cwnd(bbr, cstat); + bbr->full_bw_count = 0; + bbr->loss_in_round = 0; + bbr->inflight_lo = ngtcp2_max(bbr->inflight_lo, bbr->prior_inflight_lo); + bbr->inflight_hi = ngtcp2_max(bbr->inflight_hi, bbr->prior_inflight_hi); + bbr->bw_lo = ngtcp2_max(bbr->bw_lo, bbr->prior_bw_lo); + } +} + +static void bbr_cc_on_persistent_congestion(ngtcp2_cc *cc, + ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + ngtcp2_cc_bbr *bbr = ngtcp2_struct_of(cc, ngtcp2_cc_bbr, cc); + (void)ts; + + cstat->congestion_recovery_start_ts = UINT64_MAX; + bbr->congestion_recovery_start_ts = UINT64_MAX; + bbr->in_loss_recovery = 0; + bbr->packet_conservation = 0; + + bbr_save_cwnd(bbr, cstat); + cstat->cwnd = cstat->bytes_in_flight + cstat->max_tx_udp_payload_size; + cstat->cwnd = + ngtcp2_max(cstat->cwnd, min_pipe_cwnd(cstat->max_tx_udp_payload_size)); +} + +static void bbr_cc_on_ack_recv(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, ngtcp2_tstamp ts) { + ngtcp2_cc_bbr *bbr = ngtcp2_struct_of(cc, ngtcp2_cc_bbr, cc); + + bbr_handle_recovery(bbr, cstat, ack, ts); + bbr_update_on_ack(bbr, cstat, ack, ts); +} + +static void bbr_cc_on_pkt_sent(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_pkt *pkt) { + ngtcp2_cc_bbr *bbr = ngtcp2_struct_of(cc, ngtcp2_cc_bbr, cc); + + bbr_on_transmit(bbr, cstat, pkt->sent_ts); +} + +static void bbr_cc_reset(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + ngtcp2_cc_bbr *bbr = ngtcp2_struct_of(cc, ngtcp2_cc_bbr, cc); + + bbr_on_init(bbr, cstat, ts); +} + +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; + + bbr_on_init(bbr, cstat, initial_ts); +} diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_bbr.h b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_bbr.h new file mode 100644 index 0000000..0017be3 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_bbr.h @@ -0,0 +1,146 @@ +/* + * ngtcp2 + * + * Copyright (c) 2021 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_BBR_H +#define NGTCP2_BBR_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_cc.h" +#include "ngtcp2_window_filter.h" + +typedef struct ngtcp2_rst ngtcp2_rst; + +typedef enum ngtcp2_bbr_state { + NGTCP2_BBR_STATE_STARTUP, + NGTCP2_BBR_STATE_DRAIN, + NGTCP2_BBR_STATE_PROBE_BW_DOWN, + NGTCP2_BBR_STATE_PROBE_BW_CRUISE, + NGTCP2_BBR_STATE_PROBE_BW_REFILL, + NGTCP2_BBR_STATE_PROBE_BW_UP, + NGTCP2_BBR_STATE_PROBE_RTT, +} ngtcp2_bbr_state; + +typedef enum ngtcp2_bbr_ack_phase { + NGTCP2_BBR_ACK_PHASE_ACKS_PROBE_STARTING, + NGTCP2_BBR_ACK_PHASE_ACKS_PROBE_STOPPING, + NGTCP2_BBR_ACK_PHASE_ACKS_PROBE_FEEDBACK, + NGTCP2_BBR_ACK_PHASE_ACKS_REFILLING, +} ngtcp2_bbr_ack_phase; + +/* + * ngtcp2_cc_bbr is BBR v2 congestion controller, described in + * https://datatracker.ietf.org/doc/html/draft-cardwell-iccrg-bbr-congestion-control-01 + */ +typedef struct ngtcp2_cc_bbr { + ngtcp2_cc cc; + + uint64_t initial_cwnd; + ngtcp2_rst *rst; + ngtcp2_rand rand; + ngtcp2_rand_ctx rand_ctx; + + /* max_bw_filter for tracking the maximum recent delivery rate + samples for estimating max_bw. */ + ngtcp2_window_filter max_bw_filter; + + ngtcp2_window_filter extra_acked_filter; + + ngtcp2_duration min_rtt; + ngtcp2_tstamp min_rtt_stamp; + ngtcp2_tstamp probe_rtt_done_stamp; + int probe_rtt_round_done; + uint64_t prior_cwnd; + int idle_restart; + ngtcp2_tstamp extra_acked_interval_start; + uint64_t extra_acked_delivered; + + /* Congestion signals */ + int loss_in_round; + uint64_t bw_latest; + uint64_t inflight_latest; + + /* Lower bounds */ + uint64_t bw_lo; + uint64_t inflight_lo; + + /* Round counting */ + uint64_t next_round_delivered; + int round_start; + uint64_t round_count; + + /* Full pipe */ + int filled_pipe; + uint64_t full_bw; + size_t full_bw_count; + + /* Pacing rate */ + uint64_t pacing_gain_h; + + ngtcp2_bbr_state state; + uint64_t cwnd_gain_h; + + int loss_round_start; + uint64_t loss_round_delivered; + uint64_t rounds_since_bw_probe; + uint64_t max_bw; + uint64_t bw; + uint64_t cycle_count; + uint64_t extra_acked; + uint64_t bytes_lost_in_round; + size_t loss_events_in_round; + uint64_t offload_budget; + uint64_t probe_up_cnt; + 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_hi; + uint64_t bw_hi; + int probe_rtt_expired; + ngtcp2_duration probe_rtt_min_delay; + ngtcp2_tstamp probe_rtt_min_stamp; + int in_loss_recovery; + int packet_conservation; + uint64_t max_inflight; + ngtcp2_tstamp congestion_recovery_start_ts; + uint64_t congestion_recovery_next_round_delivered; + + uint64_t prior_inflight_lo; + uint64_t prior_inflight_hi; + uint64_t prior_bw_lo; +} 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); + +#endif /* NGTCP2_BBR_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_buf.c b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_buf.c new file mode 100644 index 0000000..75326d6 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_buf.c @@ -0,0 +1,56 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 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_buf.h" +#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; +} + +void ngtcp2_buf_reset(ngtcp2_buf *buf) { buf->pos = buf->last = buf->begin; } + +size_t ngtcp2_buf_cap(const ngtcp2_buf *buf) { + return (size_t)(buf->end - buf->begin); +} + +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); + if (*pbufchain == NULL) { + return NGTCP2_ERR_NOMEM; + } + + (*pbufchain)->next = NULL; + + ngtcp2_buf_init(&(*pbufchain)->buf, + (uint8_t *)(*pbufchain) + sizeof(ngtcp2_buf_chain), len); + + return 0; +} + +void ngtcp2_buf_chain_del(ngtcp2_buf_chain *bufchain, const ngtcp2_mem *mem) { + ngtcp2_mem_free(mem, bufchain); +} diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_buf.h b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_buf.h new file mode 100644 index 0000000..107d413 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_buf.h @@ -0,0 +1,108 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 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_BUF_H +#define NGTCP2_BUF_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +typedef struct ngtcp2_buf { + /* begin points to the beginning of the buffer. */ + uint8_t *begin; + /* end points to the one beyond of the last byte of the buffer */ + uint8_t *end; + /* pos pointers to the start of data. Typically, this points to the + point that next data should be read. Initially, it points to + |begin|. */ + uint8_t *pos; + /* last points to the one beyond of the last data of the buffer. + Typically, new data is written at this point. Initially, it + points to |begin|. */ + uint8_t *last; +} ngtcp2_buf; + +/* + * ngtcp2_buf_init initializes |buf| with the given buffer. + */ +void ngtcp2_buf_init(ngtcp2_buf *buf, uint8_t *begin, size_t len); + +/* + * ngtcp2_buf_reset resets pos and last fields to match begin field to + * make ngtcp2_buf_len(buf) return 0. + */ +void ngtcp2_buf_reset(ngtcp2_buf *buf); + +/* + * ngtcp2_buf_left returns the number of additional bytes which can be + * 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) + +/* + * ngtcp2_buf_len returns the number of bytes left to read. In other + * words, it returns buf->last - buf->pos. + */ +#define ngtcp2_buf_len(BUF) (size_t)((BUF)->last - (BUF)->pos) + +/* + * ngtcp2_buf_cap returns the capacity of the buffer. In other words, + * it returns buf->end - buf->begin. + */ +size_t ngtcp2_buf_cap(const ngtcp2_buf *buf); + +/* + * ngtcp2_buf_chain is a linked list of ngtcp2_buf. + */ +typedef struct ngtcp2_buf_chain ngtcp2_buf_chain; + +struct ngtcp2_buf_chain { + ngtcp2_buf_chain *next; + ngtcp2_buf buf; +}; + +/* + * ngtcp2_buf_chain_new creates new ngtcp2_buf_chain and initializes + * the internal buffer with |len| bytes space. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory + */ +int ngtcp2_buf_chain_new(ngtcp2_buf_chain **pbufchain, size_t len, + const ngtcp2_mem *mem); + +/* + * ngtcp2_buf_chain_del deletes the resource allocated by |bufchain|. + * It also deletes the memory pointed by |bufchain|. + */ +void ngtcp2_buf_chain_del(ngtcp2_buf_chain *bufchain, const ngtcp2_mem *mem); + +#endif /* NGTCP2_BUF_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_cc.c b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_cc.c new file mode 100644 index 0000000..ef311ff --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_cc.c @@ -0,0 +1,593 @@ +/* + * ngtcp2 + * + * Copyright (c) 2018 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_cc.h" + +#include <assert.h> +#include <string.h> + +#if defined(_MSC_VER) +# include <intrin.h> +#endif + +#include "ngtcp2_log.h" +#include "ngtcp2_macro.h" +#include "ngtcp2_mem.h" +#include "ngtcp2_rcvry.h" +#include "ngtcp2_conn_stat.h" +#include "ngtcp2_unreachable.h" + +/* NGTCP2_CC_DELIVERY_RATE_SEC_FILTERLEN is the window length of + delivery rate filter driven by ACK clocking. */ +#define NGTCP2_CC_DELIVERY_RATE_SEC_FILTERLEN 10 + +uint64_t ngtcp2_cc_compute_initcwnd(size_t max_udp_payload_size) { + uint64_t n = 2 * max_udp_payload_size; + n = ngtcp2_max(n, 14720); + return ngtcp2_min(10 * max_udp_payload_size, n); +} + +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, + uint64_t tx_in_flight, int is_app_limited) { + pkt->pkt_num = pkt_num; + pkt->pktlen = pktlen; + pkt->pktns_id = pktns_id; + pkt->sent_ts = sent_ts; + pkt->lost = lost; + pkt->tx_in_flight = tx_in_flight; + pkt->is_app_limited = is_app_limited; + + return pkt; +} + +static void reno_cc_reset(ngtcp2_cc_reno *reno) { + ngtcp2_window_filter_init(&reno->delivery_rate_sec_filter, + NGTCP2_CC_DELIVERY_RATE_SEC_FILTERLEN); + reno->ack_count = 0; + reno->target_cwnd = 0; + reno->pending_add = 0; +} + +void ngtcp2_cc_reno_init(ngtcp2_cc_reno *reno, ngtcp2_log *log) { + memset(reno, 0, sizeof(*reno)); + + 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.on_ack_recv = ngtcp2_cc_reno_cc_on_ack_recv; + reno->cc.reset = ngtcp2_cc_reno_cc_reset; + + reno_cc_reset(reno); +} + +static int in_congestion_recovery(const ngtcp2_conn_stat *cstat, + ngtcp2_tstamp sent_time) { + return cstat->congestion_recovery_start_ts != UINT64_MAX && + sent_time <= cstat->congestion_recovery_start_ts; +} + +void ngtcp2_cc_reno_cc_on_pkt_acked(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_pkt *pkt, + ngtcp2_tstamp ts) { + ngtcp2_cc_reno *reno = ngtcp2_struct_of(cc, ngtcp2_cc_reno, cc); + uint64_t m; + (void)ts; + + if (in_congestion_recovery(cstat, pkt->sent_ts)) { + return; + } + + if (reno->target_cwnd && reno->target_cwnd < cstat->cwnd) { + return; + } + + 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); + return; + } + + m = cstat->max_tx_udp_payload_size * pkt->pktlen + reno->pending_add; + reno->pending_add = m % cstat->cwnd; + + cstat->cwnd += m / cstat->cwnd; +} + +void ngtcp2_cc_reno_cc_congestion_event(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp sent_ts, + ngtcp2_tstamp ts) { + ngtcp2_cc_reno *reno = ngtcp2_struct_of(cc, ngtcp2_cc_reno, cc); + uint64_t min_cwnd; + + if (in_congestion_recovery(cstat, sent_ts)) { + return; + } + + cstat->congestion_recovery_start_ts = ts; + cstat->cwnd >>= NGTCP2_LOSS_REDUCTION_FACTOR_BITS; + min_cwnd = 2 * cstat->max_tx_udp_payload_size; + cstat->cwnd = ngtcp2_max(cstat->cwnd, min_cwnd); + cstat->ssthresh = cstat->cwnd; + + reno->pending_add = 0; + + ngtcp2_log_info(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, + ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + (void)cc; + (void)ts; + + cstat->cwnd = 2 * cstat->max_tx_udp_payload_size; + cstat->congestion_recovery_start_ts = UINT64_MAX; +} + +void ngtcp2_cc_reno_cc_on_ack_recv(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, ngtcp2_tstamp ts) { + ngtcp2_cc_reno *reno = ngtcp2_struct_of(cc, ngtcp2_cc_reno, cc); + uint64_t target_cwnd, initcwnd; + uint64_t max_delivery_rate_sec; + (void)ack; + (void)ts; + + ++reno->ack_count; + + ngtcp2_window_filter_update(&reno->delivery_rate_sec_filter, + cstat->delivery_rate_sec, reno->ack_count); + + max_delivery_rate_sec = + ngtcp2_window_filter_get_best(&reno->delivery_rate_sec_filter); + + if (cstat->min_rtt != UINT64_MAX && max_delivery_rate_sec) { + target_cwnd = max_delivery_rate_sec * cstat->smoothed_rtt / NGTCP2_SECONDS; + initcwnd = ngtcp2_cc_compute_initcwnd(cstat->max_tx_udp_payload_size); + reno->target_cwnd = ngtcp2_max(initcwnd, target_cwnd) * 289 / 100; + + ngtcp2_log_info(reno->cc.log, NGTCP2_LOG_EVENT_CCA, + "target_cwnd=%" PRIu64 " max_delivery_rate_sec=%" PRIu64 + " smoothed_rtt=%" PRIu64, + reno->target_cwnd, max_delivery_rate_sec, + cstat->smoothed_rtt); + } +} + +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); +} + +static void cubic_cc_reset(ngtcp2_cc_cubic *cubic) { + ngtcp2_window_filter_init(&cubic->delivery_rate_sec_filter, + NGTCP2_CC_DELIVERY_RATE_SEC_FILTERLEN); + cubic->ack_count = 0; + cubic->target_cwnd = 0; + cubic->w_last_max = 0; + cubic->w_tcp = 0; + cubic->origin_point = 0; + cubic->epoch_start = UINT64_MAX; + cubic->k = 0; + + cubic->prior.cwnd = 0; + cubic->prior.ssthresh = 0; + cubic->prior.w_last_max = 0; + cubic->prior.w_tcp = 0; + cubic->prior.origin_point = 0; + cubic->prior.epoch_start = UINT64_MAX; + cubic->prior.k = 0; + + cubic->rtt_sample_count = 0; + cubic->current_round_min_rtt = UINT64_MAX; + cubic->last_round_min_rtt = UINT64_MAX; + cubic->window_end = -1; +} + +void ngtcp2_cc_cubic_init(ngtcp2_cc_cubic *cubic, ngtcp2_log *log) { + memset(cubic, 0, sizeof(*cubic)); + + cubic->cc.log = log; + cubic->cc.on_pkt_acked = ngtcp2_cc_cubic_cc_on_pkt_acked; + 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.on_ack_recv = ngtcp2_cc_cubic_cc_on_ack_recv; + cubic->cc.on_pkt_sent = ngtcp2_cc_cubic_cc_on_pkt_sent; + cubic->cc.new_rtt_sample = ngtcp2_cc_cubic_cc_new_rtt_sample; + cubic->cc.reset = ngtcp2_cc_cubic_cc_reset; + cubic->cc.event = ngtcp2_cc_cubic_cc_event; + + cubic_cc_reset(cubic); +} + +uint64_t ngtcp2_cbrt(uint64_t n) { + int d; + uint64_t a; + + if (n == 0) { + return 0; + } + +#if defined(_MSC_VER) + { + unsigned long index; +# if defined(_WIN64) + if (_BitScanReverse64(&index, n)) { + d = 61 - index; + } else { + ngtcp2_unreachable(); + } +# else /* !defined(_WIN64) */ + if (_BitScanReverse(&index, (unsigned int)(n >> 32))) { + d = 31 - index; + } else { + d = 32 + 31 - _BitScanReverse(&index, (unsigned int)n); + } +# endif /* !defined(_WIN64) */ + } +#else /* !defined(_MSC_VER) */ + d = __builtin_clzll(n); +#endif /* !defined(_MSC_VER) */ + a = 1ULL << ((64 - d) / 3 + 1); + + for (; a * a * a > n;) { + a = (2 * a + n / a / a) / 3; + } + return a; +} + +/* HyStart++ constants */ +#define NGTCP2_HS_MIN_SSTHRESH 16 +#define NGTCP2_HS_N_RTT_SAMPLE 8 +#define NGTCP2_HS_MIN_ETA (4 * NGTCP2_MILLISECONDS) +#define NGTCP2_HS_MAX_ETA (16 * NGTCP2_MILLISECONDS) + +void ngtcp2_cc_cubic_cc_on_pkt_acked(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_pkt *pkt, + ngtcp2_tstamp ts) { + ngtcp2_cc_cubic *cubic = ngtcp2_struct_of(cc, ngtcp2_cc_cubic, cc); + ngtcp2_duration t, eta; + uint64_t target, cwnd_thres; + uint64_t tx, kx, time_delta, delta; + uint64_t add, tcp_add; + uint64_t m; + + if (pkt->pktns_id == NGTCP2_PKTNS_ID_APPLICATION && cubic->window_end != -1 && + cubic->window_end <= pkt->pkt_num) { + cubic->window_end = -1; + } + + if (in_congestion_recovery(cstat, pkt->sent_ts)) { + return; + } + + if (cstat->cwnd < cstat->ssthresh) { + /* slow-start */ + if (cubic->target_cwnd == 0 || cubic->target_cwnd > cstat->cwnd) { + cstat->cwnd += pkt->pktlen; + } + + ngtcp2_log_info(cubic->cc.log, NGTCP2_LOG_EVENT_CCA, + "pkn=%" PRId64 " acked, slow start cwnd=%" PRIu64, + pkt->pkt_num, cstat->cwnd); + + if (cubic->last_round_min_rtt != UINT64_MAX && + cubic->current_round_min_rtt != UINT64_MAX && + cstat->cwnd >= + NGTCP2_HS_MIN_SSTHRESH * cstat->max_tx_udp_payload_size && + cubic->rtt_sample_count >= NGTCP2_HS_N_RTT_SAMPLE) { + eta = cubic->last_round_min_rtt / 8; + + if (eta < NGTCP2_HS_MIN_ETA) { + eta = NGTCP2_HS_MIN_ETA; + } else if (eta > NGTCP2_HS_MAX_ETA) { + eta = NGTCP2_HS_MAX_ETA; + } + + if (cubic->current_round_min_rtt >= cubic->last_round_min_rtt + eta) { + ngtcp2_log_info(cubic->cc.log, NGTCP2_LOG_EVENT_CCA, + "HyStart++ exit slow start"); + + cubic->w_last_max = cstat->cwnd; + cstat->ssthresh = cstat->cwnd; + } + } + + return; + } + + /* congestion avoidance */ + + if (cubic->epoch_start == UINT64_MAX) { + cubic->epoch_start = ts; + if (cstat->cwnd < cubic->w_last_max) { + cubic->k = ngtcp2_cbrt((cubic->w_last_max - cstat->cwnd) * 10 / 4 / + cstat->max_tx_udp_payload_size); + cubic->origin_point = cubic->w_last_max; + } else { + cubic->k = 0; + cubic->origin_point = cstat->cwnd; + } + + cubic->w_tcp = cstat->cwnd; + + ngtcp2_log_info(cubic->cc.log, NGTCP2_LOG_EVENT_CCA, + "cubic-ca epoch_start=%" PRIu64 " k=%" PRIu64 + " origin_point=%" PRIu64, + cubic->epoch_start, cubic->k, cubic->origin_point); + + cubic->pending_add = 0; + cubic->pending_w_add = 0; + } + + t = ts - cubic->epoch_start; + + tx = (t << 10) / NGTCP2_SECONDS; + kx = (cubic->k << 10); + + if (tx > kx) { + time_delta = tx - kx; + } else { + time_delta = kx - tx; + } + + delta = cstat->max_tx_udp_payload_size * + ((((time_delta * time_delta) >> 10) * time_delta) >> 10) * 4 / 10; + delta >>= 10; + + if (tx > kx) { + target = cubic->origin_point + delta; + } else { + target = cubic->origin_point - delta; + } + + cwnd_thres = + (target * (((t + cstat->smoothed_rtt) << 10) / NGTCP2_SECONDS)) >> 10; + if (cwnd_thres < cstat->cwnd) { + target = cstat->cwnd; + } else if (2 * cwnd_thres > 3 * cstat->cwnd) { + target = cstat->cwnd * 3 / 2; + } else { + target = cwnd_thres; + } + + if (target > cstat->cwnd) { + m = cubic->pending_add + + cstat->max_tx_udp_payload_size * (target - cstat->cwnd); + add = m / cstat->cwnd; + cubic->pending_add = m % cstat->cwnd; + } else { + m = cubic->pending_add + cstat->max_tx_udp_payload_size; + add = m / (100 * cstat->cwnd); + cubic->pending_add = m % (100 * cstat->cwnd); + } + + m = cubic->pending_w_add + cstat->max_tx_udp_payload_size * pkt->pktlen; + + cubic->w_tcp += m / cstat->cwnd; + cubic->pending_w_add = m % cstat->cwnd; + + if (cubic->w_tcp > cstat->cwnd) { + tcp_add = cstat->max_tx_udp_payload_size * (cubic->w_tcp - cstat->cwnd) / + cstat->cwnd; + if (tcp_add > add) { + add = tcp_add; + } + } + + if (cubic->target_cwnd == 0 || cubic->target_cwnd > cstat->cwnd) { + cstat->cwnd += add; + } + + ngtcp2_log_info(cubic->cc.log, NGTCP2_LOG_EVENT_CCA, + "pkn=%" PRId64 " acked, cubic-ca cwnd=%" PRIu64 " t=%" PRIu64 + " k=%" PRIi64 " time_delta=%" PRIu64 " delta=%" PRIu64 + " target=%" PRIu64 " w_tcp=%" PRIu64, + pkt->pkt_num, cstat->cwnd, t, cubic->k, time_delta >> 4, + delta, target, cubic->w_tcp); +} + +void ngtcp2_cc_cubic_cc_congestion_event(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp sent_ts, + ngtcp2_tstamp ts) { + ngtcp2_cc_cubic *cubic = ngtcp2_struct_of(cc, ngtcp2_cc_cubic, cc); + uint64_t min_cwnd; + + if (in_congestion_recovery(cstat, sent_ts)) { + return; + } + + if (cubic->prior.cwnd < cstat->cwnd) { + cubic->prior.cwnd = cstat->cwnd; + cubic->prior.ssthresh = cstat->ssthresh; + cubic->prior.w_last_max = cubic->w_last_max; + cubic->prior.w_tcp = cubic->w_tcp; + cubic->prior.origin_point = cubic->origin_point; + cubic->prior.epoch_start = cubic->epoch_start; + cubic->prior.k = cubic->k; + } + + cstat->congestion_recovery_start_ts = ts; + + cubic->epoch_start = UINT64_MAX; + if (cstat->cwnd < cubic->w_last_max) { + cubic->w_last_max = cstat->cwnd * 17 / 10 / 2; + } else { + cubic->w_last_max = cstat->cwnd; + } + + min_cwnd = 2 * cstat->max_tx_udp_payload_size; + cstat->ssthresh = cstat->cwnd * 7 / 10; + cstat->ssthresh = ngtcp2_max(cstat->ssthresh, min_cwnd); + cstat->cwnd = cstat->ssthresh; + + ngtcp2_log_info(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, + ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + ngtcp2_cc_cubic *cubic = ngtcp2_struct_of(cc, ngtcp2_cc_cubic, cc); + (void)ts; + + if (cstat->cwnd >= cubic->prior.cwnd) { + return; + } + + cstat->congestion_recovery_start_ts = UINT64_MAX; + + cstat->cwnd = cubic->prior.cwnd; + cstat->ssthresh = cubic->prior.ssthresh; + cubic->w_last_max = cubic->prior.w_last_max; + cubic->w_tcp = cubic->prior.w_tcp; + cubic->origin_point = cubic->prior.origin_point; + cubic->epoch_start = cubic->prior.epoch_start; + cubic->k = cubic->prior.k; + + cubic->prior.cwnd = 0; + cubic->prior.ssthresh = 0; + cubic->prior.w_last_max = 0; + cubic->prior.w_tcp = 0; + cubic->prior.origin_point = 0; + cubic->prior.epoch_start = UINT64_MAX; + cubic->prior.k = 0; + + ngtcp2_log_info(cubic->cc.log, NGTCP2_LOG_EVENT_CCA, + "spurious congestion is detected and congestion state is " + "restored cwnd=%" PRIu64, + cstat->cwnd); +} + +void ngtcp2_cc_cubic_cc_on_persistent_congestion(ngtcp2_cc *cc, + ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + (void)cc; + (void)ts; + + cstat->cwnd = 2 * cstat->max_tx_udp_payload_size; + cstat->congestion_recovery_start_ts = UINT64_MAX; +} + +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); + uint64_t target_cwnd, initcwnd; + uint64_t max_delivery_rate_sec; + (void)ack; + (void)ts; + + ++cubic->ack_count; + + ngtcp2_window_filter_update(&cubic->delivery_rate_sec_filter, + cstat->delivery_rate_sec, cubic->ack_count); + + max_delivery_rate_sec = + ngtcp2_window_filter_get_best(&cubic->delivery_rate_sec_filter); + + if (cstat->min_rtt != UINT64_MAX && max_delivery_rate_sec) { + target_cwnd = max_delivery_rate_sec * cstat->smoothed_rtt / NGTCP2_SECONDS; + initcwnd = ngtcp2_cc_compute_initcwnd(cstat->max_tx_udp_payload_size); + cubic->target_cwnd = ngtcp2_max(initcwnd, target_cwnd) * 289 / 100; + + ngtcp2_log_info(cubic->cc.log, NGTCP2_LOG_EVENT_CCA, + "target_cwnd=%" PRIu64 " max_delivery_rate_sec=%" PRIu64 + " smoothed_rtt=%" PRIu64, + cubic->target_cwnd, max_delivery_rate_sec, + cstat->smoothed_rtt); + } +} + +void ngtcp2_cc_cubic_cc_on_pkt_sent(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_pkt *pkt) { + ngtcp2_cc_cubic *cubic = ngtcp2_struct_of(cc, ngtcp2_cc_cubic, cc); + (void)cstat; + + if (pkt->pktns_id != NGTCP2_PKTNS_ID_APPLICATION || cubic->window_end != -1) { + return; + } + + cubic->window_end = pkt->pkt_num; + cubic->last_round_min_rtt = cubic->current_round_min_rtt; + cubic->current_round_min_rtt = UINT64_MAX; + cubic->rtt_sample_count = 0; +} + +void ngtcp2_cc_cubic_cc_new_rtt_sample(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + ngtcp2_cc_cubic *cubic = ngtcp2_struct_of(cc, ngtcp2_cc_cubic, cc); + (void)ts; + + if (cubic->window_end == -1) { + return; + } + + cubic->current_round_min_rtt = + ngtcp2_min(cubic->current_round_min_rtt, cstat->latest_rtt); + ++cubic->rtt_sample_count; +} + +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); +} + +void ngtcp2_cc_cubic_cc_event(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + ngtcp2_cc_event_type event, ngtcp2_tstamp ts) { + ngtcp2_cc_cubic *cubic = ngtcp2_struct_of(cc, ngtcp2_cc_cubic, cc); + ngtcp2_tstamp last_ts; + + if (event != NGTCP2_CC_EVENT_TYPE_TX_START || + cubic->epoch_start == UINT64_MAX) { + return; + } + + last_ts = cstat->last_tx_pkt_ts[NGTCP2_PKTNS_ID_APPLICATION]; + if (last_ts == UINT64_MAX || last_ts <= cubic->epoch_start) { + return; + } + + assert(ts >= last_ts); + + cubic->epoch_start += ts - last_ts; +} diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_cc.h b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_cc.h new file mode 100644 index 0000000..524bcdb --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_cc.h @@ -0,0 +1,400 @@ +/* + * ngtcp2 + * + * Copyright (c) 2018 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_CC_H +#define NGTCP2_CC_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_pktns_id.h" +#include "ngtcp2_window_filter.h" + +#define NGTCP2_LOSS_REDUCTION_FACTOR_BITS 1 +#define NGTCP2_PERSISTENT_CONGESTION_THRESHOLD 3 + +typedef struct ngtcp2_log ngtcp2_log; +typedef struct ngtcp2_conn_stat ngtcp2_conn_stat; + +/** + * @struct + * + * :type:`ngtcp2_cc_pkt` is a convenient structure to include + * acked/lost/sent packet. + */ +typedef struct ngtcp2_cc_pkt { + /** + * :member:`pkt_num` is the packet number + */ + int64_t pkt_num; + /** + * :member:`pktlen` is the length of packet. + */ + size_t pktlen; + /** + * :member:`pktns_id` is the ID of packet number space which this + * packet belongs to. + */ + ngtcp2_pktns_id pktns_id; + /** + * :member:`sent_ts` is the timestamp when packet is sent. + */ + ngtcp2_tstamp sent_ts; + /** + * :member:`lost` is the number of bytes lost when this packet was + * sent. + */ + uint64_t lost; + /** + * :member:`tx_in_flight` is the bytes in flight when this packet + * was sent. + */ + uint64_t tx_in_flight; + /** + * :member:`is_app_limited` is nonzero if the connection is + * app-limited when this packet was sent. + */ + int is_app_limited; +} ngtcp2_cc_pkt; + +/** + * @struct + * + * :type:`ngtcp2_cc_ack` is a convenient structure which stores + * 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. + */ + uint64_t bytes_delivered; + /** + * :member:`bytes_lost` is the number of bytes declared lost. + */ + uint64_t bytes_lost; + /** + * :member:`pkt_delivered` is the cumulative acknowledged bytes when + * the last packet acknowledged by this ACK was sent. + */ + uint64_t pkt_delivered; + /** + * :member:`largest_pkt_sent_ts` is the time when the largest + * acknowledged packet was sent. It is UINT64_MAX if it is unknown. + */ + ngtcp2_tstamp largest_pkt_sent_ts; + /** + * :member:`rtt` is the RTT sample. It is UINT64_MAX if no RTT + * sample is available. + */ + ngtcp2_duration rtt; +} ngtcp2_cc_ack; + +typedef struct ngtcp2_cc ngtcp2_cc; + +/** + * @functypedef + * + * :type:`ngtcp2_cc_on_pkt_acked` is a callback function which is + * called with an acknowledged packet. + */ +typedef void (*ngtcp2_cc_on_pkt_acked)(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_pkt *pkt, + ngtcp2_tstamp ts); + +/** + * @functypedef + * + * :type:`ngtcp2_cc_on_pkt_lost` is a callback function which is + * called with a lost packet. + */ +typedef void (*ngtcp2_cc_on_pkt_lost)(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_pkt *pkt, + ngtcp2_tstamp ts); +/** + * @functypedef + * + * :type:`ngtcp2_cc_congestion_event` is a callback function which is + * called when congestion event happens (e.g., when packet is lost). + */ +typedef void (*ngtcp2_cc_congestion_event)(ngtcp2_cc *cc, + ngtcp2_conn_stat *cstat, + ngtcp2_tstamp sent_ts, + ngtcp2_tstamp ts); + +/** + * @functypedef + * + * :type:`ngtcp2_cc_on_spurious_congestion` is a callback function + * which is called when a spurious congestion is detected. + */ +typedef void (*ngtcp2_cc_on_spurious_congestion)(ngtcp2_cc *cc, + ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); + +/** + * @functypedef + * + * :type:`ngtcp2_cc_on_persistent_congestion` is a callback function + * which is called when persistent congestion is established. + */ +typedef void (*ngtcp2_cc_on_persistent_congestion)(ngtcp2_cc *cc, + ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); + +/** + * @functypedef + * + * :type:`ngtcp2_cc_on_ack_recv` is a callback function which is + * called when an acknowledgement is received. + */ +typedef void (*ngtcp2_cc_on_ack_recv)(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, + ngtcp2_tstamp ts); + +/** + * @functypedef + * + * :type:`ngtcp2_cc_on_pkt_sent` is a callback function which is + * called when an ack-eliciting packet is sent. + */ +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 + * + * :type:`ngtcp2_cc_reset` is a callback function which is called when + * congestion state must be reset. + */ +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 + * + * :type:`ngtcp2_cc` is congestion control algorithm interface shared + * by implementations. All callback functions are optional. + */ +typedef struct ngtcp2_cc { + /** + * :member:`log` is ngtcp2 library internal logger. + */ + ngtcp2_log *log; + /** + * :member:`on_pkt_acked` is a callback function which is called + * when a packet is acknowledged. + */ + ngtcp2_cc_on_pkt_acked on_pkt_acked; + /** + * :member:`on_pkt_lost` is a callback function which is called when + * a packet is lost. + */ + 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). + */ + ngtcp2_cc_congestion_event congestion_event; + /** + * :member:`on_spurious_congestion` is a callback function which is + * called when a spurious congestion is detected. + */ + ngtcp2_cc_on_spurious_congestion on_spurious_congestion; + /** + * :member:`on_persistent_congestion` is a callback function which + * is called when persistent congestion is established. + */ + ngtcp2_cc_on_persistent_congestion on_persistent_congestion; + /** + * :member:`on_ack_recv` is a callback function which is called when + * an acknowledgement is received. + */ + ngtcp2_cc_on_ack_recv on_ack_recv; + /** + * :member:`on_pkt_sent` is a callback function which is called when + * 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; + +/* + * ngtcp2_cc_compute_initcwnd computes initial cwnd. + */ +uint64_t ngtcp2_cc_compute_initcwnd(size_t max_packet_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, + uint64_t tx_in_flight, int is_app_limited); + +/* ngtcp2_cc_reno is the RENO congestion controller. */ +typedef struct ngtcp2_cc_reno { + ngtcp2_cc cc; + ngtcp2_window_filter delivery_rate_sec_filter; + uint64_t ack_count; + uint64_t target_cwnd; + uint64_t pending_add; +} ngtcp2_cc_reno; + +void ngtcp2_cc_reno_init(ngtcp2_cc_reno *reno, ngtcp2_log *log); + +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, + ngtcp2_tstamp ts); + +void ngtcp2_cc_reno_cc_on_persistent_congestion(ngtcp2_cc *cc, + ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); + +void ngtcp2_cc_reno_cc_on_ack_recv(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, ngtcp2_tstamp ts); + +void ngtcp2_cc_reno_cc_reset(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); + +/* ngtcp2_cc_cubic is CUBIC congestion controller. */ +typedef struct ngtcp2_cc_cubic { + ngtcp2_cc cc; + ngtcp2_window_filter delivery_rate_sec_filter; + uint64_t ack_count; + uint64_t target_cwnd; + uint64_t w_last_max; + uint64_t w_tcp; + uint64_t origin_point; + ngtcp2_tstamp epoch_start; + uint64_t k; + /* prior stores the congestion state when a congestion event occurs + in order to restore the state when it turns out that the event is + spurious. */ + struct { + uint64_t cwnd; + uint64_t ssthresh; + uint64_t w_last_max; + uint64_t w_tcp; + uint64_t origin_point; + ngtcp2_tstamp epoch_start; + uint64_t k; + } prior; + /* HyStart++ variables */ + size_t rtt_sample_count; + uint64_t current_round_min_rtt; + uint64_t last_round_min_rtt; + int64_t window_end; + uint64_t pending_add; + uint64_t pending_w_add; +} ngtcp2_cc_cubic; + +void ngtcp2_cc_cubic_init(ngtcp2_cc_cubic *cc, ngtcp2_log *log); + +void ngtcp2_cc_cubic_cc_on_pkt_acked(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_pkt *pkt, + ngtcp2_tstamp ts); + +void ngtcp2_cc_cubic_cc_congestion_event(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp sent_ts, + ngtcp2_tstamp ts); + +void ngtcp2_cc_cubic_cc_on_spurious_congestion(ngtcp2_cc *ccx, + ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); + +void ngtcp2_cc_cubic_cc_on_persistent_congestion(ngtcp2_cc *cc, + ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); + +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_on_pkt_sent(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_pkt *pkt); + +void ngtcp2_cc_cubic_cc_new_rtt_sample(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); + +void ngtcp2_cc_cubic_cc_reset(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); + +void ngtcp2_cc_cubic_cc_event(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + ngtcp2_cc_event_type event, ngtcp2_tstamp ts); + +uint64_t ngtcp2_cbrt(uint64_t n); + +#endif /* NGTCP2_CC_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_cid.c b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_cid.c new file mode 100644 index 0000000..f3b92b5 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_cid.c @@ -0,0 +1,147 @@ +/* + * ngtcp2 + * + * Copyright (c) 2018 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_cid.h" + +#include <assert.h> +#include <string.h> + +#include "ngtcp2_path.h" +#include "ngtcp2_str.h" + +void ngtcp2_cid_zero(ngtcp2_cid *cid) { memset(cid, 0, sizeof(*cid)); } + +void ngtcp2_cid_init(ngtcp2_cid *cid, const uint8_t *data, size_t datalen) { + assert(datalen <= NGTCP2_MAX_CIDLEN); + + cid->datalen = datalen; + if (datalen) { + ngtcp2_cpymem(cid->data, data, datalen); + } +} + +int ngtcp2_cid_eq(const ngtcp2_cid *cid, const ngtcp2_cid *other) { + return cid->datalen == other->datalen && + 0 == memcmp(cid->data, other->data, cid->datalen); +} + +int ngtcp2_cid_less(const ngtcp2_cid *lhs, const ngtcp2_cid *rhs) { + int s = lhs->datalen < rhs->datalen; + size_t n = s ? lhs->datalen : rhs->datalen; + int c = memcmp(lhs->data, rhs->data, n); + + return c < 0 || (c == 0 && s); +} + +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; +} + +void ngtcp2_scid_copy(ngtcp2_scid *dest, const ngtcp2_scid *src) { + ngtcp2_scid_init(dest, src->seq, &src->cid); + dest->retired_ts = src->retired_ts; + dest->flags = src->flags; +} + +void ngtcp2_dcid_init(ngtcp2_dcid *dcid, uint64_t seq, const ngtcp2_cid *cid, + const uint8_t *token) { + dcid->seq = seq; + dcid->cid = *cid; + if (token) { + memcpy(dcid->token, token, NGTCP2_STATELESS_RESET_TOKENLEN); + dcid->flags = NGTCP2_DCID_FLAG_TOKEN_PRESENT; + } else { + dcid->flags = NGTCP2_DCID_FLAG_NONE; + } + ngtcp2_path_storage_zero(&dcid->ps); + dcid->retired_ts = UINT64_MAX; + dcid->bound_ts = UINT64_MAX; + dcid->bytes_sent = 0; + dcid->bytes_recv = 0; + dcid->max_udp_payload_size = NGTCP2_MAX_UDP_PAYLOAD_SIZE; +} + +void ngtcp2_dcid_set_token(ngtcp2_dcid *dcid, const uint8_t *token) { + assert(token); + + dcid->flags |= NGTCP2_DCID_FLAG_TOKEN_PRESENT; + memcpy(dcid->token, token, NGTCP2_STATELESS_RESET_TOKENLEN); +} + +void ngtcp2_dcid_set_path(ngtcp2_dcid *dcid, const ngtcp2_path *path) { + ngtcp2_path_copy(&dcid->ps.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 + : NULL); + ngtcp2_path_copy(&dest->ps.path, &src->ps.path); + dest->retired_ts = src->retired_ts; + dest->bound_ts = src->bound_ts; + dest->flags = src->flags; + dest->bytes_sent = src->bytes_sent; + dest->bytes_recv = src->bytes_recv; + dest->max_udp_payload_size = src->max_udp_payload_size; +} + +void ngtcp2_dcid_copy_cid_token(ngtcp2_dcid *dest, const ngtcp2_dcid *src) { + dest->seq = src->seq; + dest->cid = src->cid; + if (src->flags & NGTCP2_DCID_FLAG_TOKEN_PRESENT) { + dest->flags |= NGTCP2_DCID_FLAG_TOKEN_PRESENT; + memcpy(dest->token, src->token, NGTCP2_STATELESS_RESET_TOKENLEN); + } else if (dest->flags & NGTCP2_DCID_FLAG_TOKEN_PRESENT) { + dest->flags &= (uint8_t)~NGTCP2_DCID_FLAG_TOKEN_PRESENT; + } +} + +int ngtcp2_dcid_verify_uniqueness(ngtcp2_dcid *dcid, uint64_t seq, + const ngtcp2_cid *cid, const uint8_t *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 + ? 0 + : NGTCP2_ERR_PROTO; + } + + return !ngtcp2_cid_eq(&dcid->cid, cid) ? 0 : NGTCP2_ERR_PROTO; +} + +int ngtcp2_dcid_verify_stateless_reset_token(const ngtcp2_dcid *dcid, + const uint8_t *token) { + return (dcid->flags & NGTCP2_DCID_FLAG_TOKEN_PRESENT) && + ngtcp2_cmemeq(dcid->token, token, + NGTCP2_STATELESS_RESET_TOKENLEN) + ? 0 + : NGTCP2_ERR_INVALID_ARGUMENT; +} diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_cid.h b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_cid.h new file mode 100644 index 0000000..0b37441 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_cid.h @@ -0,0 +1,175 @@ +/* + * ngtcp2 + * + * Copyright (c) 2018 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_CID_H +#define NGTCP2_CID_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_pq.h" +#include "ngtcp2_path.h" + +/* NGTCP2_SCID_FLAG_NONE indicates that no flag is set. */ +#define NGTCP2_SCID_FLAG_NONE 0x00u +/* NGTCP2_SCID_FLAG_USED indicates that a local endpoint observed that + a remote endpoint uses a particular Connection ID. */ +#define NGTCP2_SCID_FLAG_USED 0x01u +/* NGTCP2_SCID_FLAG_RETIRED indicates that a particular Connection ID + is retired. */ +#define NGTCP2_SCID_FLAG_RETIRED 0x02u + +typedef struct ngtcp2_scid { + ngtcp2_pq_entry pe; + /* seq is the sequence number associated to the CID. */ + uint64_t seq; + /* cid is a connection ID */ + ngtcp2_cid cid; + /* retired_ts is the timestamp when peer tells that this CID is + retired. */ + ngtcp2_tstamp retired_ts; + /* flags is the bitwise OR of zero or more of NGTCP2_SCID_FLAG_*. */ + uint8_t flags; +} ngtcp2_scid; + +/* NGTCP2_DCID_FLAG_NONE indicates that no flag is set. */ +#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 +/* NGTCP2_DCID_FLAG_TOKEN_PRESENT indicates that a stateless reset + token is set in token field. */ +#define NGTCP2_DCID_FLAG_TOKEN_PRESENT 0x02u + +typedef struct ngtcp2_dcid { + /* seq is the sequence number associated to the CID. */ + uint64_t seq; + /* cid is a connection ID */ + ngtcp2_cid cid; + /* path is a path which cid is bound to. The addresses are zero + length if cid has not been bound to a particular path yet. */ + ngtcp2_path_storage ps; + /* retired_ts is the timestamp when peer tells that this CID is + retired. */ + ngtcp2_tstamp retired_ts; + /* bound_ts is the timestamp when this connection ID is bound to a + particular path. It is only assigned when a connection ID is + used just for sending PATH_RESPONSE and is not zero-length. */ + ngtcp2_tstamp bound_ts; + /* bytes_sent is the number of bytes sent to an associated path. */ + uint64_t bytes_sent; + /* bytes_recv is the number of bytes received from an associated + path. */ + uint64_t bytes_recv; + /* max_udp_payload_size is the maximum size of UDP payload that is + allowed to send to this path. */ + size_t max_udp_payload_size; + /* flags is bitwise OR of zero or more of NGTCP2_DCID_FLAG_*. */ + uint8_t flags; + /* token is a stateless reset token associated to this CID. + Actually, the stateless reset token is tied to the connection, + not to the particular connection ID. */ + uint8_t token[NGTCP2_STATELESS_RESET_TOKENLEN]; +} ngtcp2_dcid; + +/* ngtcp2_cid_zero makes |cid| zero-length. */ +void ngtcp2_cid_zero(ngtcp2_cid *cid); + +/* + * ngtcp2_cid_less returns nonzero if |lhs| is lexicographical smaller + * than |rhs|. + */ +int ngtcp2_cid_less(const ngtcp2_cid *lhs, const ngtcp2_cid *rhs); + +/* + * ngtcp2_cid_empty returns nonzero if |cid| includes empty connection + * ID. + */ +int ngtcp2_cid_empty(const ngtcp2_cid *cid); + +/* + * ngtcp2_scid_init initializes |scid| with the given parameters. + */ +void ngtcp2_scid_init(ngtcp2_scid *scid, uint64_t seq, const ngtcp2_cid *cid); + +/* + * ngtcp2_scid_copy copies |src| into |dest|. + */ +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 it with 0. |token| + * must be NGTCP2_STATELESS_RESET_TOKENLEN bytes long. + */ +void ngtcp2_dcid_init(ngtcp2_dcid *dcid, uint64_t seq, const ngtcp2_cid *cid, + const uint8_t *token); + +/* + * ngtcp2_dcid_set_token sets |token| to |dcid|. |token| must not be + * NULL and must be NGTCP2_STATELESS_RESET_TOKENLEN bytes long. + */ +void ngtcp2_dcid_set_token(ngtcp2_dcid *dcid, const uint8_t *token); + +/* + * ngtcp2_dcid_set_path sets |path| to |dcid|. It sets + * max_udp_payload_size to the minimum UDP payload size supported + * by the IP protocol version. + */ +void ngtcp2_dcid_set_path(ngtcp2_dcid *dcid, const ngtcp2_path *path); + +/* + * ngtcp2_dcid_copy copies |src| into |dest|. + */ +void ngtcp2_dcid_copy(ngtcp2_dcid *dest, const ngtcp2_dcid *src); + +/* + * ngtcp2_dcid_copy_cid_token behaves like ngtcp2_dcid_copy, but it + * only copies cid, seq, and path. + */ +void ngtcp2_dcid_copy_cid_token(ngtcp2_dcid *dest, const ngtcp2_dcid *src); + +/* + * ngtcp2_dcid_verify_uniqueness verifies uniqueness of (|seq|, |cid|, + * |token|) tuple against |dcid|. + */ +int ngtcp2_dcid_verify_uniqueness(ngtcp2_dcid *dcid, uint64_t seq, + const ngtcp2_cid *cid, const uint8_t *token); + +/* + * ngtcp2_dcid_verify_stateless_reset_token verifies stateless reset + * token |token| against the one included in |dcid|. This function + * returns 0 if the verification succeeds, or one of the following + * negative error codes: + * + * 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 uint8_t *token); + +#endif /* NGTCP2_CID_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_conn.c b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_conn.c new file mode 100644 index 0000000..a4873eb --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_conn.c @@ -0,0 +1,13775 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 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.h" + +#include <string.h> +#include <assert.h> + +#include "ngtcp2_macro.h" +#include "ngtcp2_log.h" +#include "ngtcp2_cid.h" +#include "ngtcp2_conv.h" +#include "ngtcp2_vec.h" +#include "ngtcp2_addr.h" +#include "ngtcp2_path.h" +#include "ngtcp2_rcvry.h" +#include "ngtcp2_unreachable.h" +#include "ngtcp2_net.h" +#include "ngtcp2_conversion.h" +#include "ngtcp2_tstamp.h" +#include "ngtcp2_frame_chain.h" + +/* NGTCP2_FLOW_WINDOW_RTT_FACTOR is the factor of RTT when flow + control window auto-tuning is triggered. */ +#define NGTCP2_FLOW_WINDOW_RTT_FACTOR 2 +/* NGTCP2_FLOW_WINDOW_SCALING_FACTOR is the growth factor of flow + control window. */ +#define NGTCP2_FLOW_WINDOW_SCALING_FACTOR 2 +/* NGTCP2_MIN_COALESCED_PAYLOADLEN is the minimum length of QUIC + packet payload that should be coalesced to a long packet. */ +#define NGTCP2_MIN_COALESCED_PAYLOADLEN 128 + +ngtcp2_objalloc_def(strm, ngtcp2_strm, oplent); + +/* + * conn_local_stream returns nonzero if |stream_id| indicates that it + * is the stream initiated by local endpoint. + */ +static int conn_local_stream(ngtcp2_conn *conn, int64_t stream_id) { + return (uint8_t)(stream_id & 1) == conn->server; +} + +/* + * bidi_stream returns nonzero if |stream_id| is a bidirectional + * stream ID. + */ +static int bidi_stream(int64_t stream_id) { return (stream_id & 0x2) == 0; } + +static void conn_update_timestamp(ngtcp2_conn *conn, ngtcp2_tstamp ts) { + assert(conn->log.last_ts <= ts); + assert(conn->qlog.last_ts <= ts); + + conn->log.last_ts = ts; + conn->qlog.last_ts = ts; +} + +/* + * conn_is_tls_handshake_completed returns nonzero if TLS handshake + * has completed and 1 RTT keys are available. + */ +static int conn_is_tls_handshake_completed(ngtcp2_conn *conn) { + return (conn->flags & NGTCP2_CONN_FLAG_TLS_HANDSHAKE_COMPLETED) && + conn->pktns.crypto.rx.ckm && conn->pktns.crypto.tx.ckm; +} + +static int conn_call_recv_client_initial(ngtcp2_conn *conn, + const ngtcp2_cid *dcid) { + int rv; + + assert(conn->callbacks.recv_client_initial); + + rv = conn->callbacks.recv_client_initial(conn, dcid, conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_handshake_completed(ngtcp2_conn *conn) { + int rv; + + if (!conn->callbacks.handshake_completed) { + return 0; + } + + rv = conn->callbacks.handshake_completed(conn, conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_recv_stream_data(ngtcp2_conn *conn, ngtcp2_strm *strm, + uint32_t flags, uint64_t offset, + const uint8_t *data, size_t datalen) { + int rv; + + if (!conn->callbacks.recv_stream_data) { + return 0; + } + + rv = conn->callbacks.recv_stream_data(conn, flags, strm->stream_id, offset, + data, datalen, conn->user_data, + strm->stream_user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_recv_crypto_data(ngtcp2_conn *conn, + ngtcp2_encryption_level encryption_level, + uint64_t offset, const uint8_t *data, + size_t datalen) { + int rv; + + assert(conn->callbacks.recv_crypto_data); + + rv = conn->callbacks.recv_crypto_data(conn, encryption_level, offset, data, + datalen, conn->user_data); + switch (rv) { + case 0: + case NGTCP2_ERR_CRYPTO: + case NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM: + case NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM: + case NGTCP2_ERR_TRANSPORT_PARAM: + case NGTCP2_ERR_PROTO: + case NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE: + case NGTCP2_ERR_NOMEM: + case NGTCP2_ERR_CALLBACK_FAILURE: + return rv; + default: + return NGTCP2_ERR_CALLBACK_FAILURE; + } +} + +static int conn_call_stream_open(ngtcp2_conn *conn, ngtcp2_strm *strm) { + int rv; + + if (!conn->callbacks.stream_open) { + return 0; + } + + rv = conn->callbacks.stream_open(conn, strm->stream_id, conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_stream_close(ngtcp2_conn *conn, ngtcp2_strm *strm) { + int rv; + uint32_t flags = NGTCP2_STREAM_CLOSE_FLAG_NONE; + + if (!conn->callbacks.stream_close) { + return 0; + } + + if (strm->flags & NGTCP2_STRM_FLAG_APP_ERROR_CODE_SET) { + flags |= NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET; + } + + rv = conn->callbacks.stream_close(conn, flags, strm->stream_id, + strm->app_error_code, conn->user_data, + strm->stream_user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_stream_reset(ngtcp2_conn *conn, int64_t stream_id, + uint64_t final_size, uint64_t app_error_code, + void *stream_user_data) { + int rv; + + if (!conn->callbacks.stream_reset) { + return 0; + } + + rv = conn->callbacks.stream_reset(conn, stream_id, final_size, app_error_code, + conn->user_data, stream_user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_extend_max_local_streams_bidi(ngtcp2_conn *conn, + uint64_t max_streams) { + int rv; + + if (!conn->callbacks.extend_max_local_streams_bidi) { + return 0; + } + + rv = conn->callbacks.extend_max_local_streams_bidi(conn, max_streams, + conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_extend_max_local_streams_uni(ngtcp2_conn *conn, + uint64_t max_streams) { + int rv; + + if (!conn->callbacks.extend_max_local_streams_uni) { + return 0; + } + + rv = conn->callbacks.extend_max_local_streams_uni(conn, max_streams, + conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_get_new_connection_id(ngtcp2_conn *conn, ngtcp2_cid *cid, + uint8_t *token, size_t cidlen) { + int rv; + + assert(conn->callbacks.get_new_connection_id); + + rv = conn->callbacks.get_new_connection_id(conn, cid, token, cidlen, + conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_remove_connection_id(ngtcp2_conn *conn, + const ngtcp2_cid *cid) { + int rv; + + if (!conn->callbacks.remove_connection_id) { + return 0; + } + + rv = conn->callbacks.remove_connection_id(conn, cid, 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; + + if (!conn->callbacks.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_ON_FAILURE) { + old_path = &pv->fallback_dcid.ps.path; + } + + if (conn->server && old_path && + (ngtcp2_addr_compare(&pv->dcid.ps.path.remote, &old_path->remote) & + (NGTCP2_ADDR_COMPARE_FLAG_ADDR | NGTCP2_ADDR_COMPARE_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); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_select_preferred_addr(ngtcp2_conn *conn, + ngtcp2_path *dest) { + int rv; + + if (!conn->callbacks.select_preferred_addr) { + return 0; + } + + assert(conn->remote.transport_params); + assert(conn->remote.transport_params->preferred_addr_present); + + rv = conn->callbacks.select_preferred_addr( + conn, dest, &conn->remote.transport_params->preferred_addr, + conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_extend_max_remote_streams_bidi(ngtcp2_conn *conn, + uint64_t max_streams) { + int rv; + + if (!conn->callbacks.extend_max_remote_streams_bidi) { + return 0; + } + + rv = conn->callbacks.extend_max_remote_streams_bidi(conn, max_streams, + conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_extend_max_remote_streams_uni(ngtcp2_conn *conn, + uint64_t max_streams) { + int rv; + + if (!conn->callbacks.extend_max_remote_streams_uni) { + return 0; + } + + rv = conn->callbacks.extend_max_remote_streams_uni(conn, max_streams, + conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_extend_max_stream_data(ngtcp2_conn *conn, + ngtcp2_strm *strm, + int64_t stream_id, + uint64_t datalen) { + int rv; + + if (!conn->callbacks.extend_max_stream_data) { + return 0; + } + + rv = conn->callbacks.extend_max_stream_data( + conn, stream_id, datalen, conn->user_data, strm->stream_user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_dcid_status(ngtcp2_conn *conn, + ngtcp2_connection_id_status_type type, + const ngtcp2_dcid *dcid) { + int rv; + + if (!conn->callbacks.dcid_status) { + 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; + } + + return 0; +} + +static int conn_call_activate_dcid(ngtcp2_conn *conn, const ngtcp2_dcid *dcid) { + return conn_call_dcid_status(conn, NGTCP2_CONNECTION_ID_STATUS_TYPE_ACTIVATE, + dcid); +} + +static int conn_call_deactivate_dcid(ngtcp2_conn *conn, + const ngtcp2_dcid *dcid) { + return conn_call_dcid_status( + conn, NGTCP2_CONNECTION_ID_STATUS_TYPE_DEACTIVATE, dcid); +} + +static int conn_call_stream_stop_sending(ngtcp2_conn *conn, int64_t stream_id, + uint64_t app_error_code, + void *stream_user_data) { + int rv; + + if (!conn->callbacks.stream_stop_sending) { + return 0; + } + + rv = conn->callbacks.stream_stop_sending(conn, stream_id, app_error_code, + conn->user_data, stream_user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static void conn_call_delete_crypto_aead_ctx(ngtcp2_conn *conn, + ngtcp2_crypto_aead_ctx *aead_ctx) { + if (!aead_ctx->native_handle) { + return; + } + + assert(conn->callbacks.delete_crypto_aead_ctx); + + conn->callbacks.delete_crypto_aead_ctx(conn, aead_ctx, conn->user_data); +} + +static void +conn_call_delete_crypto_cipher_ctx(ngtcp2_conn *conn, + ngtcp2_crypto_cipher_ctx *cipher_ctx) { + if (!cipher_ctx->native_handle) { + return; + } + + assert(conn->callbacks.delete_crypto_cipher_ctx); + + conn->callbacks.delete_crypto_cipher_ctx(conn, cipher_ctx, conn->user_data); +} + +static int conn_call_client_initial(ngtcp2_conn *conn) { + int rv; + + assert(conn->callbacks.client_initial); + + rv = conn->callbacks.client_initial(conn, conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_get_path_challenge_data(ngtcp2_conn *conn, uint8_t *data) { + int rv; + + assert(conn->callbacks.get_path_challenge_data); + + rv = conn->callbacks.get_path_challenge_data(conn, data, conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_recv_version_negotiation(ngtcp2_conn *conn, + const ngtcp2_pkt_hd *hd, + const uint32_t *sv, size_t nsv) { + int rv; + + if (!conn->callbacks.recv_version_negotiation) { + return 0; + } + + rv = conn->callbacks.recv_version_negotiation(conn, hd, sv, nsv, + conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_recv_retry(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd) { + int rv; + + assert(conn->callbacks.recv_retry); + + rv = conn->callbacks.recv_retry(conn, hd, conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int +conn_call_recv_stateless_reset(ngtcp2_conn *conn, + const ngtcp2_pkt_stateless_reset *sr) { + int rv; + + if (!conn->callbacks.recv_stateless_reset) { + return 0; + } + + rv = conn->callbacks.recv_stateless_reset(conn, sr, conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_recv_new_token(ngtcp2_conn *conn, const uint8_t *token, + size_t tokenlen) { + int rv; + + if (!conn->callbacks.recv_new_token) { + return 0; + } + + rv = conn->callbacks.recv_new_token(conn, token, tokenlen, conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_handshake_confirmed(ngtcp2_conn *conn) { + int rv; + + if (!conn->callbacks.handshake_confirmed) { + return 0; + } + + rv = conn->callbacks.handshake_confirmed(conn, conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_recv_datagram(ngtcp2_conn *conn, + const ngtcp2_datagram *fr) { + const uint8_t *data; + size_t datalen; + int rv; + uint32_t flags = NGTCP2_DATAGRAM_FLAG_NONE; + + if (!conn->callbacks.recv_datagram) { + return 0; + } + + if (fr->datacnt) { + assert(fr->datacnt == 1); + + data = fr->data->base; + datalen = fr->data->len; + } else { + data = NULL; + datalen = 0; + } + + if (!conn_is_tls_handshake_completed(conn)) { + flags |= NGTCP2_DATAGRAM_FLAG_0RTT; + } + + rv = conn->callbacks.recv_datagram(conn, flags, data, datalen, + conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int +conn_call_update_key(ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret, + ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv, + ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv, + const uint8_t *current_rx_secret, + const uint8_t *current_tx_secret, size_t secretlen) { + int rv; + + assert(conn->callbacks.update_key); + + rv = conn->callbacks.update_key( + conn, rx_secret, tx_secret, rx_aead_ctx, rx_iv, tx_aead_ctx, tx_iv, + current_rx_secret, current_tx_secret, secretlen, conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_version_negotiation(ngtcp2_conn *conn, uint32_t version, + const ngtcp2_cid *dcid) { + int rv; + + assert(conn->callbacks.version_negotiation); + + rv = + conn->callbacks.version_negotiation(conn, version, dcid, conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_recv_rx_key(ngtcp2_conn *conn, + ngtcp2_encryption_level level) { + int rv; + + if (!conn->callbacks.recv_rx_key) { + return 0; + } + + rv = conn->callbacks.recv_rx_key(conn, level, conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_recv_tx_key(ngtcp2_conn *conn, + ngtcp2_encryption_level level) { + int rv; + + if (!conn->callbacks.recv_tx_key) { + return 0; + } + + rv = conn->callbacks.recv_tx_key(conn, level, conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int 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) { + int rv; + + memset(pktns, 0, sizeof(*pktns)); + + ngtcp2_gaptr_init(&pktns->rx.pngap, mem); + + pktns->tx.last_pkt_num = initial_pkt_num - 1; + pktns->tx.non_ack_pkt_start_ts = UINT64_MAX; + pktns->rx.max_pkt_num = -1; + pktns->rx.max_ack_eliciting_pkt_num = -1; + + rv = ngtcp2_acktr_init(&pktns->acktr, log, mem); + if (rv != 0) { + goto fail_acktr_init; + } + + ngtcp2_strm_init(&pktns->crypto.strm, 0, NGTCP2_STRM_FLAG_NONE, 0, 0, NULL, + frc_objalloc, mem); + + ngtcp2_rtb_init(&pktns->rtb, pktns_id, &pktns->crypto.strm, rst, cc, + initial_pkt_num, log, qlog, rtb_entry_objalloc, frc_objalloc, + mem); + + return 0; + +fail_acktr_init: + ngtcp2_gaptr_free(&pktns->rx.pngap); + + return rv; +} + +static int pktns_new(ngtcp2_pktns **ppktns, 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) { + int rv; + + *ppktns = ngtcp2_mem_malloc(mem, sizeof(ngtcp2_pktns)); + if (*ppktns == NULL) { + return NGTCP2_ERR_NOMEM; + } + + rv = pktns_init(*ppktns, pktns_id, rst, cc, initial_pkt_num, log, qlog, + rtb_entry_objalloc, frc_objalloc, mem); + if (rv != 0) { + ngtcp2_mem_free(mem, *ppktns); + } + + return rv; +} + +static int cycle_less(const ngtcp2_pq_entry *lhs, const ngtcp2_pq_entry *rhs) { + ngtcp2_strm *ls = ngtcp2_struct_of(lhs, ngtcp2_strm, pe); + ngtcp2_strm *rs = ngtcp2_struct_of(rhs, ngtcp2_strm, pe); + + if (ls->cycle == rs->cycle) { + return ls->stream_id < rs->stream_id; + } + + return rs->cycle - ls->cycle <= 1; +} + +static void delete_buffed_pkts(ngtcp2_pkt_chain *pc, const ngtcp2_mem *mem) { + ngtcp2_pkt_chain *next; + + for (; pc;) { + next = pc->next; + ngtcp2_pkt_chain_del(pc, mem); + pc = next; + } +} + +static void delete_buf_chain(ngtcp2_buf_chain *bufchain, + const ngtcp2_mem *mem) { + ngtcp2_buf_chain *next; + + for (; bufchain;) { + next = bufchain->next; + ngtcp2_buf_chain_del(bufchain, mem); + bufchain = next; + } +} + +static void pktns_free(ngtcp2_pktns *pktns, const ngtcp2_mem *mem) { + delete_buf_chain(pktns->crypto.tx.data, mem); + + delete_buffed_pkts(pktns->rx.buffed_pkts, mem); + + ngtcp2_frame_chain_list_objalloc_del(pktns->tx.frq, pktns->rtb.frc_objalloc, + mem); + + ngtcp2_crypto_km_del(pktns->crypto.rx.ckm, mem); + ngtcp2_crypto_km_del(pktns->crypto.tx.ckm, mem); + + ngtcp2_rtb_free(&pktns->rtb); + ngtcp2_strm_free(&pktns->crypto.strm); + ngtcp2_acktr_free(&pktns->acktr); + ngtcp2_gaptr_free(&pktns->rx.pngap); +} + +static void pktns_del(ngtcp2_pktns *pktns, const ngtcp2_mem *mem) { + if (pktns == NULL) { + return; + } + + pktns_free(pktns, mem); + + ngtcp2_mem_free(mem, pktns); +} + +static int cid_less(const ngtcp2_ksl_key *lhs, const ngtcp2_ksl_key *rhs) { + return ngtcp2_cid_less(lhs, rhs); +} + +static int retired_ts_less(const ngtcp2_pq_entry *lhs, + const ngtcp2_pq_entry *rhs) { + const ngtcp2_scid *a = ngtcp2_struct_of(lhs, ngtcp2_scid, pe); + const ngtcp2_scid *b = ngtcp2_struct_of(rhs, ngtcp2_scid, pe); + + return a->retired_ts < b->retired_ts; +} + +/* + * conn_reset_conn_stat_cc resets congestion state in |cstat|. + */ +static void conn_reset_conn_stat_cc(ngtcp2_conn *conn, + ngtcp2_conn_stat *cstat) { + cstat->latest_rtt = 0; + cstat->min_rtt = UINT64_MAX; + cstat->smoothed_rtt = conn->local.settings.initial_rtt; + cstat->rttvar = conn->local.settings.initial_rtt / 2; + cstat->first_rtt_sample_ts = UINT64_MAX; + cstat->pto_count = 0; + cstat->loss_detection_timer = UINT64_MAX; + cstat->cwnd = + ngtcp2_cc_compute_initcwnd(conn->local.settings.max_tx_udp_payload_size); + cstat->ssthresh = UINT64_MAX; + cstat->congestion_recovery_start_ts = UINT64_MAX; + cstat->bytes_in_flight = 0; + cstat->delivery_rate_sec = 0; + cstat->pacing_interval = 0; + cstat->send_quantum = 64 * 1024; +} + +/* + * reset_conn_stat_recovery resets the fields related to the recovery + * function + */ +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)); +} + +/* + * conn_reset_conn_stat resets |cstat|. The following fields are not + * reset: initial_rtt and max_udp_payload_size. + */ +static void conn_reset_conn_stat(ngtcp2_conn *conn, ngtcp2_conn_stat *cstat) { + conn_reset_conn_stat_cc(conn, cstat); + reset_conn_stat_recovery(cstat); +} + +static void delete_scid(ngtcp2_ksl *scids, const ngtcp2_mem *mem) { + ngtcp2_ksl_it it; + + for (it = ngtcp2_ksl_begin(scids); !ngtcp2_ksl_it_end(&it); + ngtcp2_ksl_it_next(&it)) { + ngtcp2_mem_free(mem, ngtcp2_ksl_it_get(&it)); + } +} + +/* + * compute_pto computes PTO. + */ +static ngtcp2_duration compute_pto(ngtcp2_duration smoothed_rtt, + ngtcp2_duration rttvar, + ngtcp2_duration max_ack_delay) { + ngtcp2_duration var = ngtcp2_max(4 * rttvar, NGTCP2_GRANULARITY); + return smoothed_rtt + var + max_ack_delay; +} + +/* + * conn_compute_initial_pto computes PTO using the initial RTT. + */ +static ngtcp2_duration conn_compute_initial_pto(ngtcp2_conn *conn, + ngtcp2_pktns *pktns) { + ngtcp2_duration initial_rtt = conn->local.settings.initial_rtt; + ngtcp2_duration max_ack_delay; + + if (pktns->rtb.pktns_id == NGTCP2_PKTNS_ID_APPLICATION && + conn->remote.transport_params) { + max_ack_delay = conn->remote.transport_params->max_ack_delay; + } else { + max_ack_delay = 0; + } + return compute_pto(initial_rtt, initial_rtt / 2, max_ack_delay); +} + +/* + * conn_compute_pto computes the current PTO. + */ +static ngtcp2_duration conn_compute_pto(ngtcp2_conn *conn, + ngtcp2_pktns *pktns) { + ngtcp2_conn_stat *cstat = &conn->cstat; + ngtcp2_duration max_ack_delay; + + if (pktns->rtb.pktns_id == NGTCP2_PKTNS_ID_APPLICATION && + conn->remote.transport_params) { + max_ack_delay = conn->remote.transport_params->max_ack_delay; + } else { + max_ack_delay = 0; + } + return compute_pto(cstat->smoothed_rtt, cstat->rttvar, max_ack_delay); +} + +ngtcp2_duration ngtcp2_conn_compute_pto(ngtcp2_conn *conn, + ngtcp2_pktns *pktns) { + return conn_compute_pto(conn, pktns); +} + +/* + * conn_compute_pv_timeout_pto returns path validation timeout using + * the given |pto|. + */ +static ngtcp2_duration conn_compute_pv_timeout_pto(ngtcp2_conn *conn, + ngtcp2_duration pto) { + ngtcp2_duration initial_pto = conn_compute_initial_pto(conn, &conn->pktns); + + return 3 * ngtcp2_max(pto, initial_pto); +} + +/* + * conn_compute_pv_timeout returns path validation timeout. + */ +static ngtcp2_duration conn_compute_pv_timeout(ngtcp2_conn *conn) { + return conn_compute_pv_timeout_pto(conn, + conn_compute_pto(conn, &conn->pktns)); +} + +static void conn_handle_tx_ecn(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, + uint16_t *prtb_entry_flags, ngtcp2_pktns *pktns, + const ngtcp2_pkt_hd *hd, ngtcp2_tstamp ts) { + assert(pi); + + if (pi->ecn != NGTCP2_ECN_NOT_ECT) { + /* We have already made a transition of validation state and + deceided to send UDP datagram with ECN bit set. Coalesced QUIC + packets also bear ECN bits set. */ + if (pktns->tx.ecn.start_pkt_num == INT64_MAX) { + pktns->tx.ecn.start_pkt_num = hd->pkt_num; + } + + ++pktns->tx.ecn.validation_pkt_sent; + + if (prtb_entry_flags) { + *prtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ECN; + } + + ++pktns->tx.ecn.ect0; + + return; + } + + switch (conn->tx.ecn.state) { + case NGTCP2_ECN_STATE_TESTING: + if (conn->tx.ecn.validation_start_ts == UINT64_MAX) { + assert(0 == pktns->tx.ecn.validation_pkt_sent); + assert(0 == pktns->tx.ecn.validation_pkt_lost); + + conn->tx.ecn.validation_start_ts = ts; + } else if (ts - conn->tx.ecn.validation_start_ts >= + 3 * conn_compute_pto(conn, pktns)) { + conn->tx.ecn.state = NGTCP2_ECN_STATE_UNKNOWN; + break; + } + + if (pktns->tx.ecn.start_pkt_num == INT64_MAX) { + pktns->tx.ecn.start_pkt_num = hd->pkt_num; + } + + ++pktns->tx.ecn.validation_pkt_sent; + + if (++conn->tx.ecn.dgram_sent == NGTCP2_ECN_MAX_NUM_VALIDATION_PKTS) { + conn->tx.ecn.state = NGTCP2_ECN_STATE_UNKNOWN; + } + /* fall through */ + case NGTCP2_ECN_STATE_CAPABLE: + /* pi is provided per UDP datagram. */ + assert(NGTCP2_ECN_NOT_ECT == pi->ecn); + + pi->ecn = NGTCP2_ECN_ECT_0; + + if (prtb_entry_flags) { + *prtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ECN; + } + + ++pktns->tx.ecn.ect0; + break; + case NGTCP2_ECN_STATE_UNKNOWN: + case NGTCP2_ECN_STATE_FAILED: + break; + default: + ngtcp2_unreachable(); + } +} + +static void conn_reset_ecn_validation_state(ngtcp2_conn *conn) { + ngtcp2_pktns *in_pktns = conn->in_pktns; + ngtcp2_pktns *hs_pktns = conn->hs_pktns; + ngtcp2_pktns *pktns = &conn->pktns; + + conn->tx.ecn.state = NGTCP2_ECN_STATE_TESTING; + conn->tx.ecn.validation_start_ts = UINT64_MAX; + conn->tx.ecn.dgram_sent = 0; + + if (in_pktns) { + in_pktns->tx.ecn.start_pkt_num = INT64_MAX; + in_pktns->tx.ecn.validation_pkt_sent = 0; + in_pktns->tx.ecn.validation_pkt_lost = 0; + } + + if (hs_pktns) { + hs_pktns->tx.ecn.start_pkt_num = INT64_MAX; + hs_pktns->tx.ecn.validation_pkt_sent = 0; + hs_pktns->tx.ecn.validation_pkt_lost = 0; + } + + pktns->tx.ecn.start_pkt_num = INT64_MAX; + pktns->tx.ecn.validation_pkt_sent = 0; + pktns->tx.ecn.validation_pkt_lost = 0; +} + +/* server_default_available_versions is the default available_versions + field sent by server. */ +static uint8_t server_default_available_versions[] = {0, 0, 0, 1}; + +/* + * available_versions_new allocates new buffer, and writes |versions| + * of length |versionslen| in network byte order, suitable for sending + * in available_versions field of version_information QUIC transport + * parameter. The pointer to the allocated buffer is assigned to + * |*pbuf|. + * + * This function returns 0 if it succeeds, or one of the negative + * error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +static int available_versions_new(uint8_t **pbuf, const uint32_t *versions, + size_t versionslen, const ngtcp2_mem *mem) { + size_t i; + uint8_t *buf = ngtcp2_mem_malloc(mem, sizeof(uint32_t) * versionslen); + + if (buf == NULL) { + return NGTCP2_ERR_NOMEM; + } + + *pbuf = buf; + + for (i = 0; i < versionslen; ++i) { + buf = ngtcp2_put_uint32be(buf, versions[i]); + } + + return 0; +} + +static void +conn_set_local_transport_params(ngtcp2_conn *conn, + const ngtcp2_transport_params *params) { + ngtcp2_transport_params *p = &conn->local.transport_params; + uint32_t chosen_version = p->version_info.chosen_version; + + *p = *params; + + if (conn->server) { + p->version_info.chosen_version = chosen_version; + } else { + p->version_info.chosen_version = conn->client_chosen_version; + } + p->version_info.available_versions = conn->vneg.available_versions; + p->version_info.available_versionslen = conn->vneg.available_versionslen; + p->version_info_present = 1; +} + +static int conn_new(ngtcp2_conn **pconn, const ngtcp2_cid *dcid, + const ngtcp2_cid *scid, const ngtcp2_path *path, + uint32_t client_chosen_version, int callbacks_version, + const ngtcp2_callbacks *callbacks, int settings_version, + const ngtcp2_settings *settings, + int transport_params_version, + const ngtcp2_transport_params *params, + const ngtcp2_mem *mem, void *user_data, int server) { + int rv; + ngtcp2_scid *scident; + uint8_t *buf; + uint8_t fixed_bit_byte; + size_t i; + uint32_t *preferred_versions; + ngtcp2_transport_params paramsbuf; + (void)callbacks_version; + (void)settings_version; + + params = ngtcp2_transport_params_convert_to_latest( + ¶msbuf, transport_params_version, params); + + 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->initial_pkt_num <= INT32_MAX); + assert(params->active_connection_id_limit >= + NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT); + assert(params->active_connection_id_limit <= NGTCP2_MAX_DCID_POOL_SIZE); + assert(params->initial_max_data <= NGTCP2_MAX_VARINT); + assert(params->initial_max_stream_data_bidi_local <= NGTCP2_MAX_VARINT); + assert(params->initial_max_stream_data_bidi_remote <= NGTCP2_MAX_VARINT); + assert(params->initial_max_stream_data_uni <= NGTCP2_MAX_VARINT); + assert((server && params->original_dcid_present) || + (!server && !params->original_dcid_present)); + assert(!params->initial_scid_present); + assert(server || !params->stateless_reset_token_present); + assert(server || !params->preferred_addr_present); + assert(server || !params->retry_scid_present); + assert(params->max_idle_timeout != UINT64_MAX); + assert(params->max_ack_delay < (1 << 14) * NGTCP2_MILLISECONDS); + assert(server || callbacks->client_initial); + assert(!server || callbacks->recv_client_initial); + assert(callbacks->recv_crypto_data); + assert(callbacks->encrypt); + assert(callbacks->decrypt); + assert(callbacks->hp_mask); + assert(server || callbacks->recv_retry); + assert(callbacks->rand); + assert(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(!server || !ngtcp2_is_reserved_version(client_chosen_version)); + + if (mem == NULL) { + mem = ngtcp2_mem_default(); + } + + *pconn = ngtcp2_mem_calloc(mem, 1, sizeof(ngtcp2_conn)); + if (*pconn == NULL) { + rv = NGTCP2_ERR_NOMEM; + goto fail_conn; + } + + (*pconn)->server = server; + + ngtcp2_objalloc_frame_chain_init(&(*pconn)->frc_objalloc, 64, mem); + ngtcp2_objalloc_rtb_entry_init(&(*pconn)->rtb_entry_objalloc, 64, mem); + ngtcp2_objalloc_strm_init(&(*pconn)->strm_objalloc, 64, mem); + + ngtcp2_static_ringbuf_dcid_bound_init(&(*pconn)->dcid.bound); + + ngtcp2_static_ringbuf_dcid_unused_init(&(*pconn)->dcid.unused); + + ngtcp2_static_ringbuf_dcid_retired_init(&(*pconn)->dcid.retired); + + ngtcp2_gaptr_init(&(*pconn)->dcid.seqgap, mem); + + ngtcp2_ksl_init(&(*pconn)->scid.set, cid_less, sizeof(ngtcp2_cid), mem); + + ngtcp2_pq_init(&(*pconn)->scid.used, retired_ts_less, mem); + + ngtcp2_map_init(&(*pconn)->strms, mem); + + ngtcp2_pq_init(&(*pconn)->tx.strmq, cycle_less, mem); + + ngtcp2_idtr_init(&(*pconn)->remote.bidi.idtr, !server, mem); + + ngtcp2_idtr_init(&(*pconn)->remote.uni.idtr, !server, mem); + + ngtcp2_static_ringbuf_path_challenge_init(&(*pconn)->rx.path_challenge); + + ngtcp2_log_init(&(*pconn)->log, scid, settings->log_printf, + settings->initial_ts, user_data); + ngtcp2_qlog_init(&(*pconn)->qlog, settings->qlog_write, settings->initial_ts, + user_data); + if ((*pconn)->qlog.write) { + buf = ngtcp2_mem_malloc(mem, NGTCP2_QLOG_BUFLEN); + if (buf == NULL) { + rv = NGTCP2_ERR_NOMEM; + goto fail_qlog_buf; + } + ngtcp2_buf_init(&(*pconn)->qlog.buf, buf, NGTCP2_QLOG_BUFLEN); + } + + (*pconn)->local.settings = *settings; + + if (settings->tokenlen) { + buf = ngtcp2_mem_malloc(mem, settings->tokenlen); + if (buf == NULL) { + rv = NGTCP2_ERR_NOMEM; + goto fail_token; + } + memcpy(buf, settings->token, settings->tokenlen); + (*pconn)->local.settings.token = buf; + } else { + (*pconn)->local.settings.token = NULL; + } + + if (!(*pconn)->local.settings.original_version) { + (*pconn)->local.settings.original_version = client_chosen_version; + } + + conn_reset_conn_stat(*pconn, &(*pconn)->cstat); + (*pconn)->cstat.initial_rtt = settings->initial_rtt; + (*pconn)->cstat.max_tx_udp_payload_size = + (*pconn)->local.settings.max_tx_udp_payload_size; + + ngtcp2_rst_init(&(*pconn)->rst); + + (*pconn)->cc_algo = settings->cc_algo; + + switch (settings->cc_algo) { + case NGTCP2_CC_ALGO_RENO: + ngtcp2_cc_reno_init(&(*pconn)->reno, &(*pconn)->log); + + break; + case NGTCP2_CC_ALGO_CUBIC: + ngtcp2_cc_cubic_init(&(*pconn)->cubic, &(*pconn)->log); + + 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); + + break; + default: + ngtcp2_unreachable(); + } + + rv = pktns_new(&(*pconn)->in_pktns, NGTCP2_PKTNS_ID_INITIAL, &(*pconn)->rst, + &(*pconn)->cc, settings->initial_pkt_num, &(*pconn)->log, + &(*pconn)->qlog, &(*pconn)->rtb_entry_objalloc, + &(*pconn)->frc_objalloc, mem); + if (rv != 0) { + goto fail_in_pktns_init; + } + + rv = pktns_new(&(*pconn)->hs_pktns, NGTCP2_PKTNS_ID_HANDSHAKE, &(*pconn)->rst, + &(*pconn)->cc, settings->initial_pkt_num, &(*pconn)->log, + &(*pconn)->qlog, &(*pconn)->rtb_entry_objalloc, + &(*pconn)->frc_objalloc, mem); + if (rv != 0) { + goto fail_hs_pktns_init; + } + + rv = pktns_init(&(*pconn)->pktns, NGTCP2_PKTNS_ID_APPLICATION, &(*pconn)->rst, + &(*pconn)->cc, settings->initial_pkt_num, &(*pconn)->log, + &(*pconn)->qlog, &(*pconn)->rtb_entry_objalloc, + &(*pconn)->frc_objalloc, mem); + if (rv != 0) { + goto fail_pktns_init; + } + + scident = ngtcp2_mem_malloc(mem, sizeof(*scident)); + if (scident == NULL) { + rv = NGTCP2_ERR_NOMEM; + goto fail_scident; + } + + /* Set stateless reset token later if it is available in the local + transport parameters */ + ngtcp2_scid_init(scident, 0, scid); + + rv = ngtcp2_ksl_insert(&(*pconn)->scid.set, NULL, &scident->cid, scident); + if (rv != 0) { + goto fail_scid_set_insert; + } + + scident = NULL; + + ngtcp2_dcid_init(&(*pconn)->dcid.current, 0, dcid, NULL); + ngtcp2_dcid_set_path(&(*pconn)->dcid.current, path); + + rv = ngtcp2_gaptr_push(&(*pconn)->dcid.seqgap, 0, 1); + if (rv != 0) { + goto fail_seqgap_push; + } + + if (settings->preferred_versionslen) { + if (!server && !ngtcp2_is_reserved_version(client_chosen_version)) { + for (i = 0; i < settings->preferred_versionslen; ++i) { + if (settings->preferred_versions[i] == client_chosen_version) { + break; + } + } + + assert(i < settings->preferred_versionslen); + } + + preferred_versions = ngtcp2_mem_malloc( + mem, sizeof(uint32_t) * settings->preferred_versionslen); + if (preferred_versions == NULL) { + rv = NGTCP2_ERR_NOMEM; + goto fail_preferred_versions; + } + + for (i = 0; i < settings->preferred_versionslen; ++i) { + assert(ngtcp2_is_supported_version(settings->preferred_versions[i])); + + preferred_versions[i] = settings->preferred_versions[i]; + } + + (*pconn)->vneg.preferred_versions = preferred_versions; + (*pconn)->vneg.preferred_versionslen = settings->preferred_versionslen; + } + + (*pconn)->local.settings.preferred_versions = NULL; + (*pconn)->local.settings.preferred_versionslen = 0; + + if (settings->available_versionslen) { + if (!server && !ngtcp2_is_reserved_version(client_chosen_version)) { + for (i = 0; i < settings->available_versionslen; ++i) { + if (settings->available_versions[i] == client_chosen_version) { + break; + } + } + + assert(i < settings->available_versionslen); + } + + for (i = 0; i < settings->available_versionslen; ++i) { + assert(ngtcp2_is_reserved_version(settings->available_versions[i]) || + ngtcp2_is_supported_version(settings->available_versions[i])); + } + + rv = available_versions_new(&buf, settings->available_versions, + settings->available_versionslen, mem); + if (rv != 0) { + goto fail_available_versions; + } + + (*pconn)->vneg.available_versions = buf; + (*pconn)->vneg.available_versionslen = + sizeof(uint32_t) * settings->available_versionslen; + } else if (server) { + if (settings->preferred_versionslen) { + rv = available_versions_new(&buf, settings->preferred_versions, + settings->preferred_versionslen, mem); + if (rv != 0) { + goto fail_available_versions; + } + + (*pconn)->vneg.available_versions = buf; + (*pconn)->vneg.available_versionslen = + sizeof(uint32_t) * settings->preferred_versionslen; + } else { + (*pconn)->vneg.available_versions = server_default_available_versions; + (*pconn)->vneg.available_versionslen = + sizeof(server_default_available_versions); + } + } else if (!server && !ngtcp2_is_reserved_version(client_chosen_version)) { + rv = available_versions_new(&buf, &client_chosen_version, 1, mem); + if (rv != 0) { + goto fail_available_versions; + } + + (*pconn)->vneg.available_versions = buf; + (*pconn)->vneg.available_versionslen = sizeof(uint32_t); + } + + (*pconn)->local.settings.available_versions = NULL; + (*pconn)->local.settings.available_versionslen = 0; + + (*pconn)->client_chosen_version = client_chosen_version; + + conn_set_local_transport_params(*pconn, params); + + callbacks->rand(&fixed_bit_byte, 1, &settings->rand_ctx); + if (fixed_bit_byte & 1) { + (*pconn)->flags |= NGTCP2_CONN_FLAG_CLEAR_FIXED_BIT; + } + + (*pconn)->keep_alive.last_ts = UINT64_MAX; + (*pconn)->keep_alive.timeout = UINT64_MAX; + + (*pconn)->oscid = *scid; + (*pconn)->callbacks = *callbacks; + (*pconn)->mem = mem; + (*pconn)->user_data = user_data; + (*pconn)->idle_ts = settings->initial_ts; + (*pconn)->crypto.key_update.confirmed_ts = UINT64_MAX; + (*pconn)->tx.last_max_data_ts = UINT64_MAX; + (*pconn)->tx.pacing.next_ts = UINT64_MAX; + (*pconn)->tx.last_blocked_offset = UINT64_MAX; + (*pconn)->early.discard_started_ts = UINT64_MAX; + + conn_reset_ecn_validation_state(*pconn); + + ngtcp2_qlog_start( + &(*pconn)->qlog, + server ? ((*pconn)->local.transport_params.retry_scid_present + ? &(*pconn)->local.transport_params.retry_scid + : &(*pconn)->local.transport_params.original_dcid) + : dcid, + server); + + return 0; + +fail_available_versions: + ngtcp2_mem_free(mem, (*pconn)->vneg.preferred_versions); +fail_preferred_versions: +fail_seqgap_push: +fail_scid_set_insert: + ngtcp2_mem_free(mem, scident); +fail_scident: + pktns_free(&(*pconn)->pktns, mem); +fail_pktns_init: + pktns_del((*pconn)->hs_pktns, mem); +fail_hs_pktns_init: + pktns_del((*pconn)->in_pktns, mem); +fail_in_pktns_init: + ngtcp2_mem_free(mem, (uint8_t *)(*pconn)->local.settings.token); +fail_token: + ngtcp2_mem_free(mem, (*pconn)->qlog.buf.begin); +fail_qlog_buf: + ngtcp2_idtr_free(&(*pconn)->remote.uni.idtr); + ngtcp2_idtr_free(&(*pconn)->remote.bidi.idtr); + ngtcp2_map_free(&(*pconn)->strms); + delete_scid(&(*pconn)->scid.set, mem); + ngtcp2_ksl_free(&(*pconn)->scid.set); + ngtcp2_gaptr_free(&(*pconn)->dcid.seqgap); + ngtcp2_objalloc_free(&(*pconn)->strm_objalloc); + ngtcp2_objalloc_free(&(*pconn)->rtb_entry_objalloc); + ngtcp2_objalloc_free(&(*pconn)->frc_objalloc); + ngtcp2_mem_free(mem, *pconn); +fail_conn: + return rv; +} + +int ngtcp2_conn_client_new_versioned( + ngtcp2_conn **pconn, const ngtcp2_cid *dcid, const ngtcp2_cid *scid, + const ngtcp2_path *path, uint32_t client_chosen_version, + int callbacks_version, const ngtcp2_callbacks *callbacks, + int settings_version, const ngtcp2_settings *settings, + int transport_params_version, const ngtcp2_transport_params *params, + const ngtcp2_mem *mem, void *user_data) { + int rv; + + rv = conn_new(pconn, dcid, scid, path, client_chosen_version, + callbacks_version, callbacks, settings_version, settings, + transport_params_version, params, mem, user_data, 0); + if (rv != 0) { + return rv; + } + (*pconn)->rcid = *dcid; + (*pconn)->state = NGTCP2_CS_CLIENT_INITIAL; + (*pconn)->local.bidi.next_stream_id = 0; + (*pconn)->local.uni.next_stream_id = 2; + + rv = ngtcp2_conn_commit_local_transport_params(*pconn); + if (rv != 0) { + ngtcp2_conn_del(*pconn); + return rv; + } + + return 0; +} + +int ngtcp2_conn_server_new_versioned( + ngtcp2_conn **pconn, const ngtcp2_cid *dcid, const ngtcp2_cid *scid, + const ngtcp2_path *path, uint32_t client_chosen_version, + int callbacks_version, const ngtcp2_callbacks *callbacks, + int settings_version, const ngtcp2_settings *settings, + int transport_params_version, const ngtcp2_transport_params *params, + const ngtcp2_mem *mem, void *user_data) { + int rv; + + rv = conn_new(pconn, dcid, scid, path, client_chosen_version, + callbacks_version, callbacks, settings_version, settings, + transport_params_version, params, mem, user_data, 1); + if (rv != 0) { + return rv; + } + + (*pconn)->state = NGTCP2_CS_SERVER_INITIAL; + (*pconn)->local.bidi.next_stream_id = 1; + (*pconn)->local.uni.next_stream_id = 3; + + if ((*pconn)->local.settings.tokenlen) { + /* Usage of token lifts amplification limit */ + (*pconn)->dcid.current.flags |= NGTCP2_DCID_FLAG_PATH_VALIDATED; + } + + return 0; +} + +/* + * conn_fc_credits returns the number of bytes allowed to be sent to + * the given stream. Both connection and stream level flow control + * credits are considered. + */ +static uint64_t conn_fc_credits(ngtcp2_conn *conn, ngtcp2_strm *strm) { + return ngtcp2_min(strm->tx.max_offset - strm->tx.offset, + conn->tx.max_offset - conn->tx.offset); +} + +/* + * conn_enforce_flow_control returns the number of bytes allowed to be + * sent to the given stream. |len| might be shorted because of + * available flow control credits. + */ +static uint64_t conn_enforce_flow_control(ngtcp2_conn *conn, ngtcp2_strm *strm, + uint64_t len) { + uint64_t fc_credits = conn_fc_credits(conn, strm); + return ngtcp2_min(len, fc_credits); +} + +static int delete_strms_each(void *data, void *ptr) { + ngtcp2_conn *conn = ptr; + ngtcp2_strm *s = data; + + ngtcp2_strm_free(s); + ngtcp2_objalloc_strm_release(&conn->strm_objalloc, s); + + return 0; +} + +static void conn_vneg_crypto_free(ngtcp2_conn *conn) { + if (conn->vneg.rx.ckm) { + conn_call_delete_crypto_aead_ctx(conn, &conn->vneg.rx.ckm->aead_ctx); + } + conn_call_delete_crypto_cipher_ctx(conn, &conn->vneg.rx.hp_ctx); + + if (conn->vneg.tx.ckm) { + conn_call_delete_crypto_aead_ctx(conn, &conn->vneg.tx.ckm->aead_ctx); + } + conn_call_delete_crypto_cipher_ctx(conn, &conn->vneg.tx.hp_ctx); + + ngtcp2_crypto_km_del(conn->vneg.rx.ckm, conn->mem); + ngtcp2_crypto_km_del(conn->vneg.tx.ckm, conn->mem); +} + +void ngtcp2_conn_del(ngtcp2_conn *conn) { + if (conn == NULL) { + return; + } + + ngtcp2_qlog_end(&conn->qlog); + + if (conn->early.ckm) { + conn_call_delete_crypto_aead_ctx(conn, &conn->early.ckm->aead_ctx); + } + conn_call_delete_crypto_cipher_ctx(conn, &conn->early.hp_ctx); + + if (conn->crypto.key_update.old_rx_ckm) { + conn_call_delete_crypto_aead_ctx( + conn, &conn->crypto.key_update.old_rx_ckm->aead_ctx); + } + if (conn->crypto.key_update.new_rx_ckm) { + conn_call_delete_crypto_aead_ctx( + conn, &conn->crypto.key_update.new_rx_ckm->aead_ctx); + } + if (conn->crypto.key_update.new_tx_ckm) { + conn_call_delete_crypto_aead_ctx( + conn, &conn->crypto.key_update.new_tx_ckm->aead_ctx); + } + + if (conn->pktns.crypto.rx.ckm) { + conn_call_delete_crypto_aead_ctx(conn, + &conn->pktns.crypto.rx.ckm->aead_ctx); + } + conn_call_delete_crypto_cipher_ctx(conn, &conn->pktns.crypto.rx.hp_ctx); + + if (conn->pktns.crypto.tx.ckm) { + conn_call_delete_crypto_aead_ctx(conn, + &conn->pktns.crypto.tx.ckm->aead_ctx); + } + conn_call_delete_crypto_cipher_ctx(conn, &conn->pktns.crypto.tx.hp_ctx); + + if (conn->hs_pktns) { + if (conn->hs_pktns->crypto.rx.ckm) { + conn_call_delete_crypto_aead_ctx( + conn, &conn->hs_pktns->crypto.rx.ckm->aead_ctx); + } + conn_call_delete_crypto_cipher_ctx(conn, &conn->hs_pktns->crypto.rx.hp_ctx); + + if (conn->hs_pktns->crypto.tx.ckm) { + conn_call_delete_crypto_aead_ctx( + conn, &conn->hs_pktns->crypto.tx.ckm->aead_ctx); + } + conn_call_delete_crypto_cipher_ctx(conn, &conn->hs_pktns->crypto.tx.hp_ctx); + } + if (conn->in_pktns) { + if (conn->in_pktns->crypto.rx.ckm) { + conn_call_delete_crypto_aead_ctx( + conn, &conn->in_pktns->crypto.rx.ckm->aead_ctx); + } + conn_call_delete_crypto_cipher_ctx(conn, &conn->in_pktns->crypto.rx.hp_ctx); + + if (conn->in_pktns->crypto.tx.ckm) { + conn_call_delete_crypto_aead_ctx( + conn, &conn->in_pktns->crypto.tx.ckm->aead_ctx); + } + conn_call_delete_crypto_cipher_ctx(conn, &conn->in_pktns->crypto.tx.hp_ctx); + } + + conn_call_delete_crypto_aead_ctx(conn, &conn->crypto.retry_aead_ctx); + + ngtcp2_transport_params_del(conn->remote.transport_params, conn->mem); + ngtcp2_transport_params_del(conn->remote.pending_transport_params, conn->mem); + + conn_vneg_crypto_free(conn); + + ngtcp2_mem_free(conn->mem, conn->vneg.preferred_versions); + if (conn->vneg.available_versions != server_default_available_versions) { + ngtcp2_mem_free(conn->mem, conn->vneg.available_versions); + } + + ngtcp2_mem_free(conn->mem, conn->crypto.decrypt_buf.base); + ngtcp2_mem_free(conn->mem, conn->crypto.decrypt_hp_buf.base); + ngtcp2_mem_free(conn->mem, (uint8_t *)conn->local.settings.token); + + ngtcp2_crypto_km_del(conn->crypto.key_update.old_rx_ckm, conn->mem); + ngtcp2_crypto_km_del(conn->crypto.key_update.new_rx_ckm, conn->mem); + ngtcp2_crypto_km_del(conn->crypto.key_update.new_tx_ckm, conn->mem); + ngtcp2_crypto_km_del(conn->early.ckm, conn->mem); + + pktns_free(&conn->pktns, conn->mem); + pktns_del(conn->hs_pktns, conn->mem); + pktns_del(conn->in_pktns, conn->mem); + + ngtcp2_mem_free(conn->mem, conn->qlog.buf.begin); + + ngtcp2_pmtud_del(conn->pmtud); + ngtcp2_pv_del(conn->pv); + + ngtcp2_mem_free(conn->mem, (uint8_t *)conn->rx.ccerr.reason); + + ngtcp2_idtr_free(&conn->remote.uni.idtr); + ngtcp2_idtr_free(&conn->remote.bidi.idtr); + ngtcp2_mem_free(conn->mem, conn->tx.ack); + ngtcp2_pq_free(&conn->tx.strmq); + ngtcp2_map_each_free(&conn->strms, delete_strms_each, (void *)conn); + ngtcp2_map_free(&conn->strms); + + ngtcp2_pq_free(&conn->scid.used); + delete_scid(&conn->scid.set, conn->mem); + ngtcp2_ksl_free(&conn->scid.set); + ngtcp2_gaptr_free(&conn->dcid.seqgap); + + ngtcp2_objalloc_free(&conn->strm_objalloc); + ngtcp2_objalloc_free(&conn->rtb_entry_objalloc); + ngtcp2_objalloc_free(&conn->frc_objalloc); + + ngtcp2_mem_free(conn->mem, conn); +} + +/* + * conn_ensure_ack_ranges makes sure that conn->tx.ack->ack.ranges can + * contain at least |n| additional ngtcp2_ack_range. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +static int conn_ensure_ack_ranges(ngtcp2_conn *conn, size_t n) { + ngtcp2_frame *fr; + size_t max = conn->tx.max_ack_ranges; + + if (n <= max) { + return 0; + } + + max *= 2; + + assert(max >= n); + + fr = ngtcp2_mem_realloc(conn->mem, conn->tx.ack, + sizeof(ngtcp2_ack) + sizeof(ngtcp2_ack_range) * max); + if (fr == NULL) { + return NGTCP2_ERR_NOMEM; + } + + conn->tx.ack = fr; + conn->tx.max_ack_ranges = max; + + return 0; +} + +/* + * conn_compute_ack_delay computes ACK delay for outgoing protected + * ACK. + */ +static ngtcp2_duration conn_compute_ack_delay(ngtcp2_conn *conn) { + return ngtcp2_min(conn->local.transport_params.max_ack_delay, + conn->cstat.smoothed_rtt / 8); +} + +int ngtcp2_conn_create_ack_frame(ngtcp2_conn *conn, ngtcp2_frame **pfr, + ngtcp2_pktns *pktns, uint8_t type, + ngtcp2_tstamp ts, ngtcp2_duration ack_delay, + uint64_t ack_delay_exponent) { + /* TODO Measure an actual size of ACK blocks to find the best + default value. */ + const size_t initial_max_ack_ranges = 8; + int64_t last_pkt_num; + ngtcp2_acktr *acktr = &pktns->acktr; + ngtcp2_ack_range *range; + ngtcp2_ksl_it it; + ngtcp2_acktr_entry *rpkt; + ngtcp2_ack *ack; + size_t range_idx; + ngtcp2_tstamp largest_ack_ts; + int rv; + + if (acktr->flags & NGTCP2_ACKTR_FLAG_IMMEDIATE_ACK) { + ack_delay = 0; + } + + if (!ngtcp2_acktr_require_active_ack(acktr, ack_delay, ts)) { + return 0; + } + + it = ngtcp2_acktr_get(acktr); + if (ngtcp2_ksl_it_end(&it)) { + ngtcp2_acktr_commit_ack(acktr); + return 0; + } + + if (conn->tx.ack == NULL) { + conn->tx.ack = ngtcp2_mem_malloc( + conn->mem, + sizeof(ngtcp2_ack) + sizeof(ngtcp2_ack_range) * initial_max_ack_ranges); + if (conn->tx.ack == NULL) { + return NGTCP2_ERR_NOMEM; + } + conn->tx.max_ack_ranges = initial_max_ack_ranges; + } + + ack = &conn->tx.ack->ack; + + if (pktns->rx.ecn.ect0 || pktns->rx.ecn.ect1 || pktns->rx.ecn.ce) { + ack->type = NGTCP2_FRAME_ACK_ECN; + ack->ecn.ect0 = pktns->rx.ecn.ect0; + ack->ecn.ect1 = pktns->rx.ecn.ect1; + ack->ecn.ce = pktns->rx.ecn.ce; + } else { + ack->type = NGTCP2_FRAME_ACK; + } + ack->rangecnt = 0; + + rpkt = ngtcp2_ksl_it_get(&it); + + if (rpkt->pkt_num == pktns->rx.max_pkt_num) { + last_pkt_num = rpkt->pkt_num - (int64_t)(rpkt->len - 1); + largest_ack_ts = rpkt->tstamp; + ack->largest_ack = rpkt->pkt_num; + ack->first_ack_range = rpkt->len - 1; + + ngtcp2_ksl_it_next(&it); + } else if (rpkt->pkt_num + 1 == pktns->rx.max_pkt_num) { + last_pkt_num = rpkt->pkt_num - (int64_t)(rpkt->len - 1); + largest_ack_ts = pktns->rx.max_pkt_ts; + ack->largest_ack = pktns->rx.max_pkt_num; + ack->first_ack_range = rpkt->len; + + ngtcp2_ksl_it_next(&it); + } else { + assert(rpkt->pkt_num < pktns->rx.max_pkt_num); + + last_pkt_num = pktns->rx.max_pkt_num; + largest_ack_ts = pktns->rx.max_pkt_ts; + ack->largest_ack = pktns->rx.max_pkt_num; + ack->first_ack_range = 0; + } + + if (type == NGTCP2_PKT_1RTT) { + ack->ack_delay_unscaled = ts - largest_ack_ts; + ack->ack_delay = ack->ack_delay_unscaled / NGTCP2_MICROSECONDS / + (1ULL << ack_delay_exponent); + } else { + ack->ack_delay_unscaled = 0; + ack->ack_delay = 0; + } + + for (; !ngtcp2_ksl_it_end(&it); ngtcp2_ksl_it_next(&it)) { + if (ack->rangecnt == NGTCP2_MAX_ACK_RANGES) { + break; + } + + rpkt = ngtcp2_ksl_it_get(&it); + + range_idx = ack->rangecnt++; + rv = conn_ensure_ack_ranges(conn, ack->rangecnt); + if (rv != 0) { + return rv; + } + ack = &conn->tx.ack->ack; + range = &ack->ranges[range_idx]; + range->gap = (uint64_t)(last_pkt_num - rpkt->pkt_num - 2); + range->len = rpkt->len - 1; + + last_pkt_num = rpkt->pkt_num - (int64_t)(rpkt->len - 1); + } + + /* TODO Just remove entries which cannot fit into a single ACK frame + for now. */ + if (!ngtcp2_ksl_it_end(&it)) { + ngtcp2_acktr_forget(acktr, ngtcp2_ksl_it_get(&it)); + } + + *pfr = conn->tx.ack; + + return 0; +} + +/* + * conn_ppe_write_frame writes |fr| to |ppe|. If |hd_logged| is not + * NULL and |*hd_logged| is zero, packet header is logged, and 1 is + * assigned to |*hd_logged|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer is too small. + */ +static int conn_ppe_write_frame_hd_log(ngtcp2_conn *conn, ngtcp2_ppe *ppe, + int *hd_logged, const ngtcp2_pkt_hd *hd, + ngtcp2_frame *fr) { + int rv; + + rv = ngtcp2_ppe_encode_frame(ppe, fr); + if (rv != 0) { + assert(NGTCP2_ERR_NOBUF == rv); + return rv; + } + + if (hd_logged && !*hd_logged) { + *hd_logged = 1; + ngtcp2_log_tx_pkt_hd(&conn->log, hd); + ngtcp2_qlog_pkt_sent_start(&conn->qlog); + } + + ngtcp2_log_tx_fr(&conn->log, hd, fr); + ngtcp2_qlog_write_frame(&conn->qlog, fr); + + return 0; +} + +/* + * conn_ppe_write_frame writes |fr| to |ppe|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer is too small. + */ +static int conn_ppe_write_frame(ngtcp2_conn *conn, ngtcp2_ppe *ppe, + const ngtcp2_pkt_hd *hd, ngtcp2_frame *fr) { + return conn_ppe_write_frame_hd_log(conn, ppe, NULL, hd, fr); +} + +/* + * conn_on_pkt_sent is called when new non-ACK-only packet is sent. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory + */ +static int conn_on_pkt_sent(ngtcp2_conn *conn, ngtcp2_rtb *rtb, + ngtcp2_rtb_entry *ent) { + int rv; + + /* This function implements OnPacketSent, but it handles only + non-ACK-only packet. */ + rv = ngtcp2_rtb_add(rtb, ent, &conn->cstat); + if (rv != 0) { + return rv; + } + + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) { + conn->cstat.last_tx_pkt_ts[rtb->pktns_id] = ent->ts; + } + + ngtcp2_conn_set_loss_detection_timer(conn, ent->ts); + + return 0; +} + +/* + * pktns_select_pkt_numlen selects shortest packet number encoding for + * the next packet number based on the largest acknowledged packet + * number. It returns the number of bytes to encode the packet + * number. + */ +static size_t pktns_select_pkt_numlen(ngtcp2_pktns *pktns) { + int64_t pkt_num = pktns->tx.last_pkt_num + 1; + ngtcp2_rtb *rtb = &pktns->rtb; + int64_t n = pkt_num - rtb->largest_acked_tx_pkt_num; + + if (NGTCP2_MAX_PKT_NUM / 2 < n) { + return 4; + } + + n = n * 2 - 1; + + if (n > 0xffffff) { + return 4; + } + if (n > 0xffff) { + return 3; + } + if (n > 0xff) { + return 2; + } + return 1; +} + +/* + * conn_get_cwnd returns cwnd for the current path. + */ +static uint64_t conn_get_cwnd(ngtcp2_conn *conn) { + return conn->pv && (conn->pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) + ? ngtcp2_cc_compute_initcwnd(conn->cstat.max_tx_udp_payload_size) + : conn->cstat.cwnd; +} + +/* + * conn_cwnd_is_zero returns nonzero if the number of bytes the local + * endpoint can sent at this time is zero. + */ +static int conn_cwnd_is_zero(ngtcp2_conn *conn) { + uint64_t bytes_in_flight = conn->cstat.bytes_in_flight; + uint64_t cwnd = conn_get_cwnd(conn); + + 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); + } + + return bytes_in_flight >= cwnd; +} + +/* + * conn_retry_early_payloadlen returns the estimated wire length of + * the first STREAM frame of 0-RTT packet which should be + * retransmitted due to Retry packet. + */ +static uint64_t conn_retry_early_payloadlen(ngtcp2_conn *conn) { + ngtcp2_frame_chain *frc; + ngtcp2_strm *strm; + uint64_t len; + + if (conn->flags & NGTCP2_CONN_FLAG_EARLY_DATA_REJECTED) { + return 0; + } + + for (; !ngtcp2_pq_empty(&conn->tx.strmq);) { + strm = ngtcp2_conn_tx_strmq_top(conn); + if (ngtcp2_strm_streamfrq_empty(strm)) { + ngtcp2_conn_tx_strmq_pop(conn); + continue; + } + + frc = ngtcp2_strm_streamfrq_top(strm); + + len = ngtcp2_vec_len(frc->fr.stream.data, frc->fr.stream.datacnt) + + NGTCP2_STREAM_OVERHEAD; + + /* Take the min because in conn_should_pad_pkt we take max in + order to deal with unbreakable DATAGRAM. */ + return ngtcp2_min(len, NGTCP2_MIN_COALESCED_PAYLOADLEN); + } + + return 0; +} + +/* + * conn_verify_dcid verifies that destination connection ID in |hd| is + * valid for the connection. If it is successfully verified and the + * remote endpoint uses new DCID in the packet, nonzero value is + * assigned to |*pnew_cid_used| if it is not NULL. Otherwise 0 is + * assigned to it. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + * NGTCP2_ERR_INVALID_ARGUMENT + * |dcid| is not known to the local endpoint. + */ +static int conn_verify_dcid(ngtcp2_conn *conn, int *pnew_cid_used, + const ngtcp2_pkt_hd *hd) { + ngtcp2_ksl_it it; + ngtcp2_scid *scid; + int rv; + + it = ngtcp2_ksl_lower_bound(&conn->scid.set, &hd->dcid); + if (ngtcp2_ksl_it_end(&it)) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + scid = ngtcp2_ksl_it_get(&it); + if (!ngtcp2_cid_eq(&scid->cid, &hd->dcid)) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + if (!(scid->flags & NGTCP2_SCID_FLAG_USED)) { + scid->flags |= NGTCP2_SCID_FLAG_USED; + + if (scid->pe.index == NGTCP2_PQ_BAD_INDEX) { + rv = ngtcp2_pq_push(&conn->scid.used, &scid->pe); + if (rv != 0) { + return rv; + } + } + + if (pnew_cid_used) { + *pnew_cid_used = 1; + } + } else if (pnew_cid_used) { + *pnew_cid_used = 0; + } + + return 0; +} + +/* + * 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 + * buffer. |write_datalen| is the number of bytes which will be sent + * in the next, coalesced 0-RTT packet. + */ +static int conn_should_pad_pkt(ngtcp2_conn *conn, uint8_t type, size_t left, + uint64_t write_datalen, int ack_eliciting, + int require_padding) { + uint64_t min_payloadlen; + + if (type == NGTCP2_PKT_INITIAL) { + if (conn->server) { + if (!ack_eliciting) { + return 0; + } + + if (conn->hs_pktns->crypto.tx.ckm && + (conn->hs_pktns->rtb.probe_pkt_left || + !ngtcp2_strm_streamfrq_empty(&conn->hs_pktns->crypto.strm) || + !ngtcp2_acktr_empty(&conn->hs_pktns->acktr))) { + /* If we have something to send in Handshake packet, then add + PADDING in Handshake packet. */ + min_payloadlen = NGTCP2_MIN_COALESCED_PAYLOADLEN; + } else { + return 1; + } + } else { + if (conn->hs_pktns->crypto.tx.ckm && + (conn->hs_pktns->rtb.probe_pkt_left || + !ngtcp2_strm_streamfrq_empty(&conn->hs_pktns->crypto.strm) || + !ngtcp2_acktr_empty(&conn->hs_pktns->acktr))) { + /* If we have something to send in Handshake packet, then add + PADDING in Handshake packet. */ + min_payloadlen = NGTCP2_MIN_COALESCED_PAYLOADLEN; + } else if (conn->early.ckm && write_datalen > 0) { + /* If we have something to send in 0RTT packet, then add + PADDING in that packet. Take maximum in case that + write_datalen includes DATAGRAM which cannot be split. */ + min_payloadlen = + ngtcp2_max(write_datalen, NGTCP2_MIN_COALESCED_PAYLOADLEN); + } else { + return 1; + } + } + } else { + assert(type == NGTCP2_PKT_HANDSHAKE); + + if (!require_padding) { + return 0; + } + + if (!conn->pktns.crypto.tx.ckm) { + return 1; + } + + min_payloadlen = 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; +} + +static void conn_restart_timer_on_write(ngtcp2_conn *conn, ngtcp2_tstamp ts) { + conn->idle_ts = ts; + conn->flags &= (uint32_t)~NGTCP2_CONN_FLAG_RESTART_IDLE_TIMER_ON_WRITE; +} + +static void conn_restart_timer_on_read(ngtcp2_conn *conn, ngtcp2_tstamp ts) { + conn->idle_ts = ts; + conn->flags |= NGTCP2_CONN_FLAG_RESTART_IDLE_TIMER_ON_WRITE; +} + +/* + * conn_keep_alive_enabled returns nonzero if keep-alive is enabled. + */ +static int conn_keep_alive_enabled(ngtcp2_conn *conn) { + return conn->keep_alive.last_ts != UINT64_MAX && + conn->keep_alive.timeout != UINT64_MAX; +} + +/* + * conn_keep_alive_expired returns nonzero if keep-alive timer has + * expired. + */ +static int conn_keep_alive_expired(ngtcp2_conn *conn, ngtcp2_tstamp ts) { + return ngtcp2_tstamp_elapsed(conn->keep_alive.last_ts, + conn->keep_alive.timeout, ts); +} + +/* + * conn_keep_alive_expiry returns the expiry time of keep-alive timer. + */ +static ngtcp2_tstamp conn_keep_alive_expiry(ngtcp2_conn *conn) { + if ((conn->flags & NGTCP2_CONN_FLAG_KEEP_ALIVE_CANCELLED) || + !(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_COMPLETED) || + !conn_keep_alive_enabled(conn) || + conn->keep_alive.last_ts >= UINT64_MAX - conn->keep_alive.timeout) { + return UINT64_MAX; + } + + return conn->keep_alive.last_ts + conn->keep_alive.timeout; +} + +/* + * conn_cancel_expired_keep_alive_timer cancels the expired keep-alive + * timer. + */ +static void conn_cancel_expired_keep_alive_timer(ngtcp2_conn *conn, + ngtcp2_tstamp ts) { + if (!(conn->flags & NGTCP2_CONN_FLAG_KEEP_ALIVE_CANCELLED) && + conn_keep_alive_expired(conn, ts)) { + conn->flags |= NGTCP2_CONN_FLAG_KEEP_ALIVE_CANCELLED; + } +} + +/* + * conn_update_keep_alive_last_ts updates the base time point of + * keep-alive timer. + */ +static void conn_update_keep_alive_last_ts(ngtcp2_conn *conn, + ngtcp2_tstamp ts) { + conn->keep_alive.last_ts = ts; + conn->flags &= (uint32_t)~NGTCP2_CONN_FLAG_KEEP_ALIVE_CANCELLED; +} + +void ngtcp2_conn_set_keep_alive_timeout(ngtcp2_conn *conn, + ngtcp2_duration timeout) { + if (timeout == 0) { + timeout = UINT64_MAX; + } + + conn->keep_alive.timeout = timeout; +} + +/* + * NGTCP2_PKT_PACING_OVERHEAD defines overhead of userspace event + * loop. Packet pacing might require sub milliseconds packet spacing, + * but userspace event loop might not offer such precision. + * Typically, if delay is 0.5 microseconds, the actual delay after + * which we can send packet is well over 1 millisecond when event loop + * is involved (which includes other stuff, like reading packets etc + * in a typical single threaded use case). + */ +#define NGTCP2_PKT_PACING_OVERHEAD NGTCP2_MILLISECONDS + +static void conn_cancel_expired_pkt_tx_timer(ngtcp2_conn *conn, + ngtcp2_tstamp ts) { + if (conn->tx.pacing.next_ts == UINT64_MAX) { + return; + } + + if (conn->tx.pacing.next_ts > ts + NGTCP2_PKT_PACING_OVERHEAD) { + return; + } + + conn->tx.pacing.next_ts = UINT64_MAX; +} + +static int conn_pacing_pkt_tx_allowed(ngtcp2_conn *conn, ngtcp2_tstamp ts) { + return conn->tx.pacing.next_ts == UINT64_MAX || + conn->tx.pacing.next_ts <= ts + NGTCP2_PKT_PACING_OVERHEAD; +} + +static uint8_t conn_pkt_flags(ngtcp2_conn *conn) { + if (conn->remote.transport_params && + conn->remote.transport_params->grease_quic_bit && + (conn->flags & NGTCP2_CONN_FLAG_CLEAR_FIXED_BIT)) { + return NGTCP2_PKT_FLAG_FIXED_BIT_CLEAR; + } + + return NGTCP2_PKT_FLAG_NONE; +} + +static uint8_t conn_pkt_flags_long(ngtcp2_conn *conn) { + return NGTCP2_PKT_FLAG_LONG_FORM | conn_pkt_flags(conn); +} + +static uint8_t conn_pkt_flags_short(ngtcp2_conn *conn) { + return (uint8_t)(conn_pkt_flags(conn) | ((conn->pktns.crypto.tx.ckm->flags & + NGTCP2_CRYPTO_KM_FLAG_KEY_PHASE_ONE) + ? NGTCP2_PKT_FLAG_KEY_PHASE + : NGTCP2_PKT_FLAG_NONE)); +} + +/* + * conn_write_handshake_pkt writes handshake packet in the buffer + * pointed by |dest| whose length is |destlen|. |type| specifies long + * packet type. It should be either NGTCP2_PKT_INITIAL or + * NGTCP2_PKT_HANDSHAKE_PKT. + * + * |write_datalen| is the minimum length of application data ready to + * send in subsequent 0RTT packet. + * + * This function returns the number of bytes written in |dest| if it + * succeeds, or one of the following negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + */ +static ngtcp2_ssize +conn_write_handshake_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, uint8_t *dest, + size_t destlen, uint8_t type, uint8_t flags, + uint64_t write_datalen, ngtcp2_tstamp ts) { + int rv; + ngtcp2_ppe ppe; + ngtcp2_pkt_hd hd; + ngtcp2_frame_chain *frq = NULL, **pfrc = &frq; + ngtcp2_frame_chain *nfrc; + ngtcp2_frame *ackfr = NULL, lfr; + ngtcp2_ssize spktlen; + ngtcp2_crypto_cc cc; + ngtcp2_rtb_entry *rtbent; + ngtcp2_pktns *pktns; + size_t left; + uint16_t rtb_entry_flags = NGTCP2_RTB_ENTRY_FLAG_NONE; + int require_padding = (flags & NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING) != 0; + int pkt_empty = 1; + int padded = 0; + int hd_logged = 0; + uint64_t crypto_offset; + ngtcp2_ssize num_reclaimed; + uint32_t version; + + switch (type) { + case NGTCP2_PKT_INITIAL: + if (!conn->in_pktns) { + return 0; + } + assert(conn->in_pktns->crypto.tx.ckm); + pktns = conn->in_pktns; + version = conn->negotiated_version ? conn->negotiated_version + : conn->client_chosen_version; + if (version == conn->client_chosen_version) { + cc.ckm = pktns->crypto.tx.ckm; + cc.hp_ctx = pktns->crypto.tx.hp_ctx; + } else { + assert(conn->vneg.version == version); + + cc.ckm = conn->vneg.tx.ckm; + cc.hp_ctx = conn->vneg.tx.hp_ctx; + } + break; + case NGTCP2_PKT_HANDSHAKE: + if (!conn->hs_pktns || !conn->hs_pktns->crypto.tx.ckm) { + return 0; + } + pktns = conn->hs_pktns; + version = conn->negotiated_version; + cc.ckm = pktns->crypto.tx.ckm; + cc.hp_ctx = pktns->crypto.tx.hp_ctx; + break; + default: + ngtcp2_unreachable(); + } + + cc.aead = pktns->crypto.ctx.aead; + cc.hp = pktns->crypto.ctx.hp; + cc.encrypt = conn->callbacks.encrypt; + cc.hp_mask = conn->callbacks.hp_mask; + + ngtcp2_pkt_hd_init(&hd, conn_pkt_flags_long(conn), type, + &conn->dcid.current.cid, &conn->oscid, + pktns->tx.last_pkt_num + 1, pktns_select_pkt_numlen(pktns), + version, 0); + + if (!conn->server && type == NGTCP2_PKT_INITIAL && + conn->local.settings.tokenlen) { + hd.token = conn->local.settings.token; + hd.tokenlen = conn->local.settings.tokenlen; + } + + ngtcp2_ppe_init(&ppe, dest, destlen, &cc); + + rv = ngtcp2_ppe_encode_hd(&ppe, &hd); + if (rv != 0) { + assert(NGTCP2_ERR_NOBUF == rv); + return 0; + } + + if (!ngtcp2_ppe_ensure_hp_sample(&ppe)) { + return 0; + } + + rv = ngtcp2_conn_create_ack_frame(conn, &ackfr, pktns, type, ts, + /* ack_delay = */ 0, + NGTCP2_DEFAULT_ACK_DELAY_EXPONENT); + if (rv != 0) { + ngtcp2_frame_chain_list_objalloc_del(frq, &conn->frc_objalloc, conn->mem); + return rv; + } + + if (ackfr) { + rv = conn_ppe_write_frame_hd_log(conn, &ppe, &hd_logged, &hd, ackfr); + 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); + pkt_empty = 0; + } + } + + /* Server requires at least NGTCP2_MAX_UDP_PAYLOAD_SIZE bytes in + order to send ack-eliciting Initial packet. */ + if (!conn->server || type != NGTCP2_PKT_INITIAL || + 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) { + 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; + + pkt_empty = 0; + rtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE; + } + + 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); + if (num_reclaimed < 0) { + ngtcp2_frame_chain_list_objalloc_del(frq, &conn->frc_objalloc, + conn->mem); + return rv; + } + if (num_reclaimed) { + goto build_pkt; + } + /* We had pktns->rtb.num_retransmittable > 0 but the contents of + those packets have been acknowledged (i.e., retransmission in + another packet). For server, in this case, we don't have to + send any probe packet. Client needs to send probe packets + until it knows that server has completed address validation or + handshake has been confirmed. */ + if (pktns->rtb.num_pto_eliciting == 0 && + (conn->server || + (conn->flags & (NGTCP2_CONN_FLAG_SERVER_ADDR_VERIFIED | + NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED)))) { + pktns->rtb.probe_pkt_left = 0; + ngtcp2_conn_set_loss_detection_timer(conn, ts); + /* TODO If packet is empty, we should return now if cwnd is + zero. */ + } + } + + if (!(rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) && + pktns->rtb.probe_pkt_left) { + lfr.type = NGTCP2_FRAME_PING; + + rv = conn_ppe_write_frame_hd_log(conn, &ppe, &hd_logged, &hd, &lfr); + if (rv != 0) { + assert(rv == NGTCP2_ERR_NOBUF); + } else { + rtb_entry_flags |= + NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING | NGTCP2_RTB_ENTRY_FLAG_PROBE; + pkt_empty = 0; + } + } + + if (!pkt_empty) { + 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; + + rv = conn_ppe_write_frame_hd_log(conn, &ppe, &hd_logged, &hd, &lfr); + if (rv != 0) { + assert(rv == NGTCP2_ERR_NOBUF); + } else { + rtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING; + pktns->tx.non_ack_pkt_start_ts = UINT64_MAX; + } + } else if (pktns->tx.non_ack_pkt_start_ts == UINT64_MAX) { + pktns->tx.non_ack_pkt_start_ts = ts; + } + } else { + pktns->tx.non_ack_pkt_start_ts = UINT64_MAX; + } + } + } + + if (pkt_empty && !require_padding) { + return 0; + } + + /* If we cannot write another packet, then we need to add padding to + Initial here. */ + if (conn_should_pad_pkt( + 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_padding(&ppe); + } else if (pkt_empty) { + return 0; + } else { + lfr.type = NGTCP2_FRAME_PADDING; + lfr.padding.len = ngtcp2_ppe_padding_hp_sample(&ppe); + } + + if (lfr.padding.len) { + padded = 1; + ngtcp2_log_tx_fr(&conn->log, &hd, &lfr); + ngtcp2_qlog_write_frame(&conn->qlog, &lfr); + } + + spktlen = ngtcp2_ppe_final(&ppe, NULL); + if (spktlen < 0) { + assert(ngtcp2_err_is_fatal((int)spktlen)); + ngtcp2_frame_chain_list_objalloc_del(frq, &conn->frc_objalloc, conn->mem); + return spktlen; + } + + ngtcp2_qlog_pkt_sent_end(&conn->qlog, &hd, (size_t)spktlen); + + if ((rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) || padded) { + if (pi) { + conn_handle_tx_ecn(conn, pi, &rtb_entry_flags, pktns, &hd, ts); + } + + rv = ngtcp2_rtb_entry_objalloc_new(&rtbent, &hd, frq, ts, (size_t)spktlen, + rtb_entry_flags, + &conn->rtb_entry_objalloc); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + ngtcp2_frame_chain_list_objalloc_del(frq, &conn->frc_objalloc, conn->mem); + return rv; + } + + rv = conn_on_pkt_sent(conn, &pktns->rtb, rtbent); + if (rv != 0) { + ngtcp2_rtb_entry_objalloc_del(rtbent, &conn->rtb_entry_objalloc, + &conn->frc_objalloc, conn->mem); + return rv; + } + + 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); + } + + if (pktns->rtb.probe_pkt_left && + (rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING)) { + --pktns->rtb.probe_pkt_left; + } + + conn_update_keep_alive_last_ts(conn, ts); + + conn->dcid.current.bytes_sent += (uint64_t)spktlen; + + conn->tx.pacing.pktlen += (size_t)spktlen; + + ngtcp2_qlog_metrics_updated(&conn->qlog, &conn->cstat); + + ++pktns->tx.last_pkt_num; + + return spktlen; +} + +/* + * conn_write_ack_pkt writes QUIC packet for type |type| which only + * includes ACK frame in the buffer pointed by |dest| whose length is + * |destlen|. + * + * This function returns the number of bytes written in |dest| if it + * succeeds, or one of the following negative error codes: + * + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +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) { + int rv; + ngtcp2_frame *ackfr; + ngtcp2_pktns *pktns; + ngtcp2_duration ack_delay; + uint64_t ack_delay_exponent; + ngtcp2_ssize spktlen; + + assert(!(conn->flags & NGTCP2_CONN_FLAG_PPE_PENDING)); + + switch (type) { + case NGTCP2_PKT_INITIAL: + assert(conn->server); + pktns = conn->in_pktns; + ack_delay = 0; + ack_delay_exponent = NGTCP2_DEFAULT_ACK_DELAY_EXPONENT; + break; + case NGTCP2_PKT_HANDSHAKE: + pktns = conn->hs_pktns; + ack_delay = 0; + ack_delay_exponent = NGTCP2_DEFAULT_ACK_DELAY_EXPONENT; + break; + case NGTCP2_PKT_1RTT: + pktns = &conn->pktns; + ack_delay = conn_compute_ack_delay(conn); + ack_delay_exponent = conn->local.transport_params.ack_delay_exponent; + break; + default: + ngtcp2_unreachable(); + } + + if (!pktns->crypto.tx.ckm) { + return 0; + } + + ackfr = NULL; + rv = ngtcp2_conn_create_ack_frame(conn, &ackfr, pktns, type, ts, ack_delay, + ack_delay_exponent); + if (rv != 0) { + return rv; + } + + if (!ackfr) { + 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); + + if (spktlen <= 0) { + return spktlen; + } + + conn->dcid.current.bytes_sent += (uint64_t)spktlen; + + return spktlen; +} + +static void conn_discard_pktns(ngtcp2_conn *conn, ngtcp2_pktns **ppktns, + ngtcp2_tstamp ts) { + ngtcp2_pktns *pktns = *ppktns; + uint64_t bytes_in_flight; + + bytes_in_flight = pktns->rtb.cc_bytes_in_flight; + + assert(conn->cstat.bytes_in_flight >= bytes_in_flight); + + conn->cstat.bytes_in_flight -= bytes_in_flight; + conn->cstat.pto_count = 0; + conn->cstat.last_tx_pkt_ts[pktns->rtb.pktns_id] = UINT64_MAX; + conn->cstat.loss_time[pktns->rtb.pktns_id] = UINT64_MAX; + + conn_call_delete_crypto_aead_ctx(conn, &pktns->crypto.rx.ckm->aead_ctx); + conn_call_delete_crypto_cipher_ctx(conn, &pktns->crypto.rx.hp_ctx); + conn_call_delete_crypto_aead_ctx(conn, &pktns->crypto.tx.ckm->aead_ctx); + conn_call_delete_crypto_cipher_ctx(conn, &pktns->crypto.tx.hp_ctx); + + pktns_del(pktns, conn->mem); + *ppktns = NULL; + + ngtcp2_conn_set_loss_detection_timer(conn, ts); +} + +/* + * conn_discard_initial_state discards state for Initial packet number + * space. + */ +static void conn_discard_initial_state(ngtcp2_conn *conn, ngtcp2_tstamp ts) { + if (!conn->in_pktns) { + return; + } + + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, + "discarding Initial packet number space"); + + conn_discard_pktns(conn, &conn->in_pktns, ts); + + conn_vneg_crypto_free(conn); + + memset(&conn->vneg.rx, 0, sizeof(conn->vneg.rx)); + memset(&conn->vneg.tx, 0, sizeof(conn->vneg.tx)); +} + +/* + * conn_discard_handshake_state discards state for Handshake packet + * number space. + */ +static void conn_discard_handshake_state(ngtcp2_conn *conn, ngtcp2_tstamp ts) { + if (!conn->hs_pktns) { + return; + } + + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, + "discarding Handshake packet number space"); + + conn_discard_pktns(conn, &conn->hs_pktns, ts); +} + +/* + * conn_discard_early_key discards early key. + */ +static void conn_discard_early_key(ngtcp2_conn *conn) { + assert(conn->early.ckm); + + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, "discarding early key"); + + 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)); + + ngtcp2_crypto_km_del(conn->early.ckm, conn->mem); + conn->early.ckm = NULL; +} + +/* + * conn_write_handshake_ack_pkts writes packets which contain ACK + * frame only. This function writes at most 2 packets for each + * Initial and Handshake packet. + */ +static ngtcp2_ssize conn_write_handshake_ack_pkts(ngtcp2_conn *conn, + ngtcp2_pkt_info *pi, + uint8_t *dest, size_t destlen, + ngtcp2_tstamp ts) { + ngtcp2_ssize res = 0, nwrite = 0; + + /* In the most cases, client sends ACK in conn_write_handshake_pkt. + This function is only called when it is CWND limited or pacing + limited. It is not required for client to send ACK for server + Initial. This is because once it gets server Initial, it gets + Handshake tx key and discards Initial key. The only good reason + to send ACK is give server RTT measurement early. */ + if (conn->server && conn->in_pktns) { + nwrite = + conn_write_ack_pkt(conn, pi, dest, destlen, NGTCP2_PKT_INITIAL, ts); + if (nwrite < 0) { + assert(nwrite != NGTCP2_ERR_NOBUF); + return nwrite; + } + + res += nwrite; + dest += nwrite; + destlen -= (size_t)nwrite; + } + + if (conn->hs_pktns->crypto.tx.ckm) { + nwrite = + conn_write_ack_pkt(conn, pi, dest, destlen, NGTCP2_PKT_HANDSHAKE, ts); + if (nwrite < 0) { + assert(nwrite != NGTCP2_ERR_NOBUF); + return nwrite; + } + + res += nwrite; + + if (!conn->server && nwrite) { + conn_discard_initial_state(conn, ts); + } + } + + return res; +} + +/* + * conn_write_client_initial writes Initial packet in the buffer + * pointed by |dest| whose length is |destlen|. + * + * This function returns the number of bytes written in |dest| if it + * succeeds, or one of the following negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + */ +static ngtcp2_ssize conn_write_client_initial(ngtcp2_conn *conn, + ngtcp2_pkt_info *pi, + uint8_t *dest, size_t destlen, + uint64_t early_datalen, + ngtcp2_tstamp ts) { + int rv; + + rv = conn_call_client_initial(conn); + if (rv != 0) { + return rv; + } + + return conn_write_handshake_pkt(conn, pi, dest, destlen, NGTCP2_PKT_INITIAL, + NGTCP2_WRITE_PKT_FLAG_NONE, early_datalen, + ts); +} + +/* + * dcid_tx_left returns the maximum number of bytes that server is + * allowed to send to an unvalidated path associated to |dcid|. + */ +static uint64_t dcid_tx_left(ngtcp2_dcid *dcid) { + if (dcid->flags & NGTCP2_DCID_FLAG_PATH_VALIDATED) { + return SIZE_MAX; + } + /* From QUIC spec: Prior to validating the client address, servers + MUST NOT send more than three times as many bytes as the number + of bytes they have received. */ + assert(dcid->bytes_recv * 3 >= dcid->bytes_sent); + + return dcid->bytes_recv * 3 - dcid->bytes_sent; +} + +/* + * conn_server_tx_left returns the maximum number of bytes that server + * is allowed to send to an unvalidated path. + */ +static uint64_t conn_server_tx_left(ngtcp2_conn *conn, ngtcp2_dcid *dcid) { + assert(conn->server); + + /* If pv->dcid has the current path, use conn->dcid.current. This + is because conn->dcid.current gets update for bytes_recv and + bytes_sent. */ + if (ngtcp2_path_eq(&dcid->ps.path, &conn->dcid.current.ps.path)) { + return dcid_tx_left(&conn->dcid.current); + } + + return dcid_tx_left(dcid); +} + +/* + * conn_write_handshake_pkts writes Initial and Handshake packets in + * the buffer pointed by |dest| whose length is |destlen|. + * + * This function returns the number of bytes written in |dest| if it + * succeeds, or one of the following negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + */ +static ngtcp2_ssize conn_write_handshake_pkts(ngtcp2_conn *conn, + ngtcp2_pkt_info *pi, + uint8_t *dest, size_t destlen, + uint64_t write_datalen, + ngtcp2_tstamp ts) { + ngtcp2_ssize nwrite; + ngtcp2_ssize res = 0; + ngtcp2_rtb_entry *rtbent; + uint8_t wflags = NGTCP2_WRITE_PKT_FLAG_NONE; + ngtcp2_conn_stat *cstat = &conn->cstat; + ngtcp2_ksl_it it; + + /* As a client, we would like to discard Initial packet number space + when sending the first Handshake packet. When sending Handshake + packet, it should be one of 1) sending ACK, 2) sending PTO probe + packet, or 3) sending CRYPTO. If we have pending acknowledgement + for Initial, then do not discard Initial packet number space. + Otherwise, if either 1) or 2) is satisfied, discard Initial + packet number space. When sending Handshake CRYPTO, it indicates + that client has received Handshake CRYPTO from server. Initial + packet number space is discarded because 1) is met. If there is + pending Initial ACK, Initial packet number space is discarded + after writing the first Handshake packet. + */ + if (!conn->server && conn->hs_pktns->crypto.tx.ckm && conn->in_pktns && + !ngtcp2_acktr_require_active_ack(&conn->in_pktns->acktr, + /* max_ack_delay = */ 0, ts) && + (ngtcp2_acktr_require_active_ack(&conn->hs_pktns->acktr, + /* max_ack_delay = */ 0, ts) || + conn->hs_pktns->rtb.probe_pkt_left)) { + /* Discard Initial state here so that Handshake packet is not + padded. */ + conn_discard_initial_state(conn, ts); + } else if (conn->in_pktns) { + nwrite = + conn_write_handshake_pkt(conn, pi, dest, destlen, NGTCP2_PKT_INITIAL, + NGTCP2_WRITE_PKT_FLAG_NONE, write_datalen, ts); + if (nwrite < 0) { + assert(nwrite != NGTCP2_ERR_NOBUF); + return nwrite; + } + + if (nwrite == 0) { + if (conn->server && + (conn->in_pktns->rtb.probe_pkt_left || + !ngtcp2_strm_streamfrq_empty(&conn->in_pktns->crypto.strm))) { + if (cstat->loss_detection_timer != UINT64_MAX && + conn_server_tx_left(conn, &conn->dcid.current) < + NGTCP2_MAX_UDP_PAYLOAD_SIZE) { + ngtcp2_log_info( + &conn->log, NGTCP2_LOG_EVENT_LDC, + "loss detection timer canceled due to amplification limit"); + cstat->loss_detection_timer = UINT64_MAX; + } + + return 0; + } + } else { + res += nwrite; + dest += nwrite; + destlen -= (size_t)nwrite; + + if (destlen) { + /* We might have already added padding to Initial, but in that + case, we should have destlen == 0 and no Handshake packet + will be written. */ + 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; + } + } + } + } + + nwrite = conn_write_handshake_pkt( + conn, pi, dest, destlen, NGTCP2_PKT_HANDSHAKE, wflags, write_datalen, ts); + if (nwrite < 0) { + assert(nwrite != NGTCP2_ERR_NOBUF); + return nwrite; + } + + res += nwrite; + + if (!conn->server && conn->hs_pktns->crypto.tx.ckm && nwrite) { + /* We don't need to send further Initial packet if we have + Handshake key and sent something with it. So discard initial + state here. */ + conn_discard_initial_state(conn, ts); + } + + return res; +} + +/* + * conn_initial_stream_rx_offset returns the initial maximum offset of + * data for a stream denoted by |stream_id|. + */ +static uint64_t conn_initial_stream_rx_offset(ngtcp2_conn *conn, + int64_t stream_id) { + int local_stream = conn_local_stream(conn, stream_id); + + if (bidi_stream(stream_id)) { + if (local_stream) { + return conn->local.transport_params.initial_max_stream_data_bidi_local; + } + return conn->local.transport_params.initial_max_stream_data_bidi_remote; + } + + if (local_stream) { + return 0; + } + return conn->local.transport_params.initial_max_stream_data_uni; +} + +/* + * conn_should_send_max_stream_data returns nonzero if MAX_STREAM_DATA + * frame should be send for |strm|. + */ +static int conn_should_send_max_stream_data(ngtcp2_conn *conn, + ngtcp2_strm *strm) { + uint64_t inc = strm->rx.unsent_max_offset - strm->rx.max_offset; + (void)conn; + + return strm->rx.window < 2 * inc; +} + +/* + * conn_should_send_max_data returns nonzero if MAX_DATA frame should + * be sent. + */ +static int conn_should_send_max_data(ngtcp2_conn *conn) { + uint64_t inc = conn->rx.unsent_max_offset - conn->rx.max_offset; + + return conn->rx.window < 2 * inc; +} + +/* + * conn_required_num_new_connection_id returns the number of + * additional connection ID the local endpoint has to provide to the + * remote endpoint. + */ +static size_t conn_required_num_new_connection_id(ngtcp2_conn *conn) { + uint64_t n; + size_t len = ngtcp2_ksl_len(&conn->scid.set); + size_t lim; + + if (len >= NGTCP2_MAX_SCID_POOL_SIZE) { + return 0; + } + + assert(NGTCP2_MAX_SCID_POOL_SIZE >= conn->scid.num_in_flight); + + lim = NGTCP2_MAX_SCID_POOL_SIZE - conn->scid.num_in_flight; + if (lim == 0) { + return 0; + } + + assert(conn->remote.transport_params); + assert(conn->remote.transport_params->active_connection_id_limit); + + /* len includes retired CID. We don't provide extra CID if doing so + exceeds NGTCP2_MAX_SCID_POOL_SIZE. */ + + n = conn->remote.transport_params->active_connection_id_limit + + conn->scid.num_retired; + + n = ngtcp2_min(NGTCP2_MAX_SCID_POOL_SIZE, n) - len; + + return (size_t)ngtcp2_min(lim, n); +} + +/* + * conn_enqueue_new_connection_id generates additional connection IDs + * and prepares to send them to the remote endpoint. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + */ +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); + if (rv != 0) { + return rv; + } + + if (cid.datalen != cidlen) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + /* Assert uniqueness */ + it = ngtcp2_ksl_lower_bound(&conn->scid.set, &cid); + if (!ngtcp2_ksl_it_end(&it) && + ngtcp2_cid_eq(ngtcp2_ksl_it_key(&it), &cid)) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + seq = ++conn->scid.last_seq; + + scid = ngtcp2_mem_malloc(conn->mem, sizeof(*scid)); + if (scid == NULL) { + return NGTCP2_ERR_NOMEM; + } + + ngtcp2_scid_init(scid, seq, &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; + } + + 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; + + assert(NGTCP2_MAX_SCID_POOL_SIZE > conn->scid.num_in_flight); + + ++conn->scid.num_in_flight; + } + + return 0; +} + +/* + * conn_remove_retired_connection_id removes the already retired + * connection ID. It waits PTO before actually removing a connection + * ID after it receives RETIRE_CONNECTION_ID from peer to catch + * reordered packets. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + */ +static int conn_remove_retired_connection_id(ngtcp2_conn *conn, + ngtcp2_duration pto, + ngtcp2_tstamp ts) { + ngtcp2_duration timeout = pto; + ngtcp2_scid *scid; + ngtcp2_dcid *dcid; + int rv; + + for (; !ngtcp2_pq_empty(&conn->scid.used);) { + scid = ngtcp2_struct_of(ngtcp2_pq_top(&conn->scid.used), ngtcp2_scid, pe); + + if (!ngtcp2_tstamp_elapsed(scid->retired_ts, timeout, ts)) { + break; + } + + assert(scid->flags & NGTCP2_SCID_FLAG_RETIRED); + + rv = conn_call_remove_connection_id(conn, &scid->cid); + if (rv != 0) { + return rv; + } + + ngtcp2_ksl_remove(&conn->scid.set, NULL, &scid->cid); + ngtcp2_pq_pop(&conn->scid.used); + ngtcp2_mem_free(conn->mem, scid); + + assert(conn->scid.num_retired); + --conn->scid.num_retired; + } + + for (; ngtcp2_ringbuf_len(&conn->dcid.retired.rb);) { + dcid = ngtcp2_ringbuf_get(&conn->dcid.retired.rb, 0); + if (dcid->retired_ts + timeout >= ts) { + break; + } + + rv = conn_call_deactivate_dcid(conn, dcid); + if (rv != 0) { + return rv; + } + + ngtcp2_ringbuf_pop_front(&conn->dcid.retired.rb); + } + + return 0; +} + +/* + * conn_min_short_pktlen returns the minimum length of Short packet + * this endpoint sends. + */ +static size_t conn_min_short_pktlen(ngtcp2_conn *conn) { + return conn->dcid.current.cid.datalen + NGTCP2_MIN_PKT_EXPANDLEN; +} + +/* + * conn_handle_unconfirmed_key_update_from_remote deals with key + * update which has not been confirmed yet and initiated by the remote + * endpoint. + * + * If key update was initiated by the remote endpoint, acknowledging a + * packet encrypted with the new key completes key update procedure. + */ +static void conn_handle_unconfirmed_key_update_from_remote(ngtcp2_conn *conn, + int64_t largest_ack, + ngtcp2_tstamp ts) { + if (!(conn->flags & NGTCP2_CONN_FLAG_KEY_UPDATE_NOT_CONFIRMED) || + (conn->flags & NGTCP2_CONN_FLAG_KEY_UPDATE_INITIATOR) || + largest_ack < conn->pktns.crypto.rx.ckm->pkt_num) { + return; + } + + conn->flags &= (uint32_t)~NGTCP2_CONN_FLAG_KEY_UPDATE_NOT_CONFIRMED; + conn->crypto.key_update.confirmed_ts = ts; + + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CRY, "key update confirmed"); +} + +static uint64_t conn_tx_strmq_first_cycle(ngtcp2_conn *conn); + +/* + * strm_should_send_stream_data_blocked returns nonzero if + * STREAM_DATA_BLOCKED frame should be sent to |strm|. + */ +static int strm_should_send_stream_data_blocked(ngtcp2_strm *strm) { + return strm->tx.offset == strm->tx.max_offset && + strm->tx.last_blocked_offset != strm->tx.max_offset; +} + +/* + * conn_should_send_data_blocked returns nonzero if DATA_BLOCKED frame + * should be sent. + */ +static int conn_should_send_data_blocked(ngtcp2_conn *conn) { + return conn->tx.offset == conn->tx.max_offset && + conn->tx.last_blocked_offset != conn->tx.max_offset; +} + +/* + * conn_reset_ppe_pending clears NGTCP2_CONN_FLAG_PPE_PENDING flag and + * nullifies conn->pkt. + */ +static void conn_reset_ppe_pending(ngtcp2_conn *conn) { + conn->flags &= (uint32_t)~NGTCP2_CONN_FLAG_PPE_PENDING; + + memset(&conn->pkt, 0, sizeof(conn->pkt)); +} + +/* + * conn_write_pkt writes a protected packet in the buffer pointed by + * |dest| whose length if |destlen|. |type| specifies the type of + * packet. It can be NGTCP2_PKT_1RTT or NGTCP2_PKT_0RTT. + * + * This function can send new stream data. In order to send stream + * data, specify the underlying stream and parameters to + * |vmsg|->stream. If |vmsg|->stream.fin is set to nonzero, it + * signals that the given data is the final portion of the stream. + * |vmsg|->stream.data vector of length |vmsg|->stream.datacnt + * specifies stream data to send. The number of bytes sent to the + * stream is assigned to *|vmsg|->stream.pdatalen. If 0 length STREAM + * data is sent, 0 is assigned to it. The caller should initialize + * *|vmsg|->stream.pdatalen to -1. + * + * If |require_padding| is nonzero, padding bytes are added to occupy + * the remaining packet payload. + * + * This function returns the number of bytes written in |dest| if it + * succeeds, or one of the following negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + * NGTCP2_ERR_STREAM_DATA_BLOCKED + * Stream data could not be written because of flow control. + */ +static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, + uint8_t *dest, size_t destlen, + ngtcp2_vmsg *vmsg, uint8_t type, + uint8_t flags, ngtcp2_tstamp ts) { + int rv = 0; + ngtcp2_crypto_cc *cc = &conn->pkt.cc; + ngtcp2_ppe *ppe = &conn->pkt.ppe; + ngtcp2_pkt_hd *hd = &conn->pkt.hd; + ngtcp2_frame *ackfr = NULL, 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; + int send_stream = 0; + int stream_blocked = 0; + int send_datagram = 0; + ngtcp2_pktns *pktns = &conn->pktns; + size_t left; + uint64_t datalen = 0; + ngtcp2_vec data[NGTCP2_MAX_STREAM_DATACNT]; + size_t datacnt; + uint16_t rtb_entry_flags = NGTCP2_RTB_ENTRY_FLAG_NONE; + int hd_logged = 0; + ngtcp2_path_challenge_entry *pcent; + uint8_t hd_flags = NGTCP2_PKT_FLAG_NONE; + int require_padding = (flags & NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING) != 0; + int write_more = (flags & NGTCP2_WRITE_PKT_FLAG_MORE) != 0; + int ppe_pending = (conn->flags & NGTCP2_CONN_FLAG_PPE_PENDING) != 0; + size_t min_pktlen = conn_min_short_pktlen(conn); + 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; + const ngtcp2_cid *scid = NULL; + int keep_alive_expired = 0; + uint32_t version = 0; + + /* Return 0 if destlen is less than minimum packet length which can + trigger Stateless Reset */ + if (destlen < min_pktlen) { + return 0; + } + + if (vmsg) { + switch (vmsg->type) { + case NGTCP2_VMSG_TYPE_STREAM: + datalen = ngtcp2_vec_len(vmsg->stream.data, vmsg->stream.datacnt); + ndatalen = conn_enforce_flow_control(conn, vmsg->stream.strm, datalen); + /* 0 length STREAM frame is allowed */ + if (ndatalen || datalen == 0) { + send_stream = 1; + } else { + stream_blocked = 1; + } + break; + case NGTCP2_VMSG_TYPE_DATAGRAM: + datalen = ngtcp2_vec_len(vmsg->datagram.data, vmsg->datagram.datacnt); + send_datagram = 1; + break; + default: + break; + } + } + + if (!ppe_pending) { + switch (type) { + case NGTCP2_PKT_1RTT: + hd_flags = conn_pkt_flags_short(conn); + scid = NULL; + cc->aead = pktns->crypto.ctx.aead; + cc->hp = pktns->crypto.ctx.hp; + cc->ckm = pktns->crypto.tx.ckm; + cc->hp_ctx = pktns->crypto.tx.hp_ctx; + + assert(conn->negotiated_version); + + version = conn->negotiated_version; + + /* transport parameter is only valid after handshake completion + which means we don't know how many connection ID that remote + peer can accept before handshake completion. Because server + can use remote transport parameters sending stream data in + 0.5 RTT, it is also allowed to use remote transport + parameters here. */ + if (conn->oscid.datalen && + (conn->server || conn_is_tls_handshake_completed(conn))) { + rv = conn_enqueue_new_connection_id(conn); + if (rv != 0) { + return rv; + } + } + + break; + case NGTCP2_PKT_0RTT: + assert(!conn->server); + if (!conn->early.ckm) { + return 0; + } + hd_flags = conn_pkt_flags_long(conn); + scid = &conn->oscid; + cc->aead = conn->early.ctx.aead; + cc->hp = conn->early.ctx.hp; + cc->ckm = conn->early.ckm; + cc->hp_ctx = conn->early.hp_ctx; + version = conn->client_chosen_version; + break; + default: + /* Unreachable */ + ngtcp2_unreachable(); + } + + cc->encrypt = conn->callbacks.encrypt; + cc->hp_mask = conn->callbacks.hp_mask; + + if (conn_should_send_max_data(conn)) { + rv = ngtcp2_frame_chain_objalloc_new(&nfrc, &conn->frc_objalloc); + if (rv != 0) { + return rv; + } + + if (conn->local.settings.max_window && + conn->tx.last_max_data_ts != UINT64_MAX && + ts - conn->tx.last_max_data_ts < + NGTCP2_FLOW_WINDOW_RTT_FACTOR * cstat->smoothed_rtt && + conn->local.settings.max_window > conn->rx.window) { + target_max_data = NGTCP2_FLOW_WINDOW_SCALING_FACTOR * conn->rx.window; + if (target_max_data > conn->local.settings.max_window) { + target_max_data = conn->local.settings.max_window; + } + + delta = target_max_data - conn->rx.window; + if (conn->rx.unsent_max_offset + delta > NGTCP2_MAX_VARINT) { + delta = NGTCP2_MAX_VARINT - conn->rx.unsent_max_offset; + } + + conn->rx.window = target_max_data; + } else { + delta = 0; + } + + 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->next = pktns->tx.frq; + pktns->tx.frq = nfrc; + + conn->rx.max_offset = conn->rx.unsent_max_offset = + nfrc->fr.max_data.max_data; + } + + if (stream_blocked && conn_should_send_max_data(conn)) { + rv = ngtcp2_frame_chain_objalloc_new(&nfrc, &conn->frc_objalloc); + if (rv != 0) { + return rv; + } + + nfrc->fr.type = NGTCP2_FRAME_DATA_BLOCKED; + nfrc->fr.data_blocked.offset = conn->tx.max_offset; + nfrc->next = pktns->tx.frq; + pktns->tx.frq = nfrc; + + conn->tx.last_blocked_offset = conn->tx.max_offset; + } + + if (stream_blocked && !ngtcp2_strm_is_tx_queued(vmsg->stream.strm) && + strm_should_send_stream_data_blocked(vmsg->stream.strm)) { + assert(vmsg); + assert(vmsg->type == NGTCP2_VMSG_TYPE_STREAM); + + vmsg->stream.strm->cycle = conn_tx_strmq_first_cycle(conn); + rv = ngtcp2_conn_tx_strmq_push(conn, vmsg->stream.strm); + if (rv != 0) { + return rv; + } + } + + ngtcp2_pkt_hd_init(hd, hd_flags, type, &conn->dcid.current.cid, scid, + pktns->tx.last_pkt_num + 1, + pktns_select_pkt_numlen(pktns), version, 0); + + ngtcp2_ppe_init(ppe, dest, destlen, cc); + + rv = ngtcp2_ppe_encode_hd(ppe, hd); + if (rv != 0) { + assert(NGTCP2_ERR_NOBUF == rv); + return 0; + } + + if (!ngtcp2_ppe_ensure_hp_sample(ppe)) { + return 0; + } + + if (ngtcp2_ringbuf_len(&conn->rx.path_challenge.rb)) { + pcent = ngtcp2_ringbuf_get(&conn->rx.path_challenge.rb, 0); + + /* 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)); + + rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &lfr); + if (rv != 0) { + assert(NGTCP2_ERR_NOBUF == rv); + } else { + ngtcp2_ringbuf_pop_front(&conn->rx.path_challenge.rb); + + pkt_empty = 0; + rtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING; + require_padding = require_padding || !conn->server || + destlen >= NGTCP2_MAX_UDP_PAYLOAD_SIZE; + /* We don't retransmit PATH_RESPONSE. */ + } + } + } + + rv = ngtcp2_conn_create_ack_frame( + conn, &ackfr, pktns, type, ts, conn_compute_ack_delay(conn), + conn->local.transport_params.ack_delay_exponent); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + return rv; + } + + if (ackfr) { + rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, ackfr); + 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); + if (type == NGTCP2_PKT_1RTT) { + conn_handle_unconfirmed_key_update_from_remote( + conn, ackfr->ack.largest_ack, ts); + } + pkt_empty = 0; + } + } + + build_pkt: + for (pfrc = &pktns->tx.frq; *pfrc;) { + if ((*pfrc)->binder && + ((*pfrc)->binder->flags & NGTCP2_FRAME_CHAIN_BINDER_FLAG_ACK)) { + frc = *pfrc; + *pfrc = (*pfrc)->next; + ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem); + continue; + } + + switch ((*pfrc)->fr.type) { + case NGTCP2_FRAME_STOP_SENDING: + strm = + ngtcp2_conn_find_stream(conn, (*pfrc)->fr.stop_sending.stream_id); + if (strm == NULL || + ((strm->flags & NGTCP2_STRM_FLAG_SHUT_RD) && + ngtcp2_strm_rx_offset(strm) == strm->rx.last_offset)) { + frc = *pfrc; + *pfrc = (*pfrc)->next; + ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem); + continue; + } + break; + case NGTCP2_FRAME_STREAM: + ngtcp2_unreachable(); + case NGTCP2_FRAME_MAX_STREAMS_BIDI: + if ((*pfrc)->fr.max_streams.max_streams < + conn->remote.bidi.max_streams) { + frc = *pfrc; + *pfrc = (*pfrc)->next; + ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem); + continue; + } + break; + case NGTCP2_FRAME_MAX_STREAMS_UNI: + if ((*pfrc)->fr.max_streams.max_streams < + conn->remote.uni.max_streams) { + frc = *pfrc; + *pfrc = (*pfrc)->next; + ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem); + continue; + } + break; + case NGTCP2_FRAME_MAX_STREAM_DATA: + strm = ngtcp2_conn_find_stream(conn, + (*pfrc)->fr.max_stream_data.stream_id); + if (strm == NULL || + (strm->flags & + (NGTCP2_STRM_FLAG_SHUT_RD | NGTCP2_STRM_FLAG_STOP_SENDING)) || + (*pfrc)->fr.max_stream_data.max_stream_data < strm->rx.max_offset) { + frc = *pfrc; + *pfrc = (*pfrc)->next; + ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem); + continue; + } + break; + case NGTCP2_FRAME_MAX_DATA: + if ((*pfrc)->fr.max_data.max_data < conn->rx.max_offset) { + frc = *pfrc; + *pfrc = (*pfrc)->next; + ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem); + continue; + } + break; + case NGTCP2_FRAME_STREAM_DATA_BLOCKED: + strm = ngtcp2_conn_find_stream( + conn, (*pfrc)->fr.stream_data_blocked.stream_id); + if (strm == NULL || (strm->flags & NGTCP2_STRM_FLAG_SHUT_WR) || + (*pfrc)->fr.stream_data_blocked.offset != strm->tx.max_offset) { + frc = *pfrc; + *pfrc = (*pfrc)->next; + ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem); + continue; + } + break; + case NGTCP2_FRAME_DATA_BLOCKED: + if ((*pfrc)->fr.data_blocked.offset != conn->tx.max_offset) { + frc = *pfrc; + *pfrc = (*pfrc)->next; + ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem); + continue; + } + break; + case NGTCP2_FRAME_CRYPTO: + ngtcp2_unreachable(); + } + + rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &(*pfrc)->fr); + if (rv != 0) { + assert(NGTCP2_ERR_NOBUF == rv); + break; + } + + pkt_empty = 0; + rtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE; + pfrc = &(*pfrc)->next; + } + + if (*pfrc == NULL) { + 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) { + break; + } + + rv = ngtcp2_strm_streamfrq_pop(&pktns->crypto.strm, &nfrc, left); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + 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; + + pkt_empty = 0; + rtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE; + } + } + + if (*pfrc == NULL) { + for (; !ngtcp2_pq_empty(&conn->tx.strmq);) { + strm = ngtcp2_conn_tx_strmq_top(conn); + + if (strm->flags & NGTCP2_STRM_FLAG_SEND_RESET_STREAM) { + rv = ngtcp2_frame_chain_objalloc_new(&nfrc, &conn->frc_objalloc); + if (rv != 0) { + 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; + *pfrc = nfrc; + + strm->flags &= ~NGTCP2_STRM_FLAG_SEND_RESET_STREAM; + + rv = + conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &nfrc->fr); + if (rv != 0) { + assert(NGTCP2_ERR_NOBUF == rv); + + break; + } + + pkt_empty = 0; + rtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE; + pfrc = &(*pfrc)->next; + } + + if (strm->flags & NGTCP2_STRM_FLAG_SEND_STOP_SENDING) { + if ((strm->flags & NGTCP2_STRM_FLAG_SHUT_RD) && + ngtcp2_strm_rx_offset(strm) == strm->rx.last_offset) { + strm->flags &= ~NGTCP2_STRM_FLAG_SEND_STOP_SENDING; + } else { + rv = conn_call_stream_stop_sending( + conn, strm->stream_id, strm->tx.stop_sending_app_error_code, + strm->stream_user_data); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + + return rv; + } + + rv = ngtcp2_frame_chain_objalloc_new(&nfrc, &conn->frc_objalloc); + if (rv != 0) { + 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; + *pfrc = nfrc; + + strm->flags &= ~NGTCP2_STRM_FLAG_SEND_STOP_SENDING; + + rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, + &nfrc->fr); + if (rv != 0) { + assert(NGTCP2_ERR_NOBUF == rv); + + break; + } + + pkt_empty = 0; + rtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE; + pfrc = &(*pfrc)->next; + } + } + + if (!(strm->flags & NGTCP2_STRM_FLAG_SHUT_WR) && + strm_should_send_stream_data_blocked(strm)) { + rv = ngtcp2_frame_chain_objalloc_new(&nfrc, &conn->frc_objalloc); + if (rv != 0) { + 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; + *pfrc = nfrc; + + strm->tx.last_blocked_offset = strm->tx.max_offset; + + rv = + conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &nfrc->fr); + if (rv != 0) { + assert(NGTCP2_ERR_NOBUF == rv); + + break; + } + + pkt_empty = 0; + rtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE; + pfrc = &(*pfrc)->next; + } + + if (!(strm->flags & + (NGTCP2_STRM_FLAG_SHUT_RD | NGTCP2_STRM_FLAG_STOP_SENDING)) && + conn_should_send_max_stream_data(conn, strm)) { + rv = ngtcp2_frame_chain_objalloc_new(&nfrc, &conn->frc_objalloc); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + return rv; + } + + if (conn->local.settings.max_stream_window && + strm->tx.last_max_stream_data_ts != UINT64_MAX && + ts - strm->tx.last_max_stream_data_ts < + NGTCP2_FLOW_WINDOW_RTT_FACTOR * cstat->smoothed_rtt && + conn->local.settings.max_stream_window > strm->rx.window) { + target_max_data = + NGTCP2_FLOW_WINDOW_SCALING_FACTOR * strm->rx.window; + if (target_max_data > conn->local.settings.max_stream_window) { + target_max_data = conn->local.settings.max_stream_window; + } + + delta = target_max_data - strm->rx.window; + if (strm->rx.unsent_max_offset + delta > NGTCP2_MAX_VARINT) { + delta = NGTCP2_MAX_VARINT - strm->rx.unsent_max_offset; + } + + strm->rx.window = target_max_data; + } else { + delta = 0; + } + + 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; + *pfrc = nfrc; + + strm->rx.max_offset = strm->rx.unsent_max_offset = + nfrc->fr.max_stream_data.max_stream_data; + + rv = + conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &nfrc->fr); + if (rv != 0) { + assert(NGTCP2_ERR_NOBUF == rv); + break; + } + + pkt_empty = 0; + rtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE; + pfrc = &(*pfrc)->next; + } + + if (ngtcp2_strm_streamfrq_empty(strm)) { + ngtcp2_conn_tx_strmq_pop(conn); + continue; + } + + stream_offset = ngtcp2_strm_streamfrq_unacked_offset(strm); + if (stream_offset == (uint64_t)-1) { + ngtcp2_strm_streamfrq_clear(strm); + ngtcp2_conn_tx_strmq_pop(conn); + continue; + } + + left = ngtcp2_ppe_left(ppe); + + left = ngtcp2_pkt_stream_max_datalen(strm->stream_id, stream_offset, + left, left); + + if (left == (size_t)-1) { + break; + } + + rv = ngtcp2_strm_streamfrq_pop(strm, &nfrc, left); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + return rv; + } + + if (nfrc == NULL) { + /* TODO Why? */ + 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; + + pkt_empty = 0; + rtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE; + + if (ngtcp2_strm_streamfrq_empty(strm)) { + ngtcp2_conn_tx_strmq_pop(conn); + continue; + } + + ngtcp2_conn_tx_strmq_pop(conn); + ++strm->cycle; + rv = ngtcp2_conn_tx_strmq_push(conn, strm); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + return rv; + } + } + } + + /* Write MAX_STREAMS after RESET_STREAM so that we can extend + stream ID space in one packet. */ + if (*pfrc == NULL && + conn->remote.bidi.unsent_max_streams > conn->remote.bidi.max_streams) { + rv = conn_call_extend_max_remote_streams_bidi( + conn, conn->remote.bidi.unsent_max_streams); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + return rv; + } + + rv = ngtcp2_frame_chain_objalloc_new(&nfrc, &conn->frc_objalloc); + if (rv != 0) { + 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; + *pfrc = nfrc; + + conn->remote.bidi.max_streams = conn->remote.bidi.unsent_max_streams; + + rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &(*pfrc)->fr); + if (rv != 0) { + assert(NGTCP2_ERR_NOBUF == rv); + } else { + pkt_empty = 0; + rtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE; + pfrc = &(*pfrc)->next; + } + } + + if (*pfrc == NULL && + conn->remote.uni.unsent_max_streams > conn->remote.uni.max_streams) { + rv = conn_call_extend_max_remote_streams_uni( + conn, conn->remote.uni.unsent_max_streams); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + return rv; + } + + rv = ngtcp2_frame_chain_objalloc_new(&nfrc, &conn->frc_objalloc); + if (rv != 0) { + 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; + *pfrc = nfrc; + + conn->remote.uni.max_streams = conn->remote.uni.unsent_max_streams; + + rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &(*pfrc)->fr); + if (rv != 0) { + assert(NGTCP2_ERR_NOBUF == rv); + } else { + pkt_empty = 0; + rtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE; + pfrc = &(*pfrc)->next; + } + } + + if (pktns->tx.frq == NULL && !send_stream && !send_datagram && + !(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); + if (num_reclaimed < 0) { + return rv; + } + if (num_reclaimed) { + goto build_pkt; + } + + /* We had pktns->rtb.num_retransmittable > 0 but we were unable + to reclaim any frame. In this case, we do not have to send + any probe packet. */ + if (pktns->rtb.num_pto_eliciting == 0) { + pktns->rtb.probe_pkt_left = 0; + ngtcp2_conn_set_loss_detection_timer(conn, ts); + + if (pkt_empty && conn_cwnd_is_zero(conn) && !require_padding) { + return 0; + } + } + } + } else { + pfrc = conn->pkt.pfrc; + rtb_entry_flags |= conn->pkt.rtb_entry_flags; + pkt_empty = conn->pkt.pkt_empty; + hd_logged = conn->pkt.hd_logged; + } + + left = ngtcp2_ppe_left(ppe); + + if (*pfrc == NULL && send_stream && + (ndatalen = ngtcp2_pkt_stream_max_datalen( + vmsg->stream.strm->stream_id, vmsg->stream.strm->tx.offset, ndatalen, + left)) != (size_t)-1 && + (ndatalen || datalen == 0)) { + datacnt = ngtcp2_vec_copy_at_most(data, NGTCP2_MAX_STREAM_DATACNT, + vmsg->stream.data, vmsg->stream.datacnt, + (size_t)ndatalen); + ndatalen = ngtcp2_vec_len(data, datacnt); + + assert((datacnt == 0 && datalen == 0) || (datacnt && datalen)); + + rv = ngtcp2_frame_chain_stream_datacnt_objalloc_new( + &nfrc, datacnt, &conn->frc_objalloc, conn->mem); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + return rv; + } + + nfrc->fr.stream.type = NGTCP2_FRAME_STREAM; + nfrc->fr.stream.flags = 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; + + rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &nfrc->fr); + if (rv != 0) { + ngtcp2_unreachable(); + } + + *pfrc = nfrc; + pfrc = &(*pfrc)->next; + + pkt_empty = 0; + rtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE; + + vmsg->stream.strm->tx.offset += ndatalen; + conn->tx.offset += ndatalen; + + if (fin) { + ngtcp2_strm_shutdown(vmsg->stream.strm, NGTCP2_STRM_FLAG_SHUT_WR); + } + + if (vmsg->stream.pdatalen) { + *vmsg->stream.pdatalen = (ngtcp2_ssize)ndatalen; + } + } else { + send_stream = 0; + } + + if (vmsg && vmsg->type == NGTCP2_VMSG_TYPE_STREAM && + ((stream_blocked && *pfrc == NULL) || + (send_stream && + !(vmsg->stream.strm->flags & NGTCP2_STRM_FLAG_SHUT_WR)))) { + if (conn_should_send_data_blocked(conn)) { + rv = ngtcp2_frame_chain_objalloc_new(&nfrc, &conn->frc_objalloc); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + + return rv; + } + + nfrc->fr.type = NGTCP2_FRAME_DATA_BLOCKED; + nfrc->fr.data_blocked.offset = conn->tx.offset; + + rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &nfrc->fr); + if (rv != 0) { + assert(NGTCP2_ERR_NOBUF == rv); + + /* We cannot add nfrc to pktns->tx.frq here. */ + ngtcp2_frame_chain_objalloc_del(nfrc, &conn->frc_objalloc, conn->mem); + } else { + *pfrc = nfrc; + pfrc = &(*pfrc)->next; + + pkt_empty = 0; + rtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE; + + conn->tx.last_blocked_offset = conn->tx.max_offset; + } + } + + strm = vmsg->stream.strm; + + if (strm_should_send_stream_data_blocked(strm)) { + rv = ngtcp2_frame_chain_objalloc_new(&nfrc, &conn->frc_objalloc); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + + 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; + + rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &nfrc->fr); + if (rv != 0) { + assert(NGTCP2_ERR_NOBUF == rv); + + /* We cannot add nfrc to pktns->tx.frq here. */ + ngtcp2_frame_chain_objalloc_del(nfrc, &conn->frc_objalloc, conn->mem); + + if (!ngtcp2_strm_is_tx_queued(strm)) { + strm->cycle = conn_tx_strmq_first_cycle(conn); + rv = ngtcp2_conn_tx_strmq_push(conn, strm); + if (rv != 0) { + return rv; + } + } + } else { + *pfrc = nfrc; + pfrc = &(*pfrc)->next; + + pkt_empty = 0; + rtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE; + + strm->tx.last_blocked_offset = strm->tx.max_offset; + } + } + } + + if (*pfrc == NULL && send_datagram && + left >= ngtcp2_pkt_datagram_framelen((size_t)datalen)) { + if (conn->callbacks.ack_datagram || conn->callbacks.lost_datagram) { + rv = ngtcp2_frame_chain_objalloc_new(&nfrc, &conn->frc_objalloc); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + return rv; + } + + nfrc->fr.datagram.type = NGTCP2_FRAME_DATAGRAM_LEN; + nfrc->fr.datagram.dgram_id = vmsg->datagram.dgram_id; + nfrc->fr.datagram.datacnt = vmsg->datagram.datacnt; + nfrc->fr.datagram.data = (ngtcp2_vec *)vmsg->datagram.data; + + rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &nfrc->fr); + assert(rv == 0); + + /* Because DATAGRAM will not be retransmitted, we do not use + data anymore. Just nullify it. The only reason to keep + track a frame is keep dgram_id to pass it to + ngtcp2_ack_datagram or ngtcp2_lost_datagram callbacks. */ + nfrc->fr.datagram.datacnt = 0; + nfrc->fr.datagram.data = NULL; + + *pfrc = nfrc; + pfrc = &(*pfrc)->next; + } else { + lfr.datagram.type = NGTCP2_FRAME_DATAGRAM_LEN; + lfr.datagram.datacnt = vmsg->datagram.datacnt; + lfr.datagram.data = (ngtcp2_vec *)vmsg->datagram.data; + + rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &lfr); + assert(rv == 0); + } + + pkt_empty = 0; + rtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_DATAGRAM; + + if (vmsg->datagram.paccepted) { + *vmsg->datagram.paccepted = 1; + } + } else { + send_datagram = 0; + } + + if (pkt_empty) { + if (*pfrc == NULL && rv == 0 && stream_blocked && + (write_more || !require_padding) && + ngtcp2_conn_get_max_data_left(conn)) { + if (write_more) { + conn->pkt.pfrc = pfrc; + conn->pkt.pkt_empty = pkt_empty; + conn->pkt.rtb_entry_flags = rtb_entry_flags; + conn->pkt.hd_logged = hd_logged; + conn->flags |= NGTCP2_CONN_FLAG_PPE_PENDING; + } + + return NGTCP2_ERR_STREAM_DATA_BLOCKED; + } + + keep_alive_expired = + type == NGTCP2_PKT_1RTT && conn_keep_alive_expired(conn, ts); + + if (conn->pktns.rtb.probe_pkt_left == 0 && !keep_alive_expired && + !require_padding) { + conn_reset_ppe_pending(conn); + + return 0; + } + } else if (write_more) { + conn->pkt.pfrc = pfrc; + conn->pkt.pkt_empty = pkt_empty; + conn->pkt.rtb_entry_flags = rtb_entry_flags; + conn->pkt.hd_logged = hd_logged; + conn->flags |= NGTCP2_CONN_FLAG_PPE_PENDING; + + assert(vmsg); + + switch (vmsg->type) { + case NGTCP2_VMSG_TYPE_STREAM: + if (send_stream) { + if (ngtcp2_ppe_left(ppe)) { + return NGTCP2_ERR_WRITE_MORE; + } + break; + } + + if (*pfrc == NULL && ngtcp2_conn_get_max_data_left(conn) && + stream_blocked) { + return NGTCP2_ERR_STREAM_DATA_BLOCKED; + } + break; + case NGTCP2_VMSG_TYPE_DATAGRAM: + if (send_datagram && ngtcp2_ppe_left(ppe)) { + return NGTCP2_ERR_WRITE_MORE; + } + /* If DATAGRAM cannot be written due to insufficient space, + continue to create a packet with the hope that application + calls ngtcp2_conn_writev_datagram again. */ + break; + default: + ngtcp2_unreachable(); + } + } + + if (!(rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING)) { + 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; + + rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &lfr); + if (rv != 0) { + assert(rv == NGTCP2_ERR_NOBUF); + /* TODO If buffer is too small, PING cannot be written if + packet is still empty. */ + } else { + rtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING; + if (conn->pktns.rtb.probe_pkt_left) { + rtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_PROBE; + } + pktns->tx.non_ack_pkt_start_ts = UINT64_MAX; + } + } else if (pktns->tx.non_ack_pkt_start_ts == UINT64_MAX) { + pktns->tx.non_ack_pkt_start_ts = ts; + } + } else { + pktns->tx.non_ack_pkt_start_ts = UINT64_MAX; + } + + /* TODO Push STREAM frame back to ngtcp2_strm if there is an error + before ngtcp2_rtb_entry is safely created and added. */ + if (require_padding || + /* Making full sized packet will help GSO a bit */ + ngtcp2_ppe_left(ppe) < 10) { + lfr.padding.len = ngtcp2_ppe_padding(ppe); + } else if (type == NGTCP2_PKT_1RTT) { + lfr.padding.len = ngtcp2_ppe_padding_size(ppe, min_pktlen); + } else { + lfr.padding.len = ngtcp2_ppe_padding_hp_sample(ppe); + } + + if (lfr.padding.len) { + lfr.type = NGTCP2_FRAME_PADDING; + padded = 1; + ngtcp2_log_tx_fr(&conn->log, hd, &lfr); + ngtcp2_qlog_write_frame(&conn->qlog, &lfr); + } + + nwrite = ngtcp2_ppe_final(ppe, NULL); + if (nwrite < 0) { + assert(ngtcp2_err_is_fatal((int)nwrite)); + return nwrite; + } + + ++cc->ckm->use_count; + + ngtcp2_qlog_pkt_sent_end(&conn->qlog, hd, (size_t)nwrite); + + /* TODO ack-eliciting vs needs-tracking */ + /* probe packet needs tracking but it does not need ACK, could be lost. */ + if ((rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) || padded) { + if (pi) { + conn_handle_tx_ecn(conn, pi, &rtb_entry_flags, pktns, hd, ts); + } + + rv = ngtcp2_rtb_entry_objalloc_new(&ent, hd, NULL, ts, (size_t)nwrite, + rtb_entry_flags, + &conn->rtb_entry_objalloc); + if (rv != 0) { + assert(ngtcp2_err_is_fatal((int)nwrite)); + return rv; + } + + if (*pfrc != pktns->tx.frq) { + ent->frc = pktns->tx.frq; + pktns->tx.frq = *pfrc; + *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->rtb, ent); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + ngtcp2_rtb_entry_objalloc_del(ent, &conn->rtb_entry_objalloc, + &conn->frc_objalloc, conn->mem); + 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); + } + } + } else if (pi && conn->tx.ecn.state == NGTCP2_ECN_STATE_CAPABLE) { + conn_handle_tx_ecn(conn, pi, NULL, pktns, hd, ts); + } + + conn_reset_ppe_pending(conn); + + if (pktns->rtb.probe_pkt_left && + (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); + } + + conn_update_keep_alive_last_ts(conn, ts); + + conn->dcid.current.bytes_sent += (uint64_t)nwrite; + + conn->tx.pacing.pktlen += (size_t)nwrite; + + ngtcp2_qlog_metrics_updated(&conn->qlog, &conn->cstat); + + ++pktns->tx.last_pkt_num; + + return nwrite; +} + +ngtcp2_ssize ngtcp2_conn_write_single_frame_pkt( + ngtcp2_conn *conn, ngtcp2_pkt_info *pi, uint8_t *dest, size_t destlen, + uint8_t type, uint8_t flags, const ngtcp2_cid *dcid, ngtcp2_frame *fr, + uint16_t rtb_entry_flags, const ngtcp2_path *path, ngtcp2_tstamp ts) { + int rv; + ngtcp2_ppe ppe; + ngtcp2_pkt_hd hd; + ngtcp2_frame lfr; + ngtcp2_ssize nwrite; + ngtcp2_crypto_cc cc; + ngtcp2_pktns *pktns; + uint8_t hd_flags; + ngtcp2_rtb_entry *rtbent; + int padded = 0; + const ngtcp2_cid *scid; + uint32_t version; + + switch (type) { + case NGTCP2_PKT_INITIAL: + pktns = conn->in_pktns; + hd_flags = conn_pkt_flags_long(conn); + scid = &conn->oscid; + version = conn->negotiated_version ? conn->negotiated_version + : conn->client_chosen_version; + if (version == conn->client_chosen_version) { + cc.ckm = pktns->crypto.tx.ckm; + cc.hp_ctx = pktns->crypto.tx.hp_ctx; + } else { + assert(version == conn->vneg.version); + + cc.ckm = conn->vneg.tx.ckm; + cc.hp_ctx = conn->vneg.tx.hp_ctx; + } + break; + case NGTCP2_PKT_HANDSHAKE: + pktns = conn->hs_pktns; + hd_flags = conn_pkt_flags_long(conn); + scid = &conn->oscid; + version = conn->negotiated_version; + cc.ckm = pktns->crypto.tx.ckm; + cc.hp_ctx = pktns->crypto.tx.hp_ctx; + break; + case NGTCP2_PKT_1RTT: + pktns = &conn->pktns; + hd_flags = conn_pkt_flags_short(conn); + scid = NULL; + version = conn->negotiated_version; + cc.ckm = pktns->crypto.tx.ckm; + cc.hp_ctx = pktns->crypto.tx.hp_ctx; + break; + default: + /* We don't support 0-RTT packet in this function. */ + ngtcp2_unreachable(); + } + + cc.aead = pktns->crypto.ctx.aead; + cc.hp = pktns->crypto.ctx.hp; + cc.encrypt = conn->callbacks.encrypt; + cc.hp_mask = conn->callbacks.hp_mask; + + ngtcp2_pkt_hd_init(&hd, hd_flags, type, dcid, scid, + pktns->tx.last_pkt_num + 1, pktns_select_pkt_numlen(pktns), + version, 0); + + ngtcp2_ppe_init(&ppe, dest, destlen, &cc); + + rv = ngtcp2_ppe_encode_hd(&ppe, &hd); + if (rv != 0) { + assert(NGTCP2_ERR_NOBUF == rv); + return 0; + } + + if (!ngtcp2_ppe_ensure_hp_sample(&ppe)) { + return 0; + } + + ngtcp2_log_tx_pkt_hd(&conn->log, &hd); + ngtcp2_qlog_pkt_sent_start(&conn->qlog); + + rv = conn_ppe_write_frame(conn, &ppe, &hd, fr); + if (rv != 0) { + assert(NGTCP2_ERR_NOBUF == rv); + return 0; + } + + lfr.type = NGTCP2_FRAME_PADDING; + if (flags & NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING) { + lfr.padding.len = ngtcp2_ppe_padding(&ppe); + } else { + switch (fr->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_padding(&ppe); + } else { + lfr.padding.len = 0; + } + break; + default: + if (type == NGTCP2_PKT_1RTT) { + lfr.padding.len = + ngtcp2_ppe_padding_size(&ppe, conn_min_short_pktlen(conn)); + } else { + lfr.padding.len = ngtcp2_ppe_padding_hp_sample(&ppe); + } + } + } + if (lfr.padding.len) { + padded = 1; + ngtcp2_log_tx_fr(&conn->log, &hd, &lfr); + ngtcp2_qlog_write_frame(&conn->qlog, &lfr); + } + + nwrite = ngtcp2_ppe_final(&ppe, NULL); + if (nwrite < 0) { + return nwrite; + } + + if (type == NGTCP2_PKT_1RTT) { + ++cc.ckm->use_count; + } + + 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) { + case NGTCP2_FRAME_ACK: + case NGTCP2_FRAME_ACK_ECN: + ngtcp2_acktr_commit_ack(&pktns->acktr); + ngtcp2_acktr_add_ack(&pktns->acktr, hd.pkt_num, fr->ack.largest_ack); + if (type == NGTCP2_PKT_1RTT) { + conn_handle_unconfirmed_key_update_from_remote(conn, fr->ack.largest_ack, + ts); + } + break; + } + + if (((rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) || padded) && + (!path || ngtcp2_path_eq(&conn->dcid.current.ps.path, path))) { + if (pi && (conn->tx.ecn.state == NGTCP2_ECN_STATE_CAPABLE || + !(rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE))) { + conn_handle_tx_ecn(conn, pi, &rtb_entry_flags, pktns, &hd, ts); + } + + rv = ngtcp2_rtb_entry_objalloc_new(&rtbent, &hd, NULL, ts, (size_t)nwrite, + rtb_entry_flags, + &conn->rtb_entry_objalloc); + if (rv != 0) { + return rv; + } + + rv = conn_on_pkt_sent(conn, &pktns->rtb, rtbent); + if (rv != 0) { + ngtcp2_rtb_entry_objalloc_del(rtbent, &conn->rtb_entry_objalloc, + &conn->frc_objalloc, conn->mem); + return rv; + } + + if (rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) { + if (conn->flags & NGTCP2_CONN_FLAG_RESTART_IDLE_TIMER_ON_WRITE) { + conn_restart_timer_on_write(conn, ts); + } + + if (pktns->rtb.probe_pkt_left && path && + 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); + } + } + } else if (pi && conn->tx.ecn.state == NGTCP2_ECN_STATE_CAPABLE) { + conn_handle_tx_ecn(conn, pi, NULL, pktns, &hd, ts); + } + + if (path && ngtcp2_path_eq(&conn->dcid.current.ps.path, path)) { + conn_update_keep_alive_last_ts(conn, ts); + } + + if (!padded) { + switch (fr->type) { + case NGTCP2_FRAME_ACK: + case NGTCP2_FRAME_ACK_ECN: + break; + default: + conn->tx.pacing.pktlen += (size_t)nwrite; + } + } else { + conn->tx.pacing.pktlen += (size_t)nwrite; + } + + ngtcp2_qlog_metrics_updated(&conn->qlog, &conn->cstat); + + ++pktns->tx.last_pkt_num; + + return nwrite; +} + +/* + * conn_process_early_rtb makes any pending 0RTT packet 1RTT packet. + */ +static void conn_process_early_rtb(ngtcp2_conn *conn) { + ngtcp2_rtb_entry *ent; + ngtcp2_rtb *rtb = &conn->pktns.rtb; + ngtcp2_ksl_it it; + + for (it = ngtcp2_rtb_head(rtb); !ngtcp2_ksl_it_end(&it); + ngtcp2_ksl_it_next(&it)) { + ent = ngtcp2_ksl_it_get(&it); + + if ((ent->hd.flags & NGTCP2_PKT_FLAG_LONG_FORM) == 0 || + ent->hd.type != NGTCP2_PKT_0RTT) { + continue; + } + + /* 0-RTT packet is retransmitted as a 1RTT packet. */ + ent->hd.flags &= (uint8_t)~NGTCP2_PKT_FLAG_LONG_FORM; + ent->hd.type = NGTCP2_PKT_1RTT; + } +} + +/* + * conn_handshake_remnants_left returns nonzero if there may be + * handshake packets the local endpoint has to send, including new + * packets and lost ones. + */ +static int conn_handshake_remnants_left(ngtcp2_conn *conn) { + ngtcp2_pktns *in_pktns = conn->in_pktns; + ngtcp2_pktns *hs_pktns = conn->hs_pktns; + + return !conn_is_tls_handshake_completed(conn) || + (in_pktns && (in_pktns->rtb.num_pto_eliciting || + !ngtcp2_strm_streamfrq_empty(&in_pktns->crypto.strm))) || + (hs_pktns && (hs_pktns->rtb.num_pto_eliciting || + !ngtcp2_strm_streamfrq_empty(&hs_pktns->crypto.strm))); +} + +/* + * conn_retire_dcid_seq retires destination connection ID denoted by + * |seq|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + * NGTCP2_ERR_CONNECTION_ID_LIMIT + * The number of unacknowledged retirement exceeds the limit. + */ +static int conn_retire_dcid_seq(ngtcp2_conn *conn, uint64_t seq) { + ngtcp2_pktns *pktns = &conn->pktns; + ngtcp2_frame_chain *nfrc; + int rv; + + rv = ngtcp2_conn_track_retired_dcid_seq(conn, seq); + if (rv != 0) { + return rv; + } + + rv = ngtcp2_frame_chain_objalloc_new(&nfrc, &conn->frc_objalloc); + if (rv != 0) { + return rv; + } + + nfrc->fr.type = NGTCP2_FRAME_RETIRE_CONNECTION_ID; + nfrc->fr.retire_connection_id.seq = seq; + nfrc->next = pktns->tx.frq; + pktns->tx.frq = nfrc; + + return 0; +} + +/* + * conn_retire_dcid retires |dcid|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory + */ +static int conn_retire_dcid(ngtcp2_conn *conn, const ngtcp2_dcid *dcid, + ngtcp2_tstamp ts) { + ngtcp2_ringbuf *rb = &conn->dcid.retired.rb; + ngtcp2_dcid *dest, *stale_dcid; + int rv; + + assert(dcid->cid.datalen); + + if (ngtcp2_ringbuf_full(rb)) { + stale_dcid = ngtcp2_ringbuf_get(rb, 0); + rv = conn_call_deactivate_dcid(conn, stale_dcid); + if (rv != 0) { + return rv; + } + + ngtcp2_ringbuf_pop_front(rb); + } + + dest = ngtcp2_ringbuf_push_back(rb); + ngtcp2_dcid_copy(dest, dcid); + dest->retired_ts = ts; + + return conn_retire_dcid_seq(conn, dcid->seq); +} + +/* + * conn_bind_dcid stores the DCID to |*pdcid| bound to |path|. If + * such DCID is not found, bind the new DCID to |path| and stores it + * to |*pdcid|. If a remote endpoint uses zero-length connection ID, + * the pointer to conn->dcid.current is assigned to |*pdcid|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_CONN_ID_BLOCKED + * No unused DCID is available + * NGTCP2_ERR_NOMEM + * Out of memory + */ +static int conn_bind_dcid(ngtcp2_conn *conn, ngtcp2_dcid **pdcid, + const ngtcp2_path *path, ngtcp2_tstamp ts) { + ngtcp2_dcid *dcid, *ndcid; + ngtcp2_cid cid; + size_t i, len; + int rv; + + assert(!ngtcp2_path_eq(&conn->dcid.current.ps.path, path)); + assert(!conn->pv || !ngtcp2_path_eq(&conn->pv->dcid.ps.path, path)); + assert(!conn->pv || !(conn->pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) || + !ngtcp2_path_eq(&conn->pv->fallback_dcid.ps.path, path)); + + len = ngtcp2_ringbuf_len(&conn->dcid.bound.rb); + for (i = 0; i < len; ++i) { + dcid = ngtcp2_ringbuf_get(&conn->dcid.bound.rb, i); + + if (ngtcp2_path_eq(&dcid->ps.path, path)) { + *pdcid = dcid; + return 0; + } + } + + if (conn->dcid.current.cid.datalen == 0) { + ndcid = ngtcp2_ringbuf_push_back(&conn->dcid.bound.rb); + ngtcp2_cid_zero(&cid); + ngtcp2_dcid_init(ndcid, ++conn->dcid.zerolen_seq, &cid, NULL); + ngtcp2_dcid_set_path(ndcid, path); + + *pdcid = ndcid; + + return 0; + } + + if (ngtcp2_ringbuf_len(&conn->dcid.unused.rb) == 0) { + return NGTCP2_ERR_CONN_ID_BLOCKED; + } + + if (ngtcp2_ringbuf_full(&conn->dcid.bound.rb)) { + dcid = ngtcp2_ringbuf_get(&conn->dcid.bound.rb, 0); + rv = conn_retire_dcid(conn, dcid, ts); + if (rv != 0) { + return rv; + } + } + + dcid = ngtcp2_ringbuf_get(&conn->dcid.unused.rb, 0); + ndcid = ngtcp2_ringbuf_push_back(&conn->dcid.bound.rb); + + ngtcp2_dcid_copy(ndcid, dcid); + ndcid->bound_ts = ts; + ngtcp2_dcid_set_path(ndcid, path); + + ngtcp2_ringbuf_pop_front(&conn->dcid.unused.rb); + + *pdcid = ndcid; + + return 0; +} + +static int conn_start_pmtud(ngtcp2_conn *conn) { + int rv; + size_t hard_max_udp_payload_size; + + assert(!conn->local.settings.no_pmtud); + assert(!conn->pmtud); + assert(conn_is_tls_handshake_completed(conn)); + assert(conn->remote.transport_params); + assert(conn->remote.transport_params->max_udp_payload_size >= + NGTCP2_MAX_UDP_PAYLOAD_SIZE); + + hard_max_udp_payload_size = (size_t)ngtcp2_min( + conn->remote.transport_params->max_udp_payload_size, + (uint64_t)conn->local.settings.max_tx_udp_payload_size); + + rv = ngtcp2_pmtud_new(&conn->pmtud, conn->dcid.current.max_udp_payload_size, + hard_max_udp_payload_size, + conn->pktns.tx.last_pkt_num + 1, conn->mem); + if (rv != 0) { + return rv; + } + + if (ngtcp2_pmtud_finished(conn->pmtud)) { + ngtcp2_conn_stop_pmtud(conn); + } + + return 0; +} + +int ngtcp2_conn_start_pmtud(ngtcp2_conn *conn) { + return conn_start_pmtud(conn); +} + +void ngtcp2_conn_stop_pmtud(ngtcp2_conn *conn) { + if (!conn->pmtud) { + return; + } + + ngtcp2_pmtud_del(conn->pmtud); + + conn->pmtud = NULL; +} + +static ngtcp2_ssize conn_write_pmtud_probe(ngtcp2_conn *conn, + ngtcp2_pkt_info *pi, uint8_t *dest, + size_t destlen, ngtcp2_tstamp ts) { + size_t probelen; + ngtcp2_ssize nwrite; + ngtcp2_frame lfr; + + assert(conn->pmtud); + assert(!ngtcp2_pmtud_finished(conn->pmtud)); + + if (!ngtcp2_pmtud_require_probe(conn->pmtud)) { + return 0; + } + + probelen = ngtcp2_pmtud_probelen(conn->pmtud); + if (probelen > destlen) { + return 0; + } + + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, + "sending PMTUD probe packet len=%zu", probelen); + + lfr.type = NGTCP2_FRAME_PING; + + nwrite = ngtcp2_conn_write_single_frame_pkt( + conn, pi, dest, probelen, NGTCP2_PKT_1RTT, + NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING, &conn->dcid.current.cid, &lfr, + NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE, + NULL, ts); + if (nwrite < 0) { + return nwrite; + } + + assert(nwrite); + + ngtcp2_pmtud_probe_sent(conn->pmtud, conn_compute_pto(conn, &conn->pktns), + ts); + + return nwrite; +} + +/* + * conn_stop_pv stops the path validation which is currently running. + * This function does nothing if no path validation is currently being + * performed. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory + */ +static int conn_stop_pv(ngtcp2_conn *conn, ngtcp2_tstamp ts) { + int rv = 0; + ngtcp2_pv *pv = conn->pv; + + if (pv == NULL) { + return 0; + } + + if (pv->dcid.cid.datalen && pv->dcid.seq != conn->dcid.current.seq) { + rv = conn_retire_dcid(conn, &pv->dcid, ts); + if (rv != 0) { + goto fin; + } + } + + if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) && + pv->fallback_dcid.cid.datalen && + pv->fallback_dcid.seq != conn->dcid.current.seq && + pv->fallback_dcid.seq != pv->dcid.seq) { + rv = conn_retire_dcid(conn, &pv->fallback_dcid, ts); + if (rv != 0) { + goto fin; + } + } + +fin: + ngtcp2_pv_del(pv); + conn->pv = NULL; + + return rv; +} + +/* + * conn_abort_pv aborts the current path validation and frees + * resources allocated for it. This function assumes that conn->pv is + * not NULL. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + */ +static int conn_abort_pv(ngtcp2_conn *conn, ngtcp2_tstamp ts) { + ngtcp2_pv *pv = conn->pv; + int rv; + + assert(pv); + + if (!(pv->flags & NGTCP2_PV_FLAG_DONT_CARE)) { + rv = conn_call_path_validation(conn, pv, + NGTCP2_PATH_VALIDATION_RESULT_ABORTED); + if (rv != 0) { + return rv; + } + } + + return conn_stop_pv(conn, ts); +} + +static size_t conn_shape_udp_payload(ngtcp2_conn *conn, const ngtcp2_dcid *dcid, + size_t payloadlen) { + if (conn->remote.transport_params && + conn->remote.transport_params->max_udp_payload_size) { + assert(conn->remote.transport_params->max_udp_payload_size >= + NGTCP2_MAX_UDP_PAYLOAD_SIZE); + + payloadlen = + (size_t)ngtcp2_min((uint64_t)payloadlen, + conn->remote.transport_params->max_udp_payload_size); + } + + payloadlen = + ngtcp2_min(payloadlen, conn->local.settings.max_tx_udp_payload_size); + + if (conn->local.settings.no_tx_udp_payload_size_shaping) { + return payloadlen; + } + + return ngtcp2_min(payloadlen, dcid->max_udp_payload_size); +} + +static void conn_reset_congestion_state(ngtcp2_conn *conn, ngtcp2_tstamp ts); + +/* + * conn_on_path_validation_failed is called when path validation + * fails. This function may delete |pv|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + */ +static int conn_on_path_validation_failed(ngtcp2_conn *conn, ngtcp2_pv *pv, + ngtcp2_tstamp ts) { + int rv; + + if (!(pv->flags & NGTCP2_PV_FLAG_DONT_CARE)) { + rv = conn_call_path_validation(conn, pv, + NGTCP2_PATH_VALIDATION_RESULT_FAILURE); + if (rv != 0) { + return rv; + } + } + + if (pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) { + ngtcp2_dcid_copy(&conn->dcid.current, &pv->fallback_dcid); + conn_reset_congestion_state(conn, ts); + } + + return conn_stop_pv(conn, ts); +} + +/* + * conn_write_path_challenge writes a packet which includes + * PATH_CHALLENGE frame into |dest| of length |destlen|. + * + * This function returns the number of bytes written to |dest|, or one + * of the following negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + */ +static ngtcp2_ssize conn_write_path_challenge(ngtcp2_conn *conn, + ngtcp2_path *path, + ngtcp2_pkt_info *pi, + uint8_t *dest, size_t destlen, + ngtcp2_tstamp ts) { + ngtcp2_ssize nwrite; + ngtcp2_tstamp expiry; + ngtcp2_pv *pv = conn->pv; + ngtcp2_frame lfr; + ngtcp2_duration timeout, initial_pto; + uint8_t flags; + uint64_t tx_left; + int rv; + + if (ngtcp2_pv_validation_timed_out(pv, ts)) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PTV, + "path validation was timed out"); + rv = conn_on_path_validation_failed(conn, pv, ts); + if (rv != 0) { + return rv; + } + + /* We might set path to the one which we just failed validate. + Set it to the current path here. */ + if (path) { + ngtcp2_path_copy(path, &conn->dcid.current.ps.path); + } + + return 0; + } + + ngtcp2_pv_handle_entry_expiry(pv, ts); + + if (!ngtcp2_pv_should_send_probe(pv)) { + return 0; + } + + rv = conn_call_get_path_challenge_data(conn, lfr.path_challenge.data); + if (rv != 0) { + return rv; + } + + lfr.type = NGTCP2_FRAME_PATH_CHALLENGE; + + initial_pto = conn_compute_initial_pto(conn, &conn->pktns); + timeout = conn_compute_pto(conn, &conn->pktns); + timeout = ngtcp2_max(timeout, initial_pto); + expiry = ts + timeout * (1ULL << pv->round); + + destlen = ngtcp2_min(destlen, NGTCP2_MAX_UDP_PAYLOAD_SIZE); + + if (conn->server) { + if (!(pv->dcid.flags & NGTCP2_DCID_FLAG_PATH_VALIDATED)) { + tx_left = conn_server_tx_left(conn, &pv->dcid); + destlen = (size_t)ngtcp2_min((uint64_t)destlen, tx_left); + if (destlen == 0) { + return 0; + } + } + + if (destlen < NGTCP2_MAX_UDP_PAYLOAD_SIZE) { + flags = NGTCP2_PV_ENTRY_FLAG_UNDERSIZED; + } else { + flags = NGTCP2_PV_ENTRY_FLAG_NONE; + } + } else { + flags = NGTCP2_PV_ENTRY_FLAG_NONE; + } + + 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, + &pv->dcid.cid, &lfr, + NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING | NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING, + &pv->dcid.ps.path, ts); + if (nwrite <= 0) { + return nwrite; + } + + if (path) { + ngtcp2_path_copy(path, &pv->dcid.ps.path); + } + + if (ngtcp2_path_eq(&pv->dcid.ps.path, &conn->dcid.current.ps.path)) { + conn->dcid.current.bytes_sent += (uint64_t)nwrite; + } else { + pv->dcid.bytes_sent += (uint64_t)nwrite; + } + + return nwrite; +} + +/* + * conn_write_path_response writes a packet which includes + * PATH_RESPONSE frame into |dest| of length |destlen|. + * + * This function returns the number of bytes written to |dest|, or one + * of the following negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + */ +static ngtcp2_ssize conn_write_path_response(ngtcp2_conn *conn, + ngtcp2_path *path, + ngtcp2_pkt_info *pi, uint8_t *dest, + size_t destlen, ngtcp2_tstamp ts) { + ngtcp2_pv *pv = conn->pv; + ngtcp2_path_challenge_entry *pcent = NULL; + ngtcp2_dcid *dcid = NULL; + ngtcp2_frame lfr; + ngtcp2_ssize nwrite; + int rv; + uint64_t tx_left; + + for (; ngtcp2_ringbuf_len(&conn->rx.path_challenge.rb);) { + pcent = ngtcp2_ringbuf_get(&conn->rx.path_challenge.rb, 0); + + if (ngtcp2_path_eq(&conn->dcid.current.ps.path, &pcent->ps.path)) { + /* Send PATH_RESPONSE from conn_write_pkt. */ + return 0; + } + + if (pv) { + if (ngtcp2_path_eq(&pv->dcid.ps.path, &pcent->ps.path)) { + dcid = &pv->dcid; + break; + } + if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) && + ngtcp2_path_eq(&pv->fallback_dcid.ps.path, &pcent->ps.path)) { + dcid = &pv->fallback_dcid; + break; + } + } + + if (conn->server) { + break; + } + + /* Client does not expect to respond to path validation against + unknown path */ + ngtcp2_ringbuf_pop_front(&conn->rx.path_challenge.rb); + pcent = NULL; + } + + if (pcent == NULL) { + return 0; + } + + if (dcid == NULL) { + /* client is expected to have |path| in conn->dcid.current or + conn->pv. */ + assert(conn->server); + + rv = conn_bind_dcid(conn, &dcid, &pcent->ps.path, ts); + if (rv != 0) { + if (ngtcp2_err_is_fatal(rv)) { + return rv; + } + return 0; + } + } + + destlen = ngtcp2_min(destlen, NGTCP2_MAX_UDP_PAYLOAD_SIZE); + + if (conn->server && !(dcid->flags & NGTCP2_DCID_FLAG_PATH_VALIDATED)) { + tx_left = conn_server_tx_left(conn, dcid); + destlen = (size_t)ngtcp2_min((uint64_t)destlen, tx_left); + if (destlen == 0) { + return 0; + } + } + + lfr.type = NGTCP2_FRAME_PATH_RESPONSE; + memcpy(lfr.path_response.data, pcent->data, sizeof(lfr.path_response.data)); + + nwrite = ngtcp2_conn_write_single_frame_pkt( + conn, pi, dest, destlen, NGTCP2_PKT_1RTT, NGTCP2_WRITE_PKT_FLAG_NONE, + &dcid->cid, &lfr, NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING, &pcent->ps.path, + ts); + if (nwrite <= 0) { + return nwrite; + } + + if (path) { + ngtcp2_path_copy(path, &pcent->ps.path); + } + + ngtcp2_ringbuf_pop_front(&conn->rx.path_challenge.rb); + + dcid->bytes_sent += (uint64_t)nwrite; + + return nwrite; +} + +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) { + return ngtcp2_conn_writev_stream_versioned( + conn, path, pkt_info_version, pi, dest, destlen, + /* pdatalen = */ NULL, NGTCP2_WRITE_STREAM_FLAG_NONE, + /* stream_id = */ -1, + /* datav = */ NULL, /* datavcnt = */ 0, ts); +} + +/* + * conn_on_version_negotiation is called when Version Negotiation + * packet is received. The function decodes the data in the buffer + * pointed by |payload| whose length is |payloadlen| as Version + * Negotiation packet payload. The packet header is given in |hd|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + * NGTCP2_ERR_INVALID_ARGUMENT + * Packet payload is badly formatted. + */ +static int conn_on_version_negotiation(ngtcp2_conn *conn, + const ngtcp2_pkt_hd *hd, + const uint8_t *payload, + size_t payloadlen) { + uint32_t sv[16]; + uint32_t *p; + int rv = 0; + size_t nsv; + size_t i; + + if (payloadlen % sizeof(uint32_t)) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + /* Version Negotiation packet is ignored if client has reacted upon + Version Negotiation packet. */ + if (conn->local.settings.original_version != conn->client_chosen_version) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + if (payloadlen > sizeof(sv)) { + p = ngtcp2_mem_malloc(conn->mem, payloadlen); + if (p == NULL) { + return NGTCP2_ERR_NOMEM; + } + } else { + p = sv; + } + + nsv = ngtcp2_pkt_decode_version_negotiation(p, payload, payloadlen); + + ngtcp2_log_rx_vn(&conn->log, hd, p, nsv); + + ngtcp2_qlog_version_negotiation_pkt_received(&conn->qlog, hd, p, nsv); + + if (!ngtcp2_is_reserved_version(conn->local.settings.original_version)) { + for (i = 0; i < nsv; ++i) { + if (p[i] == conn->local.settings.original_version) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "ignore Version Negotiation because it contains the " + "original version"); + + rv = NGTCP2_ERR_INVALID_ARGUMENT; + goto fin; + } + } + } + + rv = conn_call_recv_version_negotiation(conn, hd, p, nsv); + if (rv != 0) { + goto fin; + } + +fin: + if (p != sv) { + ngtcp2_mem_free(conn->mem, p); + } + + return rv; +} + +static uint64_t conn_tx_strmq_first_cycle(ngtcp2_conn *conn) { + ngtcp2_strm *strm; + + if (ngtcp2_pq_empty(&conn->tx.strmq)) { + return 0; + } + + strm = ngtcp2_struct_of(ngtcp2_pq_top(&conn->tx.strmq), ngtcp2_strm, pe); + return strm->cycle; +} + +uint64_t ngtcp2_conn_tx_strmq_first_cycle(ngtcp2_conn *conn) { + ngtcp2_strm *strm; + + if (ngtcp2_pq_empty(&conn->tx.strmq)) { + return 0; + } + + strm = ngtcp2_struct_of(ngtcp2_pq_top(&conn->tx.strmq), ngtcp2_strm, pe); + return strm->cycle; +} + +/* + * conn_on_retry is called when Retry packet is received. The + * function decodes the data in the buffer pointed by |pkt| whose + * length is |pktlen| as Retry packet. The length of long packet + * header is given in |hdpktlen|. |pkt| includes packet header. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + * NGTCP2_ERR_INVALID_ARGUMENT + * Packet payload is badly formatted. + * NGTCP2_ERR_PROTO + * ODCID does not match; or Token is empty. + */ +static int conn_on_retry(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd, + size_t hdpktlen, const uint8_t *pkt, size_t pktlen, + ngtcp2_tstamp ts) { + int rv; + ngtcp2_pkt_retry retry; + 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]; + uint8_t *token; + + if (!in_pktns || conn->flags & NGTCP2_CONN_FLAG_RECV_RETRY) { + return 0; + } + + in_rtb = &in_pktns->rtb; + + rv = ngtcp2_pkt_decode_retry(&retry, pkt + hdpktlen, pktlen - hdpktlen); + if (rv != 0) { + return rv; + } + + retry.odcid = conn->dcid.current.cid; + + rv = ngtcp2_pkt_verify_retry_tag( + conn->client_chosen_version, &retry, pkt, pktlen, conn->callbacks.encrypt, + &conn->crypto.retry_aead, &conn->crypto.retry_aead_ctx); + if (rv != 0) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "unable to verify Retry packet integrity"); + 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)); + + if (retry.tokenlen == 0) { + return NGTCP2_ERR_PROTO; + } + + if (ngtcp2_cid_eq(&conn->dcid.current.cid, &hd->scid)) { + return 0; + } + + ngtcp2_qlog_retry_pkt_received(&conn->qlog, hd, &retry); + + /* DCID must be updated before invoking callback because client + generates new initial keys there. */ + conn->dcid.current.cid = hd->scid; + conn->retry_scid = hd->scid; + + conn->flags |= NGTCP2_CONN_FLAG_RECV_RETRY; + + rv = conn_call_recv_retry(conn, hd); + if (rv != 0) { + return rv; + } + + conn->state = NGTCP2_CS_CLIENT_INITIAL; + + /* Just freeing memory is dangerous because we might free twice. */ + + rv = ngtcp2_rtb_remove_all(rtb, conn, &conn->pktns, &conn->cstat); + if (rv != 0) { + return rv; + } + + rv = ngtcp2_rtb_remove_all(in_rtb, conn, in_pktns, &conn->cstat); + if (rv != 0) { + return rv; + } + + ngtcp2_mem_free(conn->mem, (uint8_t *)conn->local.settings.token); + conn->local.settings.token = NULL; + conn->local.settings.tokenlen = 0; + + token = ngtcp2_mem_malloc(conn->mem, retry.tokenlen); + if (token == NULL) { + return NGTCP2_ERR_NOMEM; + } + + ngtcp2_cpymem(token, retry.token, retry.tokenlen); + + conn->local.settings.token = token; + conn->local.settings.tokenlen = retry.tokenlen; + + reset_conn_stat_recovery(&conn->cstat); + conn_reset_congestion_state(conn, ts); + conn_reset_ecn_validation_state(conn); + + return 0; +} + +int ngtcp2_conn_detect_lost_pkt(ngtcp2_conn *conn, ngtcp2_pktns *pktns, + ngtcp2_conn_stat *cstat, ngtcp2_tstamp ts) { + return ngtcp2_rtb_detect_lost_pkt(&pktns->rtb, conn, pktns, cstat, ts); +} + +/* + * conn_recv_ack processes received ACK frame |fr|. |pkt_ts| is the + * timestamp when packet is received. |ts| should be the current + * time. Usually they are the same, but for buffered packets, + * |pkt_ts| would be earlier than |ts|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory + * NGTCP2_ERR_ACK_FRAME + * ACK frame is malformed. + * NGTCP2_ERR_PROTO + * |fr| acknowledges a packet this endpoint has not sent. + * NGTCP2_ERR_CALLBACK_FAILURE + * User callback failed. + */ +static int conn_recv_ack(ngtcp2_conn *conn, ngtcp2_pktns *pktns, ngtcp2_ack *fr, + ngtcp2_tstamp pkt_ts, ngtcp2_tstamp ts) { + int rv; + ngtcp2_ssize num_acked; + ngtcp2_conn_stat *cstat = &conn->cstat; + + if (pktns->tx.last_pkt_num < fr->largest_ack) { + return NGTCP2_ERR_PROTO; + } + + rv = ngtcp2_pkt_validate_ack(fr, conn->local.settings.initial_pkt_num); + if (rv != 0) { + return rv; + } + + ngtcp2_acktr_recv_ack(&pktns->acktr, fr); + + num_acked = ngtcp2_rtb_recv_ack(&pktns->rtb, fr, &conn->cstat, conn, pktns, + pkt_ts, ts); + if (num_acked < 0) { + assert(ngtcp2_err_is_fatal((int)num_acked)); + return (int)num_acked; + } + + if (num_acked == 0) { + return 0; + } + + pktns->rtb.probe_pkt_left = 0; + + if (cstat->pto_count && + (conn->server || (conn->flags & NGTCP2_CONN_FLAG_SERVER_ADDR_VERIFIED))) { + /* Reset PTO count but no less than 2 to avoid frequent probe + packet transmission. */ + cstat->pto_count = ngtcp2_min(cstat->pto_count, 2); + } + + ngtcp2_conn_set_loss_detection_timer(conn, ts); + + return 0; +} + +/* + * conn_assign_recved_ack_delay_unscaled assigns + * fr->ack_delay_unscaled. + */ +static void assign_recved_ack_delay_unscaled(ngtcp2_ack *fr, + uint64_t ack_delay_exponent) { + fr->ack_delay_unscaled = + fr->ack_delay * (1ULL << ack_delay_exponent) * NGTCP2_MICROSECONDS; +} + +/* + * conn_recv_max_stream_data processes received MAX_STREAM_DATA frame + * |fr|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_STREAM_STATE + * Stream ID indicates that it is a local stream, and the local + * endpoint has not initiated it; or stream is peer initiated + * unidirectional stream. + * NGTCP2_ERR_STREAM_LIMIT + * Stream ID exceeds allowed limit. + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +static int conn_recv_max_stream_data(ngtcp2_conn *conn, + const ngtcp2_max_stream_data *fr) { + ngtcp2_strm *strm; + ngtcp2_idtr *idtr; + int local_stream = conn_local_stream(conn, fr->stream_id); + int bidi = bidi_stream(fr->stream_id); + int rv; + + if (bidi) { + if (local_stream) { + if (conn->local.bidi.next_stream_id <= fr->stream_id) { + return NGTCP2_ERR_STREAM_STATE; + } + } else if (conn->remote.bidi.max_streams < + ngtcp2_ord_stream_id(fr->stream_id)) { + return NGTCP2_ERR_STREAM_LIMIT; + } + + idtr = &conn->remote.bidi.idtr; + } else { + if (!local_stream || conn->local.uni.next_stream_id <= fr->stream_id) { + return NGTCP2_ERR_STREAM_STATE; + } + + idtr = &conn->remote.uni.idtr; + } + + strm = ngtcp2_conn_find_stream(conn, fr->stream_id); + if (strm == NULL) { + if (local_stream) { + /* Stream has been closed. */ + return 0; + } + + rv = ngtcp2_idtr_open(idtr, fr->stream_id); + if (rv != 0) { + if (ngtcp2_err_is_fatal(rv)) { + return rv; + } + assert(rv == NGTCP2_ERR_STREAM_IN_USE); + /* Stream has been closed. */ + return 0; + } + + strm = ngtcp2_objalloc_strm_get(&conn->strm_objalloc); + if (strm == NULL) { + return NGTCP2_ERR_NOMEM; + } + rv = ngtcp2_conn_init_stream(conn, strm, fr->stream_id, NULL); + if (rv != 0) { + ngtcp2_objalloc_strm_release(&conn->strm_objalloc, strm); + return rv; + } + + rv = conn_call_stream_open(conn, strm); + if (rv != 0) { + return rv; + } + } + + 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; + } + + rv = conn_call_extend_max_stream_data(conn, strm, fr->stream_id, + fr->max_stream_data); + if (rv != 0) { + return rv; + } + } + + return 0; +} + +/* + * conn_recv_max_data processes received MAX_DATA frame |fr|. + */ +static void conn_recv_max_data(ngtcp2_conn *conn, const ngtcp2_max_data *fr) { + conn->tx.max_offset = ngtcp2_max(conn->tx.max_offset, fr->max_data); +} + +/* + * conn_buffer_pkt buffers |pkt| of length |pktlen|, chaining it from + * |*ppc|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +static int conn_buffer_pkt(ngtcp2_conn *conn, ngtcp2_pktns *pktns, + const ngtcp2_path *path, const ngtcp2_pkt_info *pi, + const uint8_t *pkt, size_t pktlen, size_t dgramlen, + ngtcp2_tstamp ts) { + int rv; + ngtcp2_pkt_chain **ppc = &pktns->rx.buffed_pkts, *pc; + size_t i; + for (i = 0; *ppc && i < NGTCP2_MAX_NUM_BUFFED_RX_PKTS; + ppc = &(*ppc)->next, ++i) + ; + + if (i == NGTCP2_MAX_NUM_BUFFED_RX_PKTS) { + return 0; + } + + rv = + ngtcp2_pkt_chain_new(&pc, path, pi, pkt, pktlen, dgramlen, ts, conn->mem); + if (rv != 0) { + return rv; + } + + *ppc = pc; + + return 0; +} + +static int ensure_decrypt_buffer(ngtcp2_vec *vec, size_t n, size_t initial, + const ngtcp2_mem *mem) { + uint8_t *nbuf; + size_t len; + + if (vec->len >= n) { + return 0; + } + + len = vec->len == 0 ? initial : vec->len * 2; + for (; len < n; len *= 2) + ; + nbuf = ngtcp2_mem_realloc(mem, vec->base, len); + if (nbuf == NULL) { + return NGTCP2_ERR_NOMEM; + } + vec->base = nbuf; + vec->len = len; + + return 0; +} + +/* + * conn_ensure_decrypt_hp_buffer ensures that + * conn->crypto.decrypt_hp_buf has at least |n| bytes space. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +static int conn_ensure_decrypt_hp_buffer(ngtcp2_conn *conn, size_t n) { + return ensure_decrypt_buffer(&conn->crypto.decrypt_hp_buf, n, 256, conn->mem); +} + +/* + * conn_ensure_decrypt_buffer ensures that conn->crypto.decrypt_buf + * has at least |n| bytes space. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +static int conn_ensure_decrypt_buffer(ngtcp2_conn *conn, size_t n) { + return ensure_decrypt_buffer(&conn->crypto.decrypt_buf, n, 2048, conn->mem); +} + +/* + * decrypt_pkt decrypts the data pointed by |payload| whose length is + * |payloadlen|, and writes plaintext data to the buffer pointed by + * |dest|. The buffer pointed by |aad| is the Additional + * Authenticated Data, and its length is |aadlen|. |pkt_num| is used + * to create a nonce. |ckm| is the cryptographic key, and iv to use. + * |decrypt| is a callback function which actually decrypts a packet. + * + * This function returns the number of bytes written in |dest| if it + * succeeds, or one of the following negative error codes: + * + * NGTCP2_ERR_CALLBACK_FAILURE + * User callback failed. + * NGTCP2_ERR_DECRYPT + * Failed to decrypt a packet. + */ +static ngtcp2_ssize decrypt_pkt(uint8_t *dest, const ngtcp2_crypto_aead *aead, + const uint8_t *payload, size_t payloadlen, + const uint8_t *aad, size_t aadlen, + int64_t pkt_num, ngtcp2_crypto_km *ckm, + ngtcp2_decrypt decrypt) { + /* TODO nonce is limited to 64 bytes. */ + uint8_t nonce[64]; + int rv; + + assert(sizeof(nonce) >= ckm->iv.len); + + ngtcp2_crypto_create_nonce(nonce, ckm->iv.base, ckm->iv.len, pkt_num); + + rv = decrypt(dest, aead, &ckm->aead_ctx, payload, payloadlen, nonce, + ckm->iv.len, aad, aadlen); + + if (rv != 0) { + if (rv == NGTCP2_ERR_DECRYPT) { + return rv; + } + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + assert(payloadlen >= aead->max_overhead); + + return (ngtcp2_ssize)(payloadlen - aead->max_overhead); +} + +/* + * decrypt_hp decryptes packet header. The packet number starts at + * |pkt| + |pkt_num_offset|. The entire plaintext QUIC packet header + * will be written to the buffer pointed by |dest| whose capacity is + * |destlen|. + * + * This function returns the number of bytes written to |dest|, or one + * of the following negative error codes: + * + * NGTCP2_ERR_PROTO + * Packet is badly formatted + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed; or it does not return + * expected result. + */ +static ngtcp2_ssize +decrypt_hp(ngtcp2_pkt_hd *hd, uint8_t *dest, const ngtcp2_crypto_cipher *hp, + const uint8_t *pkt, size_t pktlen, size_t pkt_num_offset, + const ngtcp2_crypto_cipher_ctx *hp_ctx, ngtcp2_hp_mask hp_mask) { + size_t sample_offset; + uint8_t *p = dest; + uint8_t mask[NGTCP2_HP_SAMPLELEN]; + size_t i; + int rv; + + assert(hp_mask); + + if (pkt_num_offset + 4 + NGTCP2_HP_SAMPLELEN > pktlen) { + return NGTCP2_ERR_PROTO; + } + + p = ngtcp2_cpymem(p, pkt, pkt_num_offset); + + sample_offset = pkt_num_offset + 4; + + rv = hp_mask(mask, hp, hp_ctx, pkt + sample_offset); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + if (hd->flags & NGTCP2_PKT_FLAG_LONG_FORM) { + dest[0] = (uint8_t)(dest[0] ^ (mask[0] & 0x0f)); + } else { + dest[0] = (uint8_t)(dest[0] ^ (mask[0] & 0x1f)); + if (dest[0] & NGTCP2_SHORT_KEY_PHASE_BIT) { + hd->flags |= NGTCP2_PKT_FLAG_KEY_PHASE; + } + } + + hd->pkt_numlen = (size_t)((dest[0] & NGTCP2_PKT_NUMLEN_MASK) + 1); + + for (i = 0; i < hd->pkt_numlen; ++i) { + *p++ = *(pkt + pkt_num_offset + i) ^ mask[i + 1]; + } + + hd->pkt_num = ngtcp2_get_pkt_num(p - hd->pkt_numlen, hd->pkt_numlen); + + return p - dest; +} + +/* + * conn_emit_pending_crypto_data delivers pending stream data to the + * application due to packet reordering. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_CALLBACK_FAILURE + * User callback failed + * NGTCP2_ERR_CRYPTO + * TLS backend reported error + */ +static int +conn_emit_pending_crypto_data(ngtcp2_conn *conn, + ngtcp2_encryption_level encryption_level, + ngtcp2_strm *strm, uint64_t rx_offset) { + size_t datalen; + const uint8_t *data; + int rv; + uint64_t offset; + + if (!strm->rx.rob) { + return 0; + } + + for (;;) { + datalen = ngtcp2_rob_data_at(strm->rx.rob, &data, rx_offset); + if (datalen == 0) { + assert(rx_offset == ngtcp2_strm_rx_offset(strm)); + return 0; + } + + offset = rx_offset; + rx_offset += datalen; + + rv = conn_call_recv_crypto_data(conn, encryption_level, offset, data, + datalen); + if (rv != 0) { + return rv; + } + + ngtcp2_rob_pop(strm->rx.rob, rx_offset - datalen, datalen); + } +} + +/* + * conn_recv_connection_close is called when CONNECTION_CLOSE or + * APPLICATION_CLOSE frame is received. + */ +static int conn_recv_connection_close(ngtcp2_conn *conn, + ngtcp2_connection_close *fr) { + ngtcp2_ccerr *ccerr = &conn->rx.ccerr; + + conn->state = NGTCP2_CS_DRAINING; + if (fr->type == NGTCP2_FRAME_CONNECTION_CLOSE) { + ccerr->type = NGTCP2_CCERR_TYPE_TRANSPORT; + } else { + ccerr->type = NGTCP2_CCERR_TYPE_APPLICATION; + } + ccerr->error_code = fr->error_code; + ccerr->frame_type = fr->frame_type; + + if (!fr->reasonlen) { + ccerr->reasonlen = 0; + + return 0; + } + + if (ccerr->reason == NULL) { + ccerr->reason = ngtcp2_mem_malloc(conn->mem, NGTCP2_CCERR_MAX_REASONLEN); + if (ccerr->reason == NULL) { + return NGTCP2_ERR_NOMEM; + } + } + + ccerr->reasonlen = ngtcp2_min(fr->reasonlen, NGTCP2_CCERR_MAX_REASONLEN); + ngtcp2_cpymem((uint8_t *)ccerr->reason, fr->reason, ccerr->reasonlen); + + return 0; +} + +static void conn_recv_path_challenge(ngtcp2_conn *conn, const ngtcp2_path *path, + ngtcp2_path_challenge *fr) { + ngtcp2_path_challenge_entry *ent; + + /* client only responds to PATH_CHALLENGE from the current path or + path which client is migrating to. */ + if (!conn->server && !ngtcp2_path_eq(&conn->dcid.current.ps.path, path) && + (!conn->pv || !ngtcp2_path_eq(&conn->pv->dcid.ps.path, path))) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, + "discard PATH_CHALLENGE from the path which is not current " + "or endpoint is migrating to"); + return; + } + + ent = ngtcp2_ringbuf_push_front(&conn->rx.path_challenge.rb); + ngtcp2_path_challenge_entry_init(ent, path, fr->data); +} + +/* + * conn_reset_congestion_state resets congestion state. + */ +static void conn_reset_congestion_state(ngtcp2_conn *conn, ngtcp2_tstamp ts) { + conn_reset_conn_stat_cc(conn, &conn->cstat); + + if (conn->cc.reset) { + conn->cc.reset(&conn->cc, &conn->cstat, ts); + } + + if (conn->hs_pktns) { + ngtcp2_rtb_reset_cc_state(&conn->hs_pktns->rtb, + conn->hs_pktns->tx.last_pkt_num + 1); + } + ngtcp2_rtb_reset_cc_state(&conn->pktns.rtb, conn->pktns.tx.last_pkt_num + 1); + ngtcp2_rst_init(&conn->rst); + + conn->tx.pacing.next_ts = UINT64_MAX; +} + +static int conn_recv_path_response(ngtcp2_conn *conn, ngtcp2_path_response *fr, + ngtcp2_tstamp ts) { + int rv; + ngtcp2_pv *pv = conn->pv, *npv; + uint8_t ent_flags; + + if (!pv) { + return 0; + } + + rv = ngtcp2_pv_validate(pv, &ent_flags, fr->data); + if (rv != 0) { + assert(!ngtcp2_err_is_fatal(rv)); + + return 0; + } + + if (!(pv->flags & NGTCP2_PV_FLAG_DONT_CARE)) { + if (pv->dcid.seq != conn->dcid.current.seq) { + assert(!conn->server); + assert(conn->dcid.current.cid.datalen); + + rv = conn_retire_dcid(conn, &conn->dcid.current, ts); + if (rv != 0) { + return rv; + } + ngtcp2_dcid_copy(&conn->dcid.current, &pv->dcid); + + conn_reset_congestion_state(conn, ts); + conn_reset_ecn_validation_state(conn); + } + + assert(ngtcp2_path_eq(&pv->dcid.ps.path, &conn->dcid.current.ps.path)); + + conn->dcid.current.flags |= NGTCP2_DCID_FLAG_PATH_VALIDATED; + + if (!conn->local.settings.no_pmtud) { + ngtcp2_conn_stop_pmtud(conn); + + if (!(ent_flags & NGTCP2_PV_ENTRY_FLAG_UNDERSIZED)) { + rv = conn_start_pmtud(conn); + if (rv != 0) { + return rv; + } + } + } + + if (!(ent_flags & NGTCP2_PV_ENTRY_FLAG_UNDERSIZED)) { + rv = conn_call_path_validation(conn, pv, + NGTCP2_PATH_VALIDATION_RESULT_SUCCESS); + if (rv != 0) { + return rv; + } + } + } + + if (pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) { + if (ent_flags & NGTCP2_PV_ENTRY_FLAG_UNDERSIZED) { + assert(conn->server); + + /* Validate path again */ + rv = ngtcp2_pv_new(&npv, &pv->dcid, conn_compute_pv_timeout(conn), + NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE, &conn->log, + conn->mem); + if (rv != 0) { + return rv; + } + + npv->dcid.flags |= NGTCP2_DCID_FLAG_PATH_VALIDATED; + ngtcp2_dcid_copy(&npv->fallback_dcid, &pv->fallback_dcid); + npv->fallback_pto = pv->fallback_pto; + } else { + rv = ngtcp2_pv_new(&npv, &pv->fallback_dcid, + conn_compute_pv_timeout_pto(conn, pv->fallback_pto), + NGTCP2_PV_FLAG_DONT_CARE, &conn->log, conn->mem); + if (rv != 0) { + return rv; + } + } + + /* Unset the flag bit so that conn_stop_pv does not retire + DCID. */ + pv->flags &= (uint8_t)~NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE; + + rv = conn_stop_pv(conn, ts); + if (rv != 0) { + ngtcp2_pv_del(npv); + return rv; + } + + conn->pv = npv; + + return 0; + } + + return conn_stop_pv(conn, ts); +} + +/* + * pktns_pkt_num_is_duplicate returns nonzero if |pkt_num| is + * duplicated packet number. + */ +static int pktns_pkt_num_is_duplicate(ngtcp2_pktns *pktns, int64_t pkt_num) { + return ngtcp2_gaptr_is_pushed(&pktns->rx.pngap, (uint64_t)pkt_num, 1); +} + +/* + * pktns_commit_recv_pkt_num marks packet number |pkt_num| as + * received. + */ +static int pktns_commit_recv_pkt_num(ngtcp2_pktns *pktns, int64_t pkt_num, + int ack_eliciting, ngtcp2_tstamp ts) { + int rv; + ngtcp2_range r; + + rv = ngtcp2_gaptr_push(&pktns->rx.pngap, (uint64_t)pkt_num, 1); + if (rv != 0) { + return rv; + } + + if (ngtcp2_ksl_len(&pktns->rx.pngap.gap) > 256) { + ngtcp2_gaptr_drop_first_gap(&pktns->rx.pngap); + } + + if (ack_eliciting) { + if (pktns->rx.max_ack_eliciting_pkt_num != -1) { + if (pkt_num < pktns->rx.max_ack_eliciting_pkt_num) { + ngtcp2_acktr_immediate_ack(&pktns->acktr); + } else if (pkt_num > pktns->rx.max_ack_eliciting_pkt_num) { + r = ngtcp2_gaptr_get_first_gap_after( + &pktns->rx.pngap, (uint64_t)pktns->rx.max_ack_eliciting_pkt_num); + + if (r.begin < (uint64_t)pkt_num) { + ngtcp2_acktr_immediate_ack(&pktns->acktr); + } + } + } + + if (pktns->rx.max_ack_eliciting_pkt_num < pkt_num) { + pktns->rx.max_ack_eliciting_pkt_num = pkt_num; + } + } + + if (pktns->rx.max_pkt_num < pkt_num) { + pktns->rx.max_pkt_num = pkt_num; + pktns->rx.max_pkt_ts = ts; + } + + return 0; +} + +/* + * verify_token verifies |hd| contains |token| in its token field. It + * returns 0 if it succeeds, or NGTCP2_ERR_PROTO. + */ +static int verify_token(const uint8_t *token, size_t tokenlen, + const ngtcp2_pkt_hd *hd) { + if (tokenlen == hd->tokenlen && ngtcp2_cmemeq(token, hd->token, tokenlen)) { + return 0; + } + return NGTCP2_ERR_PROTO; +} + +static void pktns_increase_ecn_counts(ngtcp2_pktns *pktns, + const ngtcp2_pkt_info *pi) { + switch (pi->ecn & NGTCP2_ECN_MASK) { + case NGTCP2_ECN_ECT_0: + ++pktns->rx.ecn.ect0; + break; + case NGTCP2_ECN_ECT_1: + ++pktns->rx.ecn.ect1; + break; + case NGTCP2_ECN_CE: + ++pktns->rx.ecn.ce; + break; + } +} + +/* + * vneg_available_versions_includes returns nonzero if + * |available_versions| of length |available_versionslen| includes + * |version|. |available_versions| is the wire image of + * available_versions field of version_information transport + * parameter, and each version is encoded in network byte order. + */ +static int vneg_available_versions_includes(const uint8_t *available_versions, + size_t available_versionslen, + uint32_t version) { + size_t i; + uint32_t v; + + assert(!(available_versionslen & 0x3)); + + if (available_versionslen == 0) { + return 0; + } + + for (i = 0; i < available_versionslen; i += sizeof(uint32_t)) { + available_versions = ngtcp2_get_uint32(&v, available_versions); + + if (version == v) { + return 1; + } + } + + return 0; +} + +/* + * conn_verify_fixed_bit verifies that fixed bit in |hd| is + * acceptable. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_INVALID_ARGUMENT + * Clearing fixed bit is not permitted. + */ +static int conn_verify_fixed_bit(ngtcp2_conn *conn, ngtcp2_pkt_hd *hd) { + if (!(hd->flags & NGTCP2_PKT_FLAG_FIXED_BIT_CLEAR)) { + return 0; + } + + if (conn->server) { + switch (hd->type) { + case NGTCP2_PKT_INITIAL: + case NGTCP2_PKT_0RTT: + case NGTCP2_PKT_HANDSHAKE: + /* RFC 9287 requires that a token from NEW_TOKEN. */ + if (!(conn->flags & NGTCP2_CONN_FLAG_INITIAL_PKT_PROCESSED) && + (conn->local.settings.token_type != NGTCP2_TOKEN_TYPE_NEW_TOKEN || + !conn->local.settings.tokenlen)) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + break; + } + } + + /* TODO we have no information that we enabled grease_quic_bit in + the previous connection. */ + if (!conn->local.transport_params.grease_quic_bit) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + return 0; +} + +static int conn_recv_crypto(ngtcp2_conn *conn, + ngtcp2_encryption_level encryption_level, + ngtcp2_strm *strm, const ngtcp2_stream *fr); + +static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, + const ngtcp2_pkt_info *pi, const uint8_t *pkt, + size_t pktlen, size_t dgramlen, + ngtcp2_tstamp pkt_ts, ngtcp2_tstamp ts); + +static int conn_process_buffered_protected_pkt(ngtcp2_conn *conn, + ngtcp2_pktns *pktns, + ngtcp2_tstamp ts); + +/* + * conn_recv_handshake_pkt processes received packet |pkt| whose + * length is |pktlen| during handshake period. The buffer pointed by + * |pkt| might contain multiple packets. This function only processes + * one packet. |pkt_ts| is the timestamp when packet is received. + * |ts| should be the current time. Usually they are the same, but + * for buffered packets, |pkt_ts| would be earlier than |ts|. + * + * This function returns the number of bytes it reads if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_RECV_VERSION_NEGOTIATION + * Version Negotiation packet is received. + * NGTCP2_ERR_NOMEM + * Out of memory. + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + * NGTCP2_ERR_DISCARD_PKT + * Packet was discarded because plain text header was malformed; + * or its payload could not be decrypted. + * NGTCP2_ERR_FRAME_FORMAT + * Frame is badly formatted + * NGTCP2_ERR_ACK_FRAME + * ACK frame is malformed. + * NGTCP2_ERR_CRYPTO + * TLS stack reported error. + * NGTCP2_ERR_PROTO + * Generic QUIC protocol error. + * + * In addition to the above error codes, error codes returned from + * conn_recv_pkt are also returned. + */ +static ngtcp2_ssize +conn_recv_handshake_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, + const ngtcp2_pkt_info *pi, const uint8_t *pkt, + size_t pktlen, size_t dgramlen, ngtcp2_tstamp pkt_ts, + ngtcp2_tstamp ts) { + ngtcp2_ssize nread; + ngtcp2_pkt_hd hd; + ngtcp2_max_frame mfr; + ngtcp2_frame *fr = &mfr.fr; + int rv; + int require_ack = 0; + size_t hdpktlen; + const uint8_t *payload; + size_t payloadlen; + ngtcp2_ssize nwrite; + ngtcp2_crypto_aead *aead; + ngtcp2_crypto_cipher *hp; + ngtcp2_crypto_km *ckm; + ngtcp2_crypto_cipher_ctx *hp_ctx; + ngtcp2_hp_mask hp_mask; + ngtcp2_decrypt decrypt; + ngtcp2_pktns *pktns; + ngtcp2_strm *crypto; + ngtcp2_encryption_level encryption_level; + int invalid_reserved_bits = 0; + + if (pktlen == 0) { + return 0; + } + + if (!(pkt[0] & NGTCP2_HEADER_FORM_BIT)) { + if (conn->state == NGTCP2_CS_SERVER_INITIAL) { + /* Ignore 1RTT packet unless server's first Handshake packet has + been transmitted. */ + return (ngtcp2_ssize)pktlen; + } + + if (conn->pktns.crypto.rx.ckm) { + return 0; + } + + ngtcp2_log_info(&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); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + return rv; + } + return (ngtcp2_ssize)pktlen; + } + + nread = ngtcp2_pkt_decode_hd_long(&hd, pkt, pktlen); + if (nread < 0) { + return NGTCP2_ERR_DISCARD_PKT; + } + + switch (hd.type) { + case NGTCP2_PKT_VERSION_NEGOTIATION: + hdpktlen = (size_t)nread; + + ngtcp2_log_rx_pkt_hd(&conn->log, &hd); + + if (conn->server) { + return NGTCP2_ERR_DISCARD_PKT; + } + + /* Receiving Version Negotiation packet after getting Handshake + packet from server is invalid. */ + if (conn->flags & NGTCP2_CONN_FLAG_INITIAL_PKT_PROCESSED) { + return NGTCP2_ERR_DISCARD_PKT; + } + + if (!ngtcp2_cid_eq(&conn->oscid, &hd.dcid)) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "packet was ignored because of mismatched DCID"); + return NGTCP2_ERR_DISCARD_PKT; + } + + if (!ngtcp2_cid_eq(&conn->dcid.current.cid, &hd.scid)) { + /* Just discard invalid Version Negotiation packet */ + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "packet was ignored because of mismatched SCID"); + return NGTCP2_ERR_DISCARD_PKT; + } + rv = conn_on_version_negotiation(conn, &hd, pkt + hdpktlen, + pktlen - hdpktlen); + if (rv != 0) { + if (ngtcp2_err_is_fatal(rv)) { + return rv; + } + return NGTCP2_ERR_DISCARD_PKT; + } + return NGTCP2_ERR_RECV_VERSION_NEGOTIATION; + case NGTCP2_PKT_RETRY: + hdpktlen = (size_t)nread; + + ngtcp2_log_rx_pkt_hd(&conn->log, &hd); + + if (conn->server) { + return NGTCP2_ERR_DISCARD_PKT; + } + + if (conn_verify_fixed_bit(conn, &hd) != 0) { + return NGTCP2_ERR_DISCARD_PKT; + } + + /* Receiving Retry packet after getting Initial packet from server + is invalid. */ + if (conn->flags & NGTCP2_CONN_FLAG_INITIAL_PKT_PROCESSED) { + return NGTCP2_ERR_DISCARD_PKT; + } + + if (conn->client_chosen_version != hd.version) { + return NGTCP2_ERR_DISCARD_PKT; + } + + rv = conn_on_retry(conn, &hd, hdpktlen, pkt, pktlen, ts); + if (rv != 0) { + if (ngtcp2_err_is_fatal(rv)) { + return rv; + } + return NGTCP2_ERR_DISCARD_PKT; + } + return (ngtcp2_ssize)pktlen; + } + + if (pktlen < (size_t)nread + hd.len) { + return NGTCP2_ERR_DISCARD_PKT; + } + + pktlen = (size_t)nread + hd.len; + + if (!ngtcp2_is_supported_version(hd.version)) { + return NGTCP2_ERR_DISCARD_PKT; + } + + if (conn->server) { + if (hd.version != conn->client_chosen_version && + (!conn->negotiated_version || hd.version != conn->negotiated_version)) { + return NGTCP2_ERR_DISCARD_PKT; + } + } else if (hd.version != conn->client_chosen_version && + conn->negotiated_version && + hd.version != conn->negotiated_version) { + return NGTCP2_ERR_DISCARD_PKT; + } + + if (conn_verify_fixed_bit(conn, &hd) != 0) { + return NGTCP2_ERR_DISCARD_PKT; + } + + /* Quoted from spec: if subsequent packets of those types include a + different Source Connection ID, they MUST be discarded. */ + if ((conn->flags & NGTCP2_CONN_FLAG_INITIAL_PKT_PROCESSED) && + !ngtcp2_cid_eq(&conn->dcid.current.cid, &hd.scid)) { + ngtcp2_log_rx_pkt_hd(&conn->log, &hd); + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "packet was ignored because of mismatched SCID"); + return NGTCP2_ERR_DISCARD_PKT; + } + + switch (hd.type) { + case NGTCP2_PKT_0RTT: + if (!conn->server) { + return NGTCP2_ERR_DISCARD_PKT; + } + + if (hd.version != conn->client_chosen_version) { + return NGTCP2_ERR_DISCARD_PKT; + } + + if (conn->flags & NGTCP2_CONN_FLAG_INITIAL_PKT_PROCESSED) { + if (conn->early.ckm) { + ngtcp2_ssize nread2; + /* TODO Avoid to parse header twice. */ + nread2 = + conn_recv_pkt(conn, path, pi, pkt, pktlen, dgramlen, pkt_ts, ts); + if (nread2 < 0) { + return nread2; + } + } + + /* Discard 0-RTT packet if we don't have a key to decrypt it. */ + return (ngtcp2_ssize)pktlen; + } + + /* Buffer re-ordered 0-RTT packet. */ + ngtcp2_log_info(&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); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + return rv; + } + + return (ngtcp2_ssize)pktlen; + case NGTCP2_PKT_INITIAL: + if (!conn->in_pktns) { + ngtcp2_log_info( + &conn->log, NGTCP2_LOG_EVENT_PKT, + "Initial packet is discarded because keys have been discarded"); + return (ngtcp2_ssize)pktlen; + } + + assert(conn->in_pktns); + + if (conn->server) { + if (dgramlen < NGTCP2_MAX_UDP_PAYLOAD_SIZE) { + ngtcp2_log_info( + &conn->log, NGTCP2_LOG_EVENT_PKT, + "Initial packet was ignored because it is included in UDP datagram " + "less than %zu bytes: %zu bytes", + NGTCP2_MAX_UDP_PAYLOAD_SIZE, dgramlen); + return NGTCP2_ERR_DISCARD_PKT; + } + if (conn->local.settings.tokenlen) { + rv = verify_token(conn->local.settings.token, + conn->local.settings.tokenlen, &hd); + if (rv != 0) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "packet was ignored because token is invalid"); + return NGTCP2_ERR_DISCARD_PKT; + } + } + if ((conn->flags & NGTCP2_CONN_FLAG_INITIAL_PKT_PROCESSED) == 0) { + /* Set rcid here so that it is available to callback. If this + packet is discarded later in this function and no packet is + processed in this connection attempt so far, connection + will be dropped. */ + conn->rcid = hd.dcid; + + rv = conn_call_recv_client_initial(conn, &hd.dcid); + if (rv != 0) { + return rv; + } + } + } else { + if (hd.tokenlen != 0) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "packet was ignored because token is not empty"); + return NGTCP2_ERR_DISCARD_PKT; + } + + if (hd.version != conn->client_chosen_version && + !conn->negotiated_version && conn->vneg.version != hd.version) { + if (!vneg_available_versions_includes(conn->vneg.available_versions, + conn->vneg.available_versionslen, + hd.version)) { + return NGTCP2_ERR_DISCARD_PKT; + } + + /* Install new Initial keys using QUIC version = hd.version */ + rv = conn_call_version_negotiation( + conn, hd.version, + (conn->flags & NGTCP2_CONN_FLAG_RECV_RETRY) + ? &conn->dcid.current.cid + : &conn->rcid); + if (rv != 0) { + return rv; + } + + assert(conn->vneg.version == hd.version); + } + } + + pktns = conn->in_pktns; + crypto = &pktns->crypto.strm; + encryption_level = NGTCP2_ENCRYPTION_LEVEL_INITIAL; + + if (hd.version == conn->client_chosen_version) { + ckm = pktns->crypto.rx.ckm; + hp_ctx = &pktns->crypto.rx.hp_ctx; + } else { + assert(conn->vneg.version == hd.version); + + ckm = conn->vneg.rx.ckm; + hp_ctx = &conn->vneg.rx.hp_ctx; + } + + break; + case NGTCP2_PKT_HANDSHAKE: + if (hd.version != conn->negotiated_version) { + return NGTCP2_ERR_DISCARD_PKT; + } + + if (!conn->hs_pktns->crypto.rx.ckm) { + if (conn->server) { + ngtcp2_log_info( + &conn->log, NGTCP2_LOG_EVENT_PKT, + "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); + + rv = conn_buffer_pkt(conn, conn->hs_pktns, path, pi, pkt, pktlen, + dgramlen, ts); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + return rv; + } + return (ngtcp2_ssize)pktlen; + } + + pktns = conn->hs_pktns; + crypto = &pktns->crypto.strm; + encryption_level = NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE; + ckm = pktns->crypto.rx.ckm; + hp_ctx = &pktns->crypto.rx.hp_ctx; + + break; + default: + ngtcp2_unreachable(); + } + + hp_mask = conn->callbacks.hp_mask; + decrypt = conn->callbacks.decrypt; + aead = &pktns->crypto.ctx.aead; + hp = &pktns->crypto.ctx.hp; + + assert(ckm); + assert(hp_mask); + assert(decrypt); + + rv = conn_ensure_decrypt_hp_buffer(conn, (size_t)nread + 4); + if (rv != 0) { + return rv; + } + + nwrite = decrypt_hp(&hd, conn->crypto.decrypt_hp_buf.base, hp, pkt, pktlen, + (size_t)nread, hp_ctx, hp_mask); + if (nwrite < 0) { + if (ngtcp2_err_is_fatal((int)nwrite)) { + return nwrite; + } + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "could not decrypt packet number"); + return NGTCP2_ERR_DISCARD_PKT; + } + + hdpktlen = (size_t)nwrite; + payload = pkt + hdpktlen; + payloadlen = hd.len - hd.pkt_numlen; + + hd.pkt_num = ngtcp2_pkt_adjust_pkt_num(pktns->rx.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); + return NGTCP2_ERR_DISCARD_PKT; + } + + ngtcp2_log_rx_pkt_hd(&conn->log, &hd); + + rv = ngtcp2_pkt_verify_reserved_bits(conn->crypto.decrypt_hp_buf.base[0]); + if (rv != 0) { + invalid_reserved_bits = 1; + + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "packet has incorrect reserved bits"); + + /* Will return error after decrypting payload */ + } + + if (pktns_pkt_num_is_duplicate(pktns, hd.pkt_num)) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "packet was discarded because of duplicated packet number"); + return NGTCP2_ERR_DISCARD_PKT; + } + + rv = conn_ensure_decrypt_buffer(conn, payloadlen); + if (rv != 0) { + return rv; + } + + nwrite = decrypt_pkt(conn->crypto.decrypt_buf.base, aead, payload, payloadlen, + conn->crypto.decrypt_hp_buf.base, hdpktlen, hd.pkt_num, + ckm, decrypt); + if (nwrite < 0) { + if (ngtcp2_err_is_fatal((int)nwrite)) { + return nwrite; + } + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "could not decrypt packet payload"); + return NGTCP2_ERR_DISCARD_PKT; + } + + if (invalid_reserved_bits) { + return NGTCP2_ERR_PROTO; + } + + if (!conn->server && hd.version != conn->client_chosen_version && + !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); + } + + payload = conn->crypto.decrypt_buf.base; + payloadlen = (size_t)nwrite; + + switch (hd.type) { + case NGTCP2_PKT_INITIAL: + if (!conn->server || + ((conn->flags & NGTCP2_CONN_FLAG_INITIAL_PKT_PROCESSED) && + !ngtcp2_cid_eq(&conn->rcid, &hd.dcid))) { + rv = conn_verify_dcid(conn, NULL, &hd); + if (rv != 0) { + if (ngtcp2_err_is_fatal(rv)) { + return rv; + } + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "packet was ignored because of mismatched DCID"); + return NGTCP2_ERR_DISCARD_PKT; + } + } + break; + case NGTCP2_PKT_HANDSHAKE: + rv = conn_verify_dcid(conn, NULL, &hd); + if (rv != 0) { + if (ngtcp2_err_is_fatal(rv)) { + return rv; + } + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "packet was ignored because of mismatched DCID"); + return NGTCP2_ERR_DISCARD_PKT; + } + break; + default: + ngtcp2_unreachable(); + } + + if (payloadlen == 0) { + /* QUIC packet must contain at least one frame */ + if (hd.type == NGTCP2_PKT_INITIAL) { + return NGTCP2_ERR_DISCARD_PKT; + } + return NGTCP2_ERR_PROTO; + } + + if (hd.type == NGTCP2_PKT_INITIAL && + !(conn->flags & NGTCP2_CONN_FLAG_INITIAL_PKT_PROCESSED)) { + conn->flags |= NGTCP2_CONN_FLAG_INITIAL_PKT_PROCESSED; + if (!conn->server) { + conn->dcid.current.cid = hd.scid; + } + } + + ngtcp2_qlog_pkt_received_start(&conn->qlog); + + for (; payloadlen;) { + nread = ngtcp2_pkt_decode_frame(fr, payload, payloadlen); + if (nread < 0) { + return nread; + } + + payload += nread; + payloadlen -= (size_t)nread; + + switch (fr->type) { + case NGTCP2_FRAME_ACK: + case NGTCP2_FRAME_ACK_ECN: + fr->ack.ack_delay = 0; + fr->ack.ack_delay_unscaled = 0; + break; + } + + ngtcp2_log_rx_fr(&conn->log, &hd, fr); + + switch (fr->type) { + case NGTCP2_FRAME_ACK: + case NGTCP2_FRAME_ACK_ECN: + 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); + if (rv != 0) { + return rv; + } + break; + case NGTCP2_FRAME_PADDING: + break; + case NGTCP2_FRAME_CRYPTO: + if (!conn->server && !conn->negotiated_version && + 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); + } + + rv = conn_recv_crypto(conn, encryption_level, crypto, &fr->stream); + if (rv != 0) { + return rv; + } + require_ack = 1; + break; + case NGTCP2_FRAME_CONNECTION_CLOSE: + rv = conn_recv_connection_close(conn, &fr->connection_close); + if (rv != 0) { + return rv; + } + break; + case NGTCP2_FRAME_PING: + require_ack = 1; + break; + default: + return NGTCP2_ERR_PROTO; + } + + ngtcp2_qlog_write_frame(&conn->qlog, fr); + } + + if (hd.type == NGTCP2_PKT_HANDSHAKE) { + /* Successful processing of Handshake packet from a remote + endpoint validates its source address. */ + conn->dcid.current.flags |= NGTCP2_DCID_FLAG_PATH_VALIDATED; + } + + ngtcp2_qlog_pkt_received_end(&conn->qlog, &hd, pktlen); + + rv = pktns_commit_recv_pkt_num(pktns, hd.pkt_num, require_ack, pkt_ts); + if (rv != 0) { + return rv; + } + + pktns_increase_ecn_counts(pktns, pi); + + /* Initial and Handshake are always acknowledged without delay. No + need to call ngtcp2_acktr_immediate_ack(). */ + rv = ngtcp2_conn_sched_ack(conn, &pktns->acktr, hd.pkt_num, require_ack, + pkt_ts); + if (rv != 0) { + return rv; + } + + conn_restart_timer_on_read(conn, ts); + + ngtcp2_qlog_metrics_updated(&conn->qlog, &conn->cstat); + + return conn->state == NGTCP2_CS_DRAINING ? NGTCP2_ERR_DRAINING + : (ngtcp2_ssize)pktlen; +} + +static int is_unrecoverable_error(int liberr) { + switch (liberr) { + case NGTCP2_ERR_CRYPTO: + case NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM: + case NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM: + case NGTCP2_ERR_TRANSPORT_PARAM: + case NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE: + return 1; + } + + return 0; +} + +/* + * conn_recv_handshake_cpkt processes compound packet during + * handshake. The buffer pointed by |pkt| might contain multiple + * packets. The 1RTT packet must be the last one because it does not + * have payload length field. + * + * This function returns the same error code returned by + * conn_recv_handshake_pkt. + */ +static ngtcp2_ssize conn_recv_handshake_cpkt(ngtcp2_conn *conn, + const ngtcp2_path *path, + const ngtcp2_pkt_info *pi, + const uint8_t *pkt, size_t pktlen, + ngtcp2_tstamp ts) { + ngtcp2_ssize nread; + size_t dgramlen = pktlen; + const uint8_t *origpkt = pkt; + uint32_t version; + + if (ngtcp2_path_eq(&conn->dcid.current.ps.path, path)) { + conn->dcid.current.bytes_recv += dgramlen; + } + + while (pktlen) { + nread = + conn_recv_handshake_pkt(conn, path, pi, pkt, pktlen, dgramlen, ts, ts); + if (nread < 0) { + if (ngtcp2_err_is_fatal((int)nread)) { + return nread; + } + + if (nread == NGTCP2_ERR_DRAINING) { + return NGTCP2_ERR_DRAINING; + } + + if ((pkt[0] & NGTCP2_HEADER_FORM_BIT) && pktlen > 4) { + /* Not a Version Negotiation packet */ + ngtcp2_get_uint32(&version, &pkt[1]); + if (ngtcp2_pkt_get_type_long(version, pkt[0]) == NGTCP2_PKT_INITIAL) { + if (conn->server) { + if (is_unrecoverable_error((int)nread)) { + /* If server gets crypto error from TLS stack, it is + unrecoverable, therefore drop connection. */ + return nread; + } + + /* 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 + valid packet and prevent the handshake from + completing. */ + if (conn->in_pktns && conn->in_pktns->rx.max_pkt_num == -1) { + return NGTCP2_ERR_DROP_CONN; + } + + return (ngtcp2_ssize)dgramlen; + } + /* client */ + if (is_unrecoverable_error((int)nread)) { + /* If client gets crypto error from TLS stack, it is + unrecoverable, therefore drop connection. */ + return nread; + } + return (ngtcp2_ssize)dgramlen; + } + } + + if (nread == NGTCP2_ERR_DISCARD_PKT) { + return (ngtcp2_ssize)dgramlen; + } + + return nread; + } + + if (nread == 0) { + assert(!(pkt[0] & NGTCP2_HEADER_FORM_BIT)); + return pkt - origpkt; + } + + assert(pktlen >= (size_t)nread); + pkt += nread; + pktlen -= (size_t)nread; + + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "read packet %td left %zu", nread, pktlen); + } + + return (ngtcp2_ssize)dgramlen; +} + +int ngtcp2_conn_init_stream(ngtcp2_conn *conn, ngtcp2_strm *strm, + int64_t stream_id, void *stream_user_data) { + int rv; + uint64_t max_rx_offset; + uint64_t max_tx_offset; + int local_stream = conn_local_stream(conn, stream_id); + + assert(conn->remote.transport_params); + + if (bidi_stream(stream_id)) { + if (local_stream) { + max_rx_offset = + conn->local.transport_params.initial_max_stream_data_bidi_local; + max_tx_offset = + conn->remote.transport_params->initial_max_stream_data_bidi_remote; + } else { + max_rx_offset = + conn->local.transport_params.initial_max_stream_data_bidi_remote; + max_tx_offset = + conn->remote.transport_params->initial_max_stream_data_bidi_local; + } + } else if (local_stream) { + max_rx_offset = 0; + max_tx_offset = conn->remote.transport_params->initial_max_stream_data_uni; + } else { + max_rx_offset = conn->local.transport_params.initial_max_stream_data_uni; + max_tx_offset = 0; + } + + ngtcp2_strm_init(strm, stream_id, NGTCP2_STRM_FLAG_NONE, max_rx_offset, + max_tx_offset, stream_user_data, &conn->frc_objalloc, + conn->mem); + + rv = ngtcp2_map_insert(&conn->strms, (ngtcp2_map_key_type)strm->stream_id, + strm); + if (rv != 0) { + assert(rv != NGTCP2_ERR_INVALID_ARGUMENT); + goto fail; + } + + return 0; + +fail: + ngtcp2_strm_free(strm); + return rv; +} + +/* + * conn_emit_pending_stream_data passes buffered ordered stream data + * to the application. |rx_offset| is the first offset to deliver to + * the application. This function assumes that the data up to + * |rx_offset| has been delivered already. This function only passes + * the ordered data without any gap. If there is a gap, it stops + * providing the data to the application, and returns. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_CALLBACK_FAILURE + * User callback failed. + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +static int conn_emit_pending_stream_data(ngtcp2_conn *conn, ngtcp2_strm *strm, + uint64_t rx_offset) { + size_t datalen; + const uint8_t *data; + int rv; + uint64_t offset; + uint32_t sdflags; + int handshake_completed = conn_is_tls_handshake_completed(conn); + + if (!strm->rx.rob) { + return 0; + } + + for (;;) { + /* Stop calling callback if application has called + ngtcp2_conn_shutdown_stream_read() inside the callback. + Because it doubly counts connection window. */ + if (strm->flags & NGTCP2_STRM_FLAG_STOP_SENDING) { + return 0; + } + + datalen = ngtcp2_rob_data_at(strm->rx.rob, &data, rx_offset); + if (datalen == 0) { + assert(rx_offset == ngtcp2_strm_rx_offset(strm)); + return 0; + } + + offset = rx_offset; + rx_offset += datalen; + + sdflags = NGTCP2_STREAM_DATA_FLAG_NONE; + if ((strm->flags & NGTCP2_STRM_FLAG_SHUT_RD) && + rx_offset == strm->rx.last_offset) { + sdflags |= NGTCP2_STREAM_DATA_FLAG_FIN; + } + if (!handshake_completed) { + sdflags |= NGTCP2_STREAM_DATA_FLAG_0RTT; + } + + rv = conn_call_recv_stream_data(conn, strm, sdflags, offset, data, datalen); + if (rv != 0) { + return rv; + } + + ngtcp2_rob_pop(strm->rx.rob, rx_offset - datalen, datalen); + } +} + +/* + * conn_recv_crypto is called when CRYPTO frame |fr| is received. + * |rx_offset_base| is the offset in the entire TLS handshake stream. + * fr->offset specifies the offset in each encryption level. + * |max_rx_offset| is, if it is nonzero, the maximum offset in the + * entire TLS handshake stream that |fr| can carry. + * |encryption_level| is the encryption level where this data is + * received. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_PROTO + * CRYPTO frame has invalid offset. + * NGTCP2_ERR_NOMEM + * Out of memory. + * NGTCP2_ERR_CRYPTO + * TLS stack reported error. + * NGTCP2_ERR_FRAME_ENCODING + * The end offset exceeds the maximum value. + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + */ +static int conn_recv_crypto(ngtcp2_conn *conn, + ngtcp2_encryption_level encryption_level, + ngtcp2_strm *crypto, const ngtcp2_stream *fr) { + uint64_t fr_end_offset; + uint64_t rx_offset; + int rv; + + if (fr->datacnt == 0) { + return 0; + } + + fr_end_offset = fr->offset + fr->data[0].len; + + if (NGTCP2_MAX_VARINT < fr_end_offset) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + rx_offset = ngtcp2_strm_rx_offset(crypto); + + if (fr_end_offset <= rx_offset) { + if (conn->server && + !(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_EARLY_RETRANSMIT) && + encryption_level == NGTCP2_ENCRYPTION_LEVEL_INITIAL) { + /* https://datatracker.ietf.org/doc/html/rfc9002#section-6.2.3: + Speeding Up Handshake Completion + + When a server receives an Initial packet containing duplicate + CRYPTO data, it can assume the client did not receive all of + the server's CRYPTO data sent in Initial packets, or the + client's estimated RTT is too small. ... To speed up + handshake completion under these conditions, an endpoint MAY + send a packet containing unacknowledged CRYPTO data earlier + than the PTO expiry, subject to address validation limits; + ... */ + conn->flags |= NGTCP2_CONN_FLAG_HANDSHAKE_EARLY_RETRANSMIT; + conn->in_pktns->rtb.probe_pkt_left = 1; + conn->hs_pktns->rtb.probe_pkt_left = 1; + } + return 0; + } + + crypto->rx.last_offset = ngtcp2_max(crypto->rx.last_offset, fr_end_offset); + + /* TODO Before dispatching incoming data to TLS stack, make sure + that previous data in previous encryption level has been + completely sent to TLS stack. Usually, if data is left, it is an + error because key is generated after consuming all data in the + previous encryption level. */ + if (fr->offset <= rx_offset) { + size_t ncut = (size_t)(rx_offset - fr->offset); + const uint8_t *data = fr->data[0].base + ncut; + size_t datalen = fr->data[0].len - ncut; + uint64_t offset = rx_offset; + + rx_offset += datalen; + ngtcp2_strm_update_rx_offset(crypto, rx_offset); + + rv = conn_call_recv_crypto_data(conn, encryption_level, offset, data, + datalen); + if (rv != 0) { + return rv; + } + + rv = conn_emit_pending_crypto_data(conn, encryption_level, crypto, + rx_offset); + if (rv != 0) { + return rv; + } + + return 0; + } + + if (fr_end_offset - rx_offset > NGTCP2_MAX_REORDERED_CRYPTO_DATA) { + return NGTCP2_ERR_CRYPTO_BUFFER_EXCEEDED; + } + + return ngtcp2_strm_recv_reordering(crypto, fr->data[0].base, fr->data[0].len, + fr->offset); +} + +/* + * conn_max_data_violated returns nonzero if receiving |datalen| + * violates connection flow control on local endpoint. + */ +static int conn_max_data_violated(ngtcp2_conn *conn, uint64_t datalen) { + return conn->rx.max_offset - conn->rx.offset < datalen; +} + +/* + * conn_recv_stream is called when STREAM frame |fr| is received. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_STREAM_STATE + * STREAM frame is received for a local stream which is not + * initiated; or STREAM frame is received for a local + * unidirectional stream + * NGTCP2_ERR_STREAM_LIMIT + * STREAM frame has remote stream ID which is strictly greater + * than the allowed limit. + * NGTCP2_ERR_NOMEM + * Out of memory. + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + * NGTCP2_ERR_FLOW_CONTROL + * Flow control limit is violated; or the end offset of stream + * data is beyond the NGTCP2_MAX_VARINT. + * NGTCP2_ERR_FINAL_SIZE + * STREAM frame has strictly larger end offset than it is + * permitted. + */ +static int conn_recv_stream(ngtcp2_conn *conn, const ngtcp2_stream *fr) { + int rv; + ngtcp2_strm *strm; + ngtcp2_idtr *idtr; + uint64_t rx_offset, fr_end_offset; + int local_stream; + int bidi; + uint64_t datalen = ngtcp2_vec_len(fr->data, fr->datacnt); + uint32_t sdflags = NGTCP2_STREAM_DATA_FLAG_NONE; + + local_stream = conn_local_stream(conn, fr->stream_id); + bidi = bidi_stream(fr->stream_id); + + if (bidi) { + if (local_stream) { + if (conn->local.bidi.next_stream_id <= fr->stream_id) { + return NGTCP2_ERR_STREAM_STATE; + } + } else if (conn->remote.bidi.max_streams < + ngtcp2_ord_stream_id(fr->stream_id)) { + return NGTCP2_ERR_STREAM_LIMIT; + } + + idtr = &conn->remote.bidi.idtr; + } else { + if (local_stream) { + return NGTCP2_ERR_STREAM_STATE; + } + if (conn->remote.uni.max_streams < ngtcp2_ord_stream_id(fr->stream_id)) { + return NGTCP2_ERR_STREAM_LIMIT; + } + + idtr = &conn->remote.uni.idtr; + } + + if (NGTCP2_MAX_VARINT - datalen < fr->offset) { + return NGTCP2_ERR_FLOW_CONTROL; + } + + 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. */ + return 0; + } + + rv = ngtcp2_idtr_open(idtr, fr->stream_id); + if (rv != 0) { + if (ngtcp2_err_is_fatal(rv)) { + 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. */ + return 0; + } + + strm = ngtcp2_objalloc_strm_get(&conn->strm_objalloc); + if (strm == NULL) { + return NGTCP2_ERR_NOMEM; + } + + rv = ngtcp2_conn_init_stream(conn, strm, fr->stream_id, NULL); + if (rv != 0) { + ngtcp2_objalloc_strm_release(&conn->strm_objalloc, strm); + return rv; + } + + if (!bidi) { + ngtcp2_strm_shutdown(strm, NGTCP2_STRM_FLAG_SHUT_WR); + strm->flags |= NGTCP2_STRM_FLAG_FIN_ACKED; + } + + rv = conn_call_stream_open(conn, strm); + if (rv != 0) { + return rv; + } + } + + fr_end_offset = fr->offset + datalen; + + if (strm->rx.max_offset < fr_end_offset) { + return NGTCP2_ERR_FLOW_CONTROL; + } + + if (strm->rx.last_offset < fr_end_offset) { + uint64_t len = fr_end_offset - strm->rx.last_offset; + + if (conn_max_data_violated(conn, len)) { + return NGTCP2_ERR_FLOW_CONTROL; + } + + conn->rx.offset += len; + + if (strm->flags & NGTCP2_STRM_FLAG_STOP_SENDING) { + ngtcp2_conn_extend_max_offset(conn, len); + } + } + + rx_offset = ngtcp2_strm_rx_offset(strm); + + if (fr->fin) { + if (strm->flags & NGTCP2_STRM_FLAG_SHUT_RD) { + if (strm->rx.last_offset != fr_end_offset) { + return NGTCP2_ERR_FINAL_SIZE; + } + + if (strm->flags & NGTCP2_STRM_FLAG_RESET_STREAM_RECVED) { + return 0; + } + + if (rx_offset == fr_end_offset) { + return 0; + } + } else if (strm->rx.last_offset > fr_end_offset) { + return NGTCP2_ERR_FINAL_SIZE; + } else { + strm->rx.last_offset = fr_end_offset; + + ngtcp2_strm_shutdown(strm, NGTCP2_STRM_FLAG_SHUT_RD); + } + } else { + if ((strm->flags & NGTCP2_STRM_FLAG_SHUT_RD) && + strm->rx.last_offset < fr_end_offset) { + return NGTCP2_ERR_FINAL_SIZE; + } + + strm->rx.last_offset = ngtcp2_max(strm->rx.last_offset, fr_end_offset); + + if (fr_end_offset <= rx_offset) { + return 0; + } + + if (strm->flags & NGTCP2_STRM_FLAG_RESET_STREAM_RECVED) { + return 0; + } + } + + if (fr->offset <= rx_offset) { + size_t ncut = (size_t)(rx_offset - fr->offset); + uint64_t offset = rx_offset; + const uint8_t *data; + int fin; + + if (fr->datacnt) { + data = fr->data[0].base + ncut; + datalen -= ncut; + + rx_offset += datalen; + ngtcp2_strm_update_rx_offset(strm, rx_offset); + } else { + data = NULL; + datalen = 0; + } + + if (strm->flags & NGTCP2_STRM_FLAG_STOP_SENDING) { + return ngtcp2_conn_close_stream_if_shut_rdwr(conn, strm); + } + + 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; + } + + rv = conn_emit_pending_stream_data(conn, strm, rx_offset); + if (rv != 0) { + return rv; + } + } + } else if (fr->datacnt) { + rv = ngtcp2_strm_recv_reordering(strm, fr->data[0].base, fr->data[0].len, + fr->offset); + if (rv != 0) { + return rv; + } + } + return ngtcp2_conn_close_stream_if_shut_rdwr(conn, strm); +} + +/* + * conn_reset_stream adds RESET_STREAM frame to the transmission + * queue. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +static int conn_reset_stream(ngtcp2_conn *conn, ngtcp2_strm *strm, + uint64_t app_error_code) { + strm->flags |= NGTCP2_STRM_FLAG_SEND_RESET_STREAM; + strm->tx.reset_stream_app_error_code = app_error_code; + + if (ngtcp2_strm_is_tx_queued(strm)) { + return 0; + } + + strm->cycle = conn_tx_strmq_first_cycle(conn); + + return ngtcp2_conn_tx_strmq_push(conn, strm); +} + +/* + * conn_stop_sending adds STOP_SENDING frame to the transmission + * queue. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +static int conn_stop_sending(ngtcp2_conn *conn, ngtcp2_strm *strm, + uint64_t app_error_code) { + strm->flags |= NGTCP2_STRM_FLAG_SEND_STOP_SENDING; + strm->tx.stop_sending_app_error_code = app_error_code; + + if (ngtcp2_strm_is_tx_queued(strm)) { + return 0; + } + + strm->cycle = conn_tx_strmq_first_cycle(conn); + + return ngtcp2_conn_tx_strmq_push(conn, strm); +} + +/* + * handle_max_remote_streams_extension extends + * |*punsent_max_remote_streams| by |n| if a condition allows it. + */ +static void +handle_max_remote_streams_extension(uint64_t *punsent_max_remote_streams, + size_t n) { + if ( +#if SIZE_MAX > UINT32_MAX + NGTCP2_MAX_STREAMS < n || +#endif /* SIZE_MAX > UINT32_MAX */ + *punsent_max_remote_streams > (uint64_t)(NGTCP2_MAX_STREAMS - n)) { + *punsent_max_remote_streams = NGTCP2_MAX_STREAMS; + } else { + *punsent_max_remote_streams += n; + } +} + +/* + * conn_recv_reset_stream is called when RESET_STREAM |fr| is + * received. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_STREAM_STATE + * RESET_STREAM frame is received to the local stream which is not + * initiated. + * NGTCP2_ERR_STREAM_LIMIT + * RESET_STREAM frame has remote stream ID which is strictly + * greater than the allowed limit. + * NGTCP2_ERR_PROTO + * RESET_STREAM frame is received to the local unidirectional + * stream + * NGTCP2_ERR_NOMEM + * Out of memory. + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + * NGTCP2_ERR_FLOW_CONTROL + * Flow control limit is violated; or the final size is beyond the + * NGTCP2_MAX_VARINT. + * NGTCP2_ERR_FINAL_SIZE + * The final offset is strictly larger than it is permitted. + */ +static int conn_recv_reset_stream(ngtcp2_conn *conn, + const ngtcp2_reset_stream *fr) { + ngtcp2_strm *strm; + int local_stream = conn_local_stream(conn, fr->stream_id); + int bidi = bidi_stream(fr->stream_id); + uint64_t datalen; + ngtcp2_idtr *idtr; + int rv; + + /* TODO share this piece of code */ + if (bidi) { + if (local_stream) { + if (conn->local.bidi.next_stream_id <= fr->stream_id) { + return NGTCP2_ERR_STREAM_STATE; + } + } else if (conn->remote.bidi.max_streams < + ngtcp2_ord_stream_id(fr->stream_id)) { + return NGTCP2_ERR_STREAM_LIMIT; + } + + idtr = &conn->remote.bidi.idtr; + } else { + if (local_stream) { + return NGTCP2_ERR_PROTO; + } + if (conn->remote.uni.max_streams < ngtcp2_ord_stream_id(fr->stream_id)) { + return NGTCP2_ERR_STREAM_LIMIT; + } + + idtr = &conn->remote.uni.idtr; + } + + if (NGTCP2_MAX_VARINT < fr->final_size) { + return NGTCP2_ERR_FLOW_CONTROL; + } + + strm = ngtcp2_conn_find_stream(conn, fr->stream_id); + if (strm == NULL) { + if (local_stream) { + return 0; + } + + rv = ngtcp2_idtr_open(idtr, fr->stream_id); + if (rv != 0) { + if (ngtcp2_err_is_fatal(rv)) { + return rv; + } + assert(rv == NGTCP2_ERR_STREAM_IN_USE); + return 0; + } + + if (conn_initial_stream_rx_offset(conn, fr->stream_id) < fr->final_size || + conn_max_data_violated(conn, fr->final_size)) { + return NGTCP2_ERR_FLOW_CONTROL; + } + + /* Stream is reset before we create ngtcp2_strm object. */ + conn->rx.offset += fr->final_size; + ngtcp2_conn_extend_max_offset(conn, fr->final_size); + + rv = conn_call_stream_reset(conn, fr->stream_id, fr->final_size, + fr->app_error_code, NULL); + if (rv != 0) { + return rv; + } + + /* There will be no activity in this stream because we got + RESET_STREAM and don't write stream data any further. This + effectively allows another new stream for peer. */ + if (bidi) { + handle_max_remote_streams_extension(&conn->remote.bidi.unsent_max_streams, + 1); + } else { + handle_max_remote_streams_extension(&conn->remote.uni.unsent_max_streams, + 1); + } + + return 0; + } + + if ((strm->flags & NGTCP2_STRM_FLAG_SHUT_RD)) { + if (strm->rx.last_offset != fr->final_size) { + return NGTCP2_ERR_FINAL_SIZE; + } + } else if (strm->rx.last_offset > fr->final_size) { + return NGTCP2_ERR_FINAL_SIZE; + } + + if (strm->flags & NGTCP2_STRM_FLAG_RESET_STREAM_RECVED) { + return 0; + } + + if (strm->rx.max_offset < fr->final_size) { + return NGTCP2_ERR_FLOW_CONTROL; + } + + datalen = fr->final_size - strm->rx.last_offset; + + if (conn_max_data_violated(conn, datalen)) { + return NGTCP2_ERR_FLOW_CONTROL; + } + + rv = conn_call_stream_reset(conn, fr->stream_id, fr->final_size, + fr->app_error_code, strm->stream_user_data); + if (rv != 0) { + return rv; + } + + /* Extend connection flow control window for the amount of data + which are not passed to application. */ + if (!(strm->flags & NGTCP2_STRM_FLAG_STOP_SENDING)) { + ngtcp2_conn_extend_max_offset(conn, strm->rx.last_offset - + ngtcp2_strm_rx_offset(strm)); + } + + conn->rx.offset += datalen; + ngtcp2_conn_extend_max_offset(conn, datalen); + + strm->rx.last_offset = fr->final_size; + strm->flags |= + NGTCP2_STRM_FLAG_SHUT_RD | NGTCP2_STRM_FLAG_RESET_STREAM_RECVED; + + ngtcp2_strm_set_app_error_code(strm, fr->app_error_code); + + return ngtcp2_conn_close_stream_if_shut_rdwr(conn, strm); +} + +/* + * conn_recv_stop_sending is called when STOP_SENDING |fr| is received. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_STREAM_STATE + * STOP_SENDING frame is received for a local stream which is not + * initiated; or STOP_SENDING frame is received for a local + * unidirectional stream. + * NGTCP2_ERR_STREAM_LIMIT + * STOP_SENDING frame has remote stream ID which is strictly + * greater than the allowed limit. + * NGTCP2_ERR_NOMEM + * Out of memory. + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + */ +static int conn_recv_stop_sending(ngtcp2_conn *conn, + const ngtcp2_stop_sending *fr) { + int rv; + ngtcp2_strm *strm; + ngtcp2_idtr *idtr; + int local_stream = conn_local_stream(conn, fr->stream_id); + int bidi = bidi_stream(fr->stream_id); + + if (bidi) { + if (local_stream) { + if (conn->local.bidi.next_stream_id <= fr->stream_id) { + return NGTCP2_ERR_STREAM_STATE; + } + } else if (conn->remote.bidi.max_streams < + ngtcp2_ord_stream_id(fr->stream_id)) { + return NGTCP2_ERR_STREAM_LIMIT; + } + + idtr = &conn->remote.bidi.idtr; + } else { + if (!local_stream || conn->local.uni.next_stream_id <= fr->stream_id) { + return NGTCP2_ERR_STREAM_STATE; + } + + idtr = &conn->remote.uni.idtr; + } + + strm = ngtcp2_conn_find_stream(conn, fr->stream_id); + if (strm == NULL) { + if (local_stream) { + return 0; + } + rv = ngtcp2_idtr_open(idtr, fr->stream_id); + if (rv != 0) { + if (ngtcp2_err_is_fatal(rv)) { + return rv; + } + assert(rv == NGTCP2_ERR_STREAM_IN_USE); + return 0; + } + + /* STOP_SENDING frame is received before we create ngtcp2_strm + object. */ + strm = ngtcp2_objalloc_strm_get(&conn->strm_objalloc); + if (strm == NULL) { + return NGTCP2_ERR_NOMEM; + } + rv = ngtcp2_conn_init_stream(conn, strm, fr->stream_id, NULL); + if (rv != 0) { + ngtcp2_objalloc_strm_release(&conn->strm_objalloc, strm); + return rv; + } + + rv = conn_call_stream_open(conn, strm); + if (rv != 0) { + return rv; + } + } + + if (strm->flags & NGTCP2_STRM_FLAG_STOP_SENDING_RECVED) { + return 0; + } + + ngtcp2_strm_set_app_error_code(strm, fr->app_error_code); + + /* No RESET_STREAM is required if we have sent FIN and all data have + been acknowledged. */ + if (!ngtcp2_strm_is_all_tx_data_fin_acked(strm) && + !(strm->flags & NGTCP2_STRM_FLAG_RESET_STREAM)) { + rv = conn_reset_stream(conn, strm, fr->app_error_code); + if (rv != 0) { + return rv; + } + } + + strm->flags |= NGTCP2_STRM_FLAG_SHUT_WR | + NGTCP2_STRM_FLAG_STOP_SENDING_RECVED | + NGTCP2_STRM_FLAG_RESET_STREAM; + + ngtcp2_strm_streamfrq_clear(strm); + + return ngtcp2_conn_close_stream_if_shut_rdwr(conn, strm); +} + +/* + * check_stateless_reset returns nonzero if Stateless Reset |sr| + * coming via |path| is valid against |dcid|. + */ +static int check_stateless_reset(const ngtcp2_dcid *dcid, + const ngtcp2_path *path, + const ngtcp2_pkt_stateless_reset *sr) { + return ngtcp2_path_eq(&dcid->ps.path, path) && + ngtcp2_dcid_verify_stateless_reset_token( + dcid, sr->stateless_reset_token) == 0; +} + +/* + * conn_on_stateless_reset decodes Stateless Reset from the buffer + * pointed by |payload| whose length is |payloadlen|. |payload| + * should start after first byte of packet. + * + * If Stateless Reset is decoded, and the Stateless Reset Token is + * validated, the connection is closed. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_INVALID_ARGUMENT + * Could not decode Stateless Reset; or Stateless Reset Token does + * not match; or No stateless reset token is available. + * NGTCP2_ERR_CALLBACK_FAILURE + * User callback failed. + */ +static int conn_on_stateless_reset(ngtcp2_conn *conn, const ngtcp2_path *path, + const uint8_t *payload, size_t payloadlen) { + int rv = 1; + ngtcp2_pv *pv = conn->pv; + ngtcp2_dcid *dcid; + ngtcp2_pkt_stateless_reset sr; + size_t len, i; + + rv = ngtcp2_pkt_decode_stateless_reset(&sr, payload, payloadlen); + if (rv != 0) { + return rv; + } + + if (!check_stateless_reset(&conn->dcid.current, path, &sr) && + (!pv || (!check_stateless_reset(&pv->dcid, path, &sr) && + (!(pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) || + !check_stateless_reset(&pv->fallback_dcid, path, &sr))))) { + len = ngtcp2_ringbuf_len(&conn->dcid.retired.rb); + for (i = 0; i < len; ++i) { + dcid = ngtcp2_ringbuf_get(&conn->dcid.retired.rb, i); + if (check_stateless_reset(dcid, path, &sr)) { + break; + } + } + + if (i == len) { + len = ngtcp2_ringbuf_len(&conn->dcid.bound.rb); + for (i = 0; i < len; ++i) { + dcid = ngtcp2_ringbuf_get(&conn->dcid.bound.rb, i); + if (check_stateless_reset(dcid, path, &sr)) { + break; + } + } + + if (i == len) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + } + } + + conn->state = NGTCP2_CS_DRAINING; + + ngtcp2_log_rx_sr(&conn->log, &sr); + + ngtcp2_qlog_stateless_reset_pkt_received(&conn->qlog, &sr); + + return conn_call_recv_stateless_reset(conn, &sr); +} + +/* + * conn_recv_max_streams processes the incoming MAX_STREAMS frame + * |fr|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_CALLBACK_FAILURE + * User callback failed. + * NGTCP2_ERR_FRAME_ENCODING + * The maximum streams field exceeds the maximum value. + */ +static int conn_recv_max_streams(ngtcp2_conn *conn, + const ngtcp2_max_streams *fr) { + uint64_t n; + + if (fr->max_streams > NGTCP2_MAX_STREAMS) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + n = ngtcp2_min(fr->max_streams, NGTCP2_MAX_STREAMS); + + if (fr->type == NGTCP2_FRAME_MAX_STREAMS_BIDI) { + if (conn->local.bidi.max_streams < n) { + conn->local.bidi.max_streams = n; + return conn_call_extend_max_local_streams_bidi(conn, n); + } + return 0; + } + + if (conn->local.uni.max_streams < n) { + conn->local.uni.max_streams = n; + return conn_call_extend_max_local_streams_uni(conn, n); + } + return 0; +} + +static int conn_retire_dcid_prior_to(ngtcp2_conn *conn, ngtcp2_ringbuf *rb, + uint64_t retire_prior_to) { + size_t i; + ngtcp2_dcid *dcid, *last; + int rv; + + for (i = 0; i < ngtcp2_ringbuf_len(rb);) { + dcid = ngtcp2_ringbuf_get(rb, i); + if (dcid->seq >= retire_prior_to) { + ++i; + continue; + } + + rv = conn_retire_dcid_seq(conn, dcid->seq); + if (rv != 0) { + return rv; + } + + if (i == 0) { + ngtcp2_ringbuf_pop_front(rb); + continue; + } + + if (i == ngtcp2_ringbuf_len(rb) - 1) { + ngtcp2_ringbuf_pop_back(rb); + break; + } + + last = ngtcp2_ringbuf_get(rb, ngtcp2_ringbuf_len(rb) - 1); + ngtcp2_dcid_copy(dcid, last); + ngtcp2_ringbuf_pop_back(rb); + } + + return 0; +} + +/* + * conn_recv_new_connection_id processes the incoming + * NEW_CONNECTION_ID frame |fr|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_PROTO + * |fr| has the duplicated sequence number with different CID or + * token; or DCID is zero-length. + */ +static int conn_recv_new_connection_id(ngtcp2_conn *conn, + const ngtcp2_new_connection_id *fr) { + size_t i, len; + ngtcp2_dcid *dcid; + ngtcp2_pv *pv = conn->pv; + int rv; + int found = 0; + size_t extra_dcid = 0; + + if (conn->dcid.current.cid.datalen == 0) { + return NGTCP2_ERR_PROTO; + } + + if (fr->retire_prior_to > fr->seq) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + rv = ngtcp2_dcid_verify_uniqueness(&conn->dcid.current, fr->seq, &fr->cid, + fr->stateless_reset_token); + if (rv != 0) { + return rv; + } + if (ngtcp2_cid_eq(&conn->dcid.current.cid, &fr->cid)) { + found = 1; + } + + if (pv) { + rv = ngtcp2_dcid_verify_uniqueness(&pv->dcid, fr->seq, &fr->cid, + fr->stateless_reset_token); + if (rv != 0) { + return rv; + } + if (ngtcp2_cid_eq(&pv->dcid.cid, &fr->cid)) { + found = 1; + } + } + + len = ngtcp2_ringbuf_len(&conn->dcid.bound.rb); + + for (i = 0; i < len; ++i) { + dcid = ngtcp2_ringbuf_get(&conn->dcid.bound.rb, i); + rv = ngtcp2_dcid_verify_uniqueness(dcid, fr->seq, &fr->cid, + fr->stateless_reset_token); + if (rv != 0) { + return NGTCP2_ERR_PROTO; + } + if (ngtcp2_cid_eq(&dcid->cid, &fr->cid)) { + found = 1; + } + } + + len = ngtcp2_ringbuf_len(&conn->dcid.unused.rb); + + for (i = 0; i < len; ++i) { + dcid = ngtcp2_ringbuf_get(&conn->dcid.unused.rb, i); + rv = ngtcp2_dcid_verify_uniqueness(dcid, fr->seq, &fr->cid, + fr->stateless_reset_token); + if (rv != 0) { + return NGTCP2_ERR_PROTO; + } + if (ngtcp2_cid_eq(&dcid->cid, &fr->cid)) { + found = 1; + } + } + + if (conn->dcid.retire_prior_to < fr->retire_prior_to) { + conn->dcid.retire_prior_to = fr->retire_prior_to; + + rv = conn_retire_dcid_prior_to(conn, &conn->dcid.bound.rb, + fr->retire_prior_to); + if (rv != 0) { + return rv; + } + + rv = conn_retire_dcid_prior_to(conn, &conn->dcid.unused.rb, + conn->dcid.retire_prior_to); + if (rv != 0) { + return rv; + } + } else if (fr->seq < conn->dcid.retire_prior_to) { + /* If packets are reordered, we might have retire_prior_to which + is larger than fr->seq. + + A malicious peer might send crafted NEW_CONNECTION_ID to force + local endpoint to create lots of RETIRE_CONNECTION_ID frames. + For example, a peer might send seq = 50000 and retire_prior_to + = 50000. Then send NEW_CONNECTION_ID frames with seq < + 50000. */ + return conn_retire_dcid_seq(conn, fr->seq); + } + + if (found) { + return 0; + } + + if (ngtcp2_gaptr_is_pushed(&conn->dcid.seqgap, fr->seq, 1)) { + return 0; + } + + rv = ngtcp2_gaptr_push(&conn->dcid.seqgap, fr->seq, 1); + if (rv != 0) { + return rv; + } + + if (ngtcp2_ksl_len(&conn->dcid.seqgap.gap) > 32) { + ngtcp2_gaptr_drop_first_gap(&conn->dcid.seqgap); + } + + len = ngtcp2_ringbuf_len(&conn->dcid.unused.rb); + + if (conn->dcid.current.seq >= conn->dcid.retire_prior_to) { + ++extra_dcid; + } + if (pv) { + if (pv->dcid.seq != conn->dcid.current.seq && + pv->dcid.seq >= conn->dcid.retire_prior_to) { + ++extra_dcid; + } + if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) && + pv->fallback_dcid.seq != conn->dcid.current.seq && + pv->fallback_dcid.seq >= conn->dcid.retire_prior_to) { + ++extra_dcid; + } + } + + if (conn->local.transport_params.active_connection_id_limit <= + len + extra_dcid) { + return NGTCP2_ERR_CONNECTION_ID_LIMIT; + } + + if (len >= NGTCP2_MAX_DCID_POOL_SIZE) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, "too many connection ID"); + return 0; + } + + dcid = ngtcp2_ringbuf_push_back(&conn->dcid.unused.rb); + ngtcp2_dcid_init(dcid, fr->seq, &fr->cid, fr->stateless_reset_token); + + return 0; +} + +/* + * conn_post_process_recv_new_connection_id handles retirement request + * of active DCIDs. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + */ +static int conn_post_process_recv_new_connection_id(ngtcp2_conn *conn, + ngtcp2_tstamp ts) { + ngtcp2_pv *pv = conn->pv; + ngtcp2_dcid *dcid; + int rv; + + if (conn->dcid.current.seq < conn->dcid.retire_prior_to) { + if (ngtcp2_ringbuf_len(&conn->dcid.unused.rb) == 0) { + return 0; + } + + rv = conn_retire_dcid(conn, &conn->dcid.current, ts); + if (rv != 0) { + return rv; + } + + dcid = ngtcp2_ringbuf_get(&conn->dcid.unused.rb, 0); + if (pv) { + if (conn->dcid.current.seq == pv->dcid.seq) { + ngtcp2_dcid_copy_cid_token(&pv->dcid, dcid); + } + if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) && + conn->dcid.current.seq == pv->fallback_dcid.seq) { + ngtcp2_dcid_copy_cid_token(&pv->fallback_dcid, dcid); + } + } + + ngtcp2_dcid_copy_cid_token(&conn->dcid.current, dcid); + ngtcp2_ringbuf_pop_front(&conn->dcid.unused.rb); + + rv = conn_call_activate_dcid(conn, &conn->dcid.current); + if (rv != 0) { + return rv; + } + } + + if (pv) { + if (pv->dcid.seq < conn->dcid.retire_prior_to) { + if (ngtcp2_ringbuf_len(&conn->dcid.unused.rb)) { + rv = conn_retire_dcid(conn, &pv->dcid, ts); + if (rv != 0) { + return rv; + } + + dcid = ngtcp2_ringbuf_get(&conn->dcid.unused.rb, 0); + + if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) && + pv->dcid.seq == pv->fallback_dcid.seq) { + ngtcp2_dcid_copy_cid_token(&pv->fallback_dcid, dcid); + } + + ngtcp2_dcid_copy_cid_token(&pv->dcid, dcid); + ngtcp2_ringbuf_pop_front(&conn->dcid.unused.rb); + + rv = conn_call_activate_dcid(conn, &pv->dcid); + if (rv != 0) { + return rv; + } + } else { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PTV, + "path migration is aborted because connection ID is" + "retired and no unused connection ID is available"); + + return conn_abort_pv(conn, ts); + } + } + if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) && + pv->fallback_dcid.seq < conn->dcid.retire_prior_to) { + if (ngtcp2_ringbuf_len(&conn->dcid.unused.rb)) { + rv = conn_retire_dcid(conn, &pv->fallback_dcid, ts); + if (rv != 0) { + return rv; + } + + dcid = ngtcp2_ringbuf_get(&conn->dcid.unused.rb, 0); + ngtcp2_dcid_copy_cid_token(&pv->fallback_dcid, dcid); + ngtcp2_ringbuf_pop_front(&conn->dcid.unused.rb); + + rv = conn_call_activate_dcid(conn, &pv->fallback_dcid); + if (rv != 0) { + return rv; + } + } else { + /* Now we have no fallback dcid. */ + return conn_abort_pv(conn, ts); + } + } + } + + return 0; +} + +/* + * conn_recv_retire_connection_id processes the incoming + * RETIRE_CONNECTION_ID frame |fr|. |hd| is a packet header which + * |fr| is included. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + * NGTCP2_ERR_PROTO + * SCID is zero-length. + * NGTCP2_ERR_FRAME_ENCODING + * Attempt to retire CID which is used as DCID to send this frame. + */ +static int conn_recv_retire_connection_id(ngtcp2_conn *conn, + const ngtcp2_pkt_hd *hd, + const ngtcp2_retire_connection_id *fr, + ngtcp2_tstamp ts) { + ngtcp2_ksl_it it; + ngtcp2_scid *scid; + + if (conn->oscid.datalen == 0 || conn->scid.last_seq < fr->seq) { + return NGTCP2_ERR_PROTO; + } + + for (it = ngtcp2_ksl_begin(&conn->scid.set); !ngtcp2_ksl_it_end(&it); + ngtcp2_ksl_it_next(&it)) { + scid = ngtcp2_ksl_it_get(&it); + if (scid->seq == fr->seq) { + if (ngtcp2_cid_eq(&scid->cid, &hd->dcid)) { + return NGTCP2_ERR_PROTO; + } + + if (!(scid->flags & NGTCP2_SCID_FLAG_RETIRED)) { + scid->flags |= NGTCP2_SCID_FLAG_RETIRED; + ++conn->scid.num_retired; + } + + if (scid->pe.index != NGTCP2_PQ_BAD_INDEX) { + ngtcp2_pq_remove(&conn->scid.used, &scid->pe); + scid->pe.index = NGTCP2_PQ_BAD_INDEX; + } + + scid->retired_ts = ts; + + return ngtcp2_pq_push(&conn->scid.used, &scid->pe); + } + } + + return 0; +} + +/* + * conn_recv_new_token processes the incoming NEW_TOKEN frame |fr|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_FRAME_ENCODING + * Token is empty + * NGTCP2_ERR_PROTO: + * Server received NEW_TOKEN. + */ +static int conn_recv_new_token(ngtcp2_conn *conn, const ngtcp2_new_token *fr) { + if (conn->server) { + return NGTCP2_ERR_PROTO; + } + + if (fr->tokenlen == 0) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + return conn_call_recv_new_token(conn, fr->token, fr->tokenlen); +} + +/* + * conn_recv_streams_blocked_bidi processes the incoming + * STREAMS_BLOCKED (0x16). + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_FRAME_ENCODING + * Maximum Streams is larger than advertised value. + */ +static int conn_recv_streams_blocked_bidi(ngtcp2_conn *conn, + ngtcp2_streams_blocked *fr) { + if (fr->max_streams > conn->remote.bidi.max_streams) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + return 0; +} + +/* + * conn_recv_streams_blocked_uni processes the incoming + * STREAMS_BLOCKED (0x17). + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_FRAME_ENCODING + * Maximum Streams is larger than advertised value. + */ +static int conn_recv_streams_blocked_uni(ngtcp2_conn *conn, + ngtcp2_streams_blocked *fr) { + if (fr->max_streams > conn->remote.uni.max_streams) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + return 0; +} + +/* + * conn_recv_stream_data_blocked processes the incoming + * STREAM_DATA_BLOCKED frame |fr|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_STREAM_STATE + * STREAM_DATA_BLOCKED is received for a local stream which is not + * initiated; or it is received for a local unidirectional stream. + * NGTCP2_ERR_STREAM_LIMIT + * STREAM_DATA_BLOCKED has remote stream ID which is strictly + * greater than the allowed limit. + * NGTCP2_ERR_FLOW_CONTROL + * STREAM_DATA_BLOCKED frame violates flow control limit. + * NGTCP2_ERR_FINAL_SIZE + * The offset is strictly larger than it is permitted. + * NGTCP2_ERR_NOMEM + * Out of memory. + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + */ +static int conn_recv_stream_data_blocked(ngtcp2_conn *conn, + ngtcp2_stream_data_blocked *fr) { + int rv; + ngtcp2_strm *strm; + ngtcp2_idtr *idtr; + int local_stream = conn_local_stream(conn, fr->stream_id); + int bidi = bidi_stream(fr->stream_id); + uint64_t datalen; + + if (bidi) { + if (local_stream) { + if (conn->local.bidi.next_stream_id <= fr->stream_id) { + return NGTCP2_ERR_STREAM_STATE; + } + } else if (conn->remote.bidi.max_streams < + ngtcp2_ord_stream_id(fr->stream_id)) { + return NGTCP2_ERR_STREAM_LIMIT; + } + + idtr = &conn->remote.bidi.idtr; + } else { + if (local_stream) { + return NGTCP2_ERR_STREAM_STATE; + } + if (conn->remote.uni.max_streams < ngtcp2_ord_stream_id(fr->stream_id)) { + return NGTCP2_ERR_STREAM_LIMIT; + } + + idtr = &conn->remote.uni.idtr; + } + + strm = ngtcp2_conn_find_stream(conn, fr->stream_id); + if (strm == NULL) { + if (local_stream) { + return 0; + } + + rv = ngtcp2_idtr_open(idtr, fr->stream_id); + if (rv != 0) { + if (ngtcp2_err_is_fatal(rv)) { + return rv; + } + assert(rv == NGTCP2_ERR_STREAM_IN_USE); + return 0; + } + + /* Frame is received before we create ngtcp2_strm object. */ + strm = ngtcp2_objalloc_strm_get(&conn->strm_objalloc); + if (strm == NULL) { + return NGTCP2_ERR_NOMEM; + } + rv = ngtcp2_conn_init_stream(conn, strm, fr->stream_id, NULL); + if (rv != 0) { + ngtcp2_objalloc_strm_release(&conn->strm_objalloc, strm); + return rv; + } + + if (!bidi) { + ngtcp2_strm_shutdown(strm, NGTCP2_STRM_FLAG_SHUT_WR); + strm->flags |= NGTCP2_STRM_FLAG_FIN_ACKED; + } + + rv = conn_call_stream_open(conn, strm); + if (rv != 0) { + return rv; + } + } + + if (strm->rx.max_offset < fr->offset) { + return NGTCP2_ERR_FLOW_CONTROL; + } + + if (fr->offset <= strm->rx.last_offset) { + return 0; + } + + if (strm->flags & NGTCP2_STRM_FLAG_SHUT_RD) { + return NGTCP2_ERR_FINAL_SIZE; + } + + datalen = fr->offset - strm->rx.last_offset; + if (datalen) { + if (conn_max_data_violated(conn, datalen)) { + return NGTCP2_ERR_FLOW_CONTROL; + } + + conn->rx.offset += datalen; + + if (strm->flags & NGTCP2_STRM_FLAG_STOP_SENDING) { + ngtcp2_conn_extend_max_offset(conn, datalen); + } + } + + strm->rx.last_offset = fr->offset; + + return 0; +} + +/* + * conn_recv_data_blocked processes the incoming DATA_BLOCKED frame + * |fr|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_FLOW_CONTROL + * It violates connection-level flow control limit. + */ +static int conn_recv_data_blocked(ngtcp2_conn *conn, ngtcp2_data_blocked *fr) { + if (conn->rx.max_offset < fr->offset) { + return NGTCP2_ERR_FLOW_CONTROL; + } + + return 0; +} + +/* + * conn_select_preferred_addr asks a client application to select a + * server address from preferred addresses received from server. If a + * client chooses the address, path validation will start. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + */ +static int conn_select_preferred_addr(ngtcp2_conn *conn) { + ngtcp2_path_storage ps; + int rv; + ngtcp2_pv *pv; + ngtcp2_dcid *dcid; + + if (ngtcp2_ringbuf_len(&conn->dcid.unused.rb) == 0) { + return 0; + } + + ngtcp2_path_storage_zero(&ps); + ngtcp2_addr_copy(&ps.path.local, &conn->dcid.current.ps.path.local); + + rv = conn_call_select_preferred_addr(conn, &ps.path); + if (rv != 0) { + return rv; + } + + if (ps.path.remote.addrlen == 0 || + ngtcp2_addr_eq(&conn->dcid.current.ps.path.remote, &ps.path.remote)) { + return 0; + } + + assert(conn->pv == NULL); + + dcid = ngtcp2_ringbuf_get(&conn->dcid.unused.rb, 0); + ngtcp2_dcid_set_path(dcid, &ps.path); + + rv = ngtcp2_pv_new(&pv, dcid, conn_compute_pv_timeout(conn), + NGTCP2_PV_FLAG_PREFERRED_ADDR, &conn->log, conn->mem); + if (rv != 0) { + /* TODO Call ngtcp2_dcid_free here if it is introduced */ + return rv; + } + + ngtcp2_ringbuf_pop_front(&conn->dcid.unused.rb); + conn->pv = pv; + + return conn_call_activate_dcid(conn, &pv->dcid); +} + +/* + * conn_recv_handshake_done processes the incoming HANDSHAKE_DONE + * frame |fr|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_PROTO + * Server received HANDSHAKE_DONE frame. + * NGTCP2_ERR_NOMEM + * Out of memory. + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + */ +static int conn_recv_handshake_done(ngtcp2_conn *conn, ngtcp2_tstamp ts) { + int rv; + + if (conn->server) { + return NGTCP2_ERR_PROTO; + } + + if (conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED) { + return 0; + } + + conn->flags |= NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED | + NGTCP2_CONN_FLAG_SERVER_ADDR_VERIFIED; + + conn->pktns.rtb.persistent_congestion_start_ts = ts; + + conn_discard_handshake_state(conn, ts); + + assert(conn->remote.transport_params); + + if (conn->remote.transport_params->preferred_addr_present) { + rv = conn_select_preferred_addr(conn); + if (rv != 0) { + return rv; + } + } + + rv = conn_call_handshake_confirmed(conn); + if (rv != 0) { + return rv; + } + + /* Re-arm loss detection timer after handshake has been + confirmed. */ + ngtcp2_conn_set_loss_detection_timer(conn, ts); + + return 0; +} + +/* + * conn_recv_datagram processes the incoming DATAGRAM frame |fr|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + */ +static int conn_recv_datagram(ngtcp2_conn *conn, ngtcp2_datagram *fr) { + assert(conn->local.transport_params.max_datagram_frame_size); + + return conn_call_recv_datagram(conn, fr); +} + +/* + * conn_key_phase_changed returns nonzero if |hd| indicates that the + * key phase has unexpected value. + */ +static int conn_key_phase_changed(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd) { + ngtcp2_pktns *pktns = &conn->pktns; + + return !(pktns->crypto.rx.ckm->flags & NGTCP2_CRYPTO_KM_FLAG_KEY_PHASE_ONE) ^ + !(hd->flags & NGTCP2_PKT_FLAG_KEY_PHASE); +} + +static int conn_initiate_key_update(ngtcp2_conn *conn, ngtcp2_tstamp ts); + +/* + * conn_prepare_key_update installs new updated keys. + */ +static int conn_prepare_key_update(ngtcp2_conn *conn, ngtcp2_tstamp ts) { + int rv; + ngtcp2_tstamp confirmed_ts = conn->crypto.key_update.confirmed_ts; + ngtcp2_duration pto = conn_compute_pto(conn, &conn->pktns); + ngtcp2_pktns *pktns = &conn->pktns; + ngtcp2_crypto_km *rx_ckm = pktns->crypto.rx.ckm; + ngtcp2_crypto_km *tx_ckm = pktns->crypto.tx.ckm; + ngtcp2_crypto_km *new_rx_ckm, *new_tx_ckm; + ngtcp2_crypto_aead_ctx rx_aead_ctx = {0}, tx_aead_ctx = {0}; + size_t secretlen, ivlen; + + if ((conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED) && + tx_ckm->use_count >= pktns->crypto.ctx.max_encryption && + conn_initiate_key_update(conn, ts) != 0) { + return NGTCP2_ERR_AEAD_LIMIT_REACHED; + } + + if ((conn->flags & NGTCP2_CONN_FLAG_KEY_UPDATE_NOT_CONFIRMED) || + ngtcp2_tstamp_not_elapsed(confirmed_ts, pto, ts)) { + return 0; + } + + if (conn->crypto.key_update.new_rx_ckm || + conn->crypto.key_update.new_tx_ckm) { + assert(conn->crypto.key_update.new_rx_ckm); + assert(conn->crypto.key_update.new_tx_ckm); + return 0; + } + + secretlen = rx_ckm->secret.len; + ivlen = rx_ckm->iv.len; + + rv = ngtcp2_crypto_km_nocopy_new(&conn->crypto.key_update.new_rx_ckm, + secretlen, ivlen, conn->mem); + if (rv != 0) { + return rv; + } + + rv = ngtcp2_crypto_km_nocopy_new(&conn->crypto.key_update.new_tx_ckm, + secretlen, ivlen, conn->mem); + if (rv != 0) { + return rv; + } + + new_rx_ckm = conn->crypto.key_update.new_rx_ckm; + new_tx_ckm = conn->crypto.key_update.new_tx_ckm; + + rv = conn_call_update_key( + conn, new_rx_ckm->secret.base, new_tx_ckm->secret.base, &rx_aead_ctx, + new_rx_ckm->iv.base, &tx_aead_ctx, new_tx_ckm->iv.base, + rx_ckm->secret.base, tx_ckm->secret.base, secretlen); + if (rv != 0) { + return rv; + } + + new_rx_ckm->aead_ctx = rx_aead_ctx; + new_tx_ckm->aead_ctx = tx_aead_ctx; + + if (!(rx_ckm->flags & NGTCP2_CRYPTO_KM_FLAG_KEY_PHASE_ONE)) { + new_rx_ckm->flags |= NGTCP2_CRYPTO_KM_FLAG_KEY_PHASE_ONE; + new_tx_ckm->flags |= NGTCP2_CRYPTO_KM_FLAG_KEY_PHASE_ONE; + } + + if (conn->crypto.key_update.old_rx_ckm) { + conn_call_delete_crypto_aead_ctx( + conn, &conn->crypto.key_update.old_rx_ckm->aead_ctx); + ngtcp2_crypto_km_del(conn->crypto.key_update.old_rx_ckm, conn->mem); + conn->crypto.key_update.old_rx_ckm = NULL; + } + + return 0; +} + +/* + * conn_rotate_keys rotates keys. The current key moves to old key, + * and new key moves to the current key. If the local endpoint + * initiated this key update, pass nonzero as |initiator|. + */ +static void conn_rotate_keys(ngtcp2_conn *conn, int64_t pkt_num, + int initiator) { + ngtcp2_pktns *pktns = &conn->pktns; + + assert(conn->crypto.key_update.new_rx_ckm); + assert(conn->crypto.key_update.new_tx_ckm); + assert(!conn->crypto.key_update.old_rx_ckm); + assert(!(conn->flags & NGTCP2_CONN_FLAG_PPE_PENDING)); + + conn->crypto.key_update.old_rx_ckm = pktns->crypto.rx.ckm; + + pktns->crypto.rx.ckm = conn->crypto.key_update.new_rx_ckm; + conn->crypto.key_update.new_rx_ckm = NULL; + pktns->crypto.rx.ckm->pkt_num = pkt_num; + + assert(pktns->crypto.tx.ckm); + + conn_call_delete_crypto_aead_ctx(conn, &pktns->crypto.tx.ckm->aead_ctx); + ngtcp2_crypto_km_del(pktns->crypto.tx.ckm, conn->mem); + + pktns->crypto.tx.ckm = conn->crypto.key_update.new_tx_ckm; + conn->crypto.key_update.new_tx_ckm = NULL; + pktns->crypto.tx.ckm->pkt_num = pktns->tx.last_pkt_num + 1; + + conn->flags |= NGTCP2_CONN_FLAG_KEY_UPDATE_NOT_CONFIRMED; + if (initiator) { + conn->flags |= NGTCP2_CONN_FLAG_KEY_UPDATE_INITIATOR; + } +} + +/* + * conn_path_validation_in_progress returns nonzero if path validation + * against |path| is underway. + */ +static int conn_path_validation_in_progress(ngtcp2_conn *conn, + const ngtcp2_path *path) { + ngtcp2_pv *pv = conn->pv; + + return pv && ngtcp2_path_eq(&pv->dcid.ps.path, path); +} + +/* + * conn_recv_non_probing_pkt_on_new_path is called when non-probing + * packet is received via new path. It starts path validation against + * the new path. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_CONN_ID_BLOCKED + * No DCID is available + * NGTCP2_ERR_NOMEM + * Out of memory + */ +static int conn_recv_non_probing_pkt_on_new_path(ngtcp2_conn *conn, + const ngtcp2_path *path, + size_t dgramlen, + int new_cid_used, + ngtcp2_tstamp ts) { + + ngtcp2_dcid dcid, *bound_dcid, *last; + ngtcp2_pv *pv; + int rv; + ngtcp2_duration pto; + int require_new_cid; + int local_addr_eq; + uint32_t remote_addr_cmp; + size_t len, i; + + assert(conn->server); + + if (conn->pv && (conn->pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) && + ngtcp2_path_eq(&conn->pv->fallback_dcid.ps.path, path)) { + /* If new path equals fallback path, that means connection + migrated back to the original path. Fallback path is + considered to be validated. */ + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PTV, + "path is migrated back to the original path"); + ngtcp2_dcid_copy(&conn->dcid.current, &conn->pv->fallback_dcid); + conn_reset_congestion_state(conn, ts); + conn->dcid.current.bytes_recv += dgramlen; + conn_reset_ecn_validation_state(conn); + + rv = conn_abort_pv(conn, ts); + if (rv != 0) { + return rv; + } + + /* Run PMTUD just in case if it is prematurely aborted */ + assert(!conn->pmtud); + + return conn_start_pmtud(conn); + } + + remote_addr_cmp = + ngtcp2_addr_compare(&conn->dcid.current.ps.path.remote, &path->remote); + local_addr_eq = + ngtcp2_addr_eq(&conn->dcid.current.ps.path.local, &path->local); + + /* + * When to change DCID? RFC 9002 section 9.5 says: + * + * An endpoint MUST NOT reuse a connection ID when sending from more + * than one local address -- for example, when initiating connection + * migration as described in Section 9.2 or when probing a new + * network path as described in Section 9.1. + * + * Similarly, an endpoint MUST NOT reuse a connection ID when + * sending to more than one destination address. Due to network + * changes outside the control of its peer, an endpoint might + * receive packets from a new source address with the same + * Destination Connection ID field value, in which case it MAY + * 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"); + + len = ngtcp2_ringbuf_len(&conn->dcid.bound.rb); + + for (i = 0; i < len; ++i) { + bound_dcid = ngtcp2_ringbuf_get(&conn->dcid.bound.rb, i); + if (ngtcp2_path_eq(&bound_dcid->ps.path, path)) { + ngtcp2_log_info( + &conn->log, NGTCP2_LOG_EVENT_CON, + "Found DCID which has already been bound to the new path"); + + ngtcp2_dcid_copy(&dcid, bound_dcid); + if (i == 0) { + ngtcp2_ringbuf_pop_front(&conn->dcid.bound.rb); + } else if (i == ngtcp2_ringbuf_len(&conn->dcid.bound.rb) - 1) { + ngtcp2_ringbuf_pop_back(&conn->dcid.bound.rb); + } else { + last = ngtcp2_ringbuf_get(&conn->dcid.bound.rb, len - 1); + ngtcp2_dcid_copy(bound_dcid, last); + ngtcp2_ringbuf_pop_back(&conn->dcid.bound.rb); + } + require_new_cid = 0; + + if (dcid.cid.datalen) { + rv = conn_call_activate_dcid(conn, &dcid); + if (rv != 0) { + return rv; + } + } + break; + } + } + + if (i == len) { + if (require_new_cid) { + if (ngtcp2_ringbuf_len(&conn->dcid.unused.rb) == 0) { + return NGTCP2_ERR_CONN_ID_BLOCKED; + } + ngtcp2_dcid_copy(&dcid, ngtcp2_ringbuf_get(&conn->dcid.unused.rb, 0)); + ngtcp2_ringbuf_pop_front(&conn->dcid.unused.rb); + + rv = conn_call_activate_dcid(conn, &dcid); + if (rv != 0) { + return rv; + } + } else { + /* Use the current DCID if a remote endpoint does not change + DCID. */ + ngtcp2_dcid_copy(&dcid, &conn->dcid.current); + dcid.bytes_sent = 0; + dcid.bytes_recv = 0; + dcid.flags &= (uint8_t)~NGTCP2_DCID_FLAG_PATH_VALIDATED; + } + + ngtcp2_dcid_set_path(&dcid, path); + } + + dcid.bytes_recv += dgramlen; + + pto = conn_compute_pto(conn, &conn->pktns); + + rv = ngtcp2_pv_new(&pv, &dcid, conn_compute_pv_timeout_pto(conn, pto), + NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE, &conn->log, conn->mem); + if (rv != 0) { + return rv; + } + + if (conn->pv && (conn->pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE)) { + ngtcp2_dcid_copy(&pv->fallback_dcid, &conn->pv->fallback_dcid); + pv->fallback_pto = conn->pv->fallback_pto; + /* Unset the flag bit so that conn_stop_pv does not retire + DCID. */ + conn->pv->flags &= (uint8_t)~NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE; + } else { + ngtcp2_dcid_copy(&pv->fallback_dcid, &conn->dcid.current); + pv->fallback_pto = pto; + } + + if (!local_addr_eq || (remote_addr_cmp & (NGTCP2_ADDR_COMPARE_FLAG_ADDR | + NGTCP2_ADDR_COMPARE_FLAG_FAMILY))) { + conn_reset_congestion_state(conn, ts); + } + + ngtcp2_dcid_copy(&conn->dcid.current, &dcid); + + conn_reset_ecn_validation_state(conn); + + ngtcp2_conn_stop_pmtud(conn); + + if (conn->pv) { + ngtcp2_log_info( + &conn->log, NGTCP2_LOG_EVENT_PTV, + "path migration is aborted because new migration has started"); + rv = conn_abort_pv(conn, ts); + if (rv != 0) { + return rv; + } + } + + conn->pv = pv; + + return 0; +} + +/* + * conn_recv_pkt_from_new_path is called when a 1RTT packet is + * received from new path (not current path). This packet would be a + * packet which only contains probing frame, or reordered packet, or a + * path is being validated. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_CONN_ID_BLOCKED + * No unused DCID is available + * NGTCP2_ERR_NOMEM + * Out of memory + */ +static int conn_recv_pkt_from_new_path(ngtcp2_conn *conn, + const ngtcp2_path *path, size_t dgramlen, + int path_challenge_recved, + ngtcp2_tstamp ts) { + ngtcp2_pv *pv = conn->pv; + ngtcp2_dcid *bound_dcid; + int rv; + + if (pv) { + if (ngtcp2_path_eq(&pv->dcid.ps.path, path)) { + pv->dcid.bytes_recv += dgramlen; + return 0; + } + + if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) && + ngtcp2_path_eq(&pv->fallback_dcid.ps.path, path)) { + pv->fallback_dcid.bytes_recv += dgramlen; + return 0; + } + } + + if (!path_challenge_recved) { + return 0; + } + + rv = conn_bind_dcid(conn, &bound_dcid, path, ts); + if (rv != 0) { + return rv; + } + + ngtcp2_dcid_set_path(bound_dcid, path); + bound_dcid->bytes_recv += dgramlen; + + return 0; +} + +/* + * conn_recv_delayed_handshake_pkt processes the received Handshake + * packet which is received after handshake completed. This function + * does the minimal job, and its purpose is send acknowledgement of + * this packet to the peer. We assume that hd->type == + * NGTCP2_PKT_HANDSHAKE. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_FRAME_ENCODING + * Frame is badly formatted; or frame type is unknown. + * NGTCP2_ERR_NOMEM + * Out of memory + * NGTCP2_ERR_DISCARD_PKT + * Packet was discarded. + * NGTCP2_ERR_ACK_FRAME + * ACK frame is malformed. + * NGTCP2_ERR_PROTO + * Frame that is not allowed in Handshake packet is received. + */ +static int +conn_recv_delayed_handshake_pkt(ngtcp2_conn *conn, const ngtcp2_pkt_info *pi, + const ngtcp2_pkt_hd *hd, size_t pktlen, + 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; + int rv; + int require_ack = 0; + ngtcp2_pktns *pktns; + + assert(hd->type == NGTCP2_PKT_HANDSHAKE); + + pktns = conn->hs_pktns; + + if (payloadlen == 0) { + /* QUIC packet must contain at least one frame */ + return NGTCP2_ERR_PROTO; + } + + ngtcp2_qlog_pkt_received_start(&conn->qlog); + + for (; payloadlen;) { + nread = ngtcp2_pkt_decode_frame(fr, payload, payloadlen); + if (nread < 0) { + return (int)nread; + } + + payload += nread; + payloadlen -= (size_t)nread; + + switch (fr->type) { + case NGTCP2_FRAME_ACK: + case NGTCP2_FRAME_ACK_ECN: + fr->ack.ack_delay = 0; + fr->ack.ack_delay_unscaled = 0; + break; + } + + ngtcp2_log_rx_fr(&conn->log, hd, fr); + + switch (fr->type) { + case NGTCP2_FRAME_ACK: + case NGTCP2_FRAME_ACK_ECN: + if (!conn->server) { + conn->flags |= NGTCP2_CONN_FLAG_SERVER_ADDR_VERIFIED; + } + rv = conn_recv_ack(conn, pktns, &fr->ack, pkt_ts, ts); + if (rv != 0) { + return rv; + } + break; + case NGTCP2_FRAME_PADDING: + break; + case NGTCP2_FRAME_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: + require_ack = 1; + break; + default: + return NGTCP2_ERR_PROTO; + } + + ngtcp2_qlog_write_frame(&conn->qlog, fr); + } + + ngtcp2_qlog_pkt_received_end(&conn->qlog, hd, pktlen); + + rv = pktns_commit_recv_pkt_num(pktns, hd->pkt_num, require_ack, pkt_ts); + if (rv != 0) { + return rv; + } + + pktns_increase_ecn_counts(pktns, pi); + + /* Initial and Handshake are always acknowledged without delay. No + need to call ngtcp2_acktr_immediate_ack(). */ + rv = ngtcp2_conn_sched_ack(conn, &pktns->acktr, hd->pkt_num, require_ack, + pkt_ts); + if (rv != 0) { + return rv; + } + + conn_restart_timer_on_read(conn, ts); + + ngtcp2_qlog_metrics_updated(&conn->qlog, &conn->cstat); + + return 0; +} + +/* + * conn_allow_path_change_under_disable_active_migration returns + * nonzero if a packet from |path| is acceptable under + * disable_active_migration is on. + */ +static int +conn_allow_path_change_under_disable_active_migration(ngtcp2_conn *conn, + const ngtcp2_path *path) { + uint32_t remote_addr_cmp; + const ngtcp2_preferred_addr *paddr; + ngtcp2_addr addr; + + assert(conn->server); + assert(conn->local.transport_params.disable_active_migration); + + /* If local address does not change, it must be passive migration + (NAT rebinding). */ + if (ngtcp2_addr_eq(&conn->dcid.current.ps.path.local, &path->local)) { + remote_addr_cmp = + ngtcp2_addr_compare(&conn->dcid.current.ps.path.remote, &path->remote); + + return (remote_addr_cmp | NGTCP2_ADDR_COMPARE_FLAG_PORT) == + NGTCP2_ADDR_COMPARE_FLAG_PORT; + } + + /* If local address changes, it must be one of the preferred + addresses. */ + + if (!conn->local.transport_params.preferred_addr_present) { + return 0; + } + + paddr = &conn->local.transport_params.preferred_addr; + + if (paddr->ipv4_present) { + ngtcp2_addr_init(&addr, (const ngtcp2_sockaddr *)&paddr->ipv4, + sizeof(paddr->ipv4)); + + if (ngtcp2_addr_eq(&addr, &path->local)) { + return 1; + } + } + + if (paddr->ipv6_present) { + ngtcp2_addr_init(&addr, (const ngtcp2_sockaddr *)&paddr->ipv6, + sizeof(paddr->ipv6)); + + if (ngtcp2_addr_eq(&addr, &path->local)) { + return 1; + } + } + + return 0; +} + +/* + * conn_recv_pkt processes a packet contained in the buffer pointed by + * |pkt| of length |pktlen|. |pkt| may contain multiple QUIC packets. + * This function only processes the first packet. |pkt_ts| is the + * timestamp when packet is received. |ts| should be the current + * time. Usually they are the same, but for buffered packets, + * |pkt_ts| would be earlier than |ts|. + * + * This function returns the number of bytes processed if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_DISCARD_PKT + * Packet was discarded because plain text header was malformed; + * or its payload could not be decrypted. + * NGTCP2_ERR_PROTO + * Packet is badly formatted; or 0RTT packet contains other than + * PADDING or STREAM frames; or other QUIC protocol violation is + * found. + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + * NGTCP2_ERR_NOMEM + * Out of memory. + * NGTCP2_ERR_FRAME_ENCODING + * Frame is badly formatted; or frame type is unknown. + * NGTCP2_ERR_ACK_FRAME + * ACK frame is malformed. + * NGTCP2_ERR_STREAM_STATE + * Frame is received to the local stream which is not initiated. + * NGTCP2_ERR_STREAM_LIMIT + * Frame has remote stream ID which is strictly greater than the + * allowed limit. + * NGTCP2_ERR_FLOW_CONTROL + * Flow control limit is violated. + * NGTCP2_ERR_FINAL_SIZE + * Frame has strictly larger end offset than it is permitted. + */ +static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, + const ngtcp2_pkt_info *pi, const uint8_t *pkt, + size_t pktlen, size_t dgramlen, + ngtcp2_tstamp pkt_ts, ngtcp2_tstamp ts) { + ngtcp2_pkt_hd hd; + int rv = 0; + size_t hdpktlen; + const uint8_t *payload; + size_t payloadlen; + ngtcp2_ssize nread, nwrite; + ngtcp2_max_frame mfr; + ngtcp2_frame *fr = &mfr.fr; + int require_ack = 0; + ngtcp2_crypto_aead *aead; + ngtcp2_crypto_cipher *hp; + ngtcp2_crypto_km *ckm; + ngtcp2_crypto_cipher_ctx *hp_ctx; + ngtcp2_hp_mask hp_mask; + ngtcp2_decrypt decrypt; + ngtcp2_pktns *pktns; + int non_probing_pkt = 0; + int key_phase_bit_changed = 0; + int force_decrypt_failure = 0; + int recv_ncid = 0; + int new_cid_used = 0; + int path_challenge_recved = 0; + + if (conn->server && conn->local.transport_params.disable_active_migration && + !ngtcp2_path_eq(&conn->dcid.current.ps.path, path) && + !conn_allow_path_change_under_disable_active_migration(conn, path)) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "packet is discarded because active migration is disabled"); + + return NGTCP2_ERR_DISCARD_PKT; + } + + if (pkt[0] & NGTCP2_HEADER_FORM_BIT) { + nread = ngtcp2_pkt_decode_hd_long(&hd, pkt, pktlen); + if (nread < 0) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "could not decode long header"); + return NGTCP2_ERR_DISCARD_PKT; + } + + if (pktlen < (size_t)nread + hd.len) { + return NGTCP2_ERR_DISCARD_PKT; + } + + assert(conn->negotiated_version); + + if (hd.version != conn->client_chosen_version && + hd.version != conn->negotiated_version) { + return NGTCP2_ERR_DISCARD_PKT; + } + + if (conn_verify_fixed_bit(conn, &hd) != 0) { + return NGTCP2_ERR_DISCARD_PKT; + } + + pktlen = (size_t)nread + hd.len; + + /* Quoted from spec: if subsequent packets of those types include + a different Source Connection ID, they MUST be discarded. */ + if (!ngtcp2_cid_eq(&conn->dcid.current.cid, &hd.scid)) { + ngtcp2_log_rx_pkt_hd(&conn->log, &hd); + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "packet was ignored because of mismatched SCID"); + return NGTCP2_ERR_DISCARD_PKT; + } + + switch (hd.type) { + case NGTCP2_PKT_INITIAL: + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "delayed Initial packet was discarded"); + return (ngtcp2_ssize)pktlen; + case NGTCP2_PKT_HANDSHAKE: + if (hd.version != conn->negotiated_version) { + return NGTCP2_ERR_DISCARD_PKT; + } + + if (!conn->hs_pktns) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "delayed Handshake packet was discarded"); + return (ngtcp2_ssize)pktlen; + } + + pktns = conn->hs_pktns; + aead = &pktns->crypto.ctx.aead; + hp = &pktns->crypto.ctx.hp; + ckm = pktns->crypto.rx.ckm; + hp_ctx = &pktns->crypto.rx.hp_ctx; + hp_mask = conn->callbacks.hp_mask; + decrypt = conn->callbacks.decrypt; + break; + case NGTCP2_PKT_0RTT: + if (!conn->server || hd.version != conn->client_chosen_version) { + return NGTCP2_ERR_DISCARD_PKT; + } + + if (!conn->early.ckm) { + return (ngtcp2_ssize)pktlen; + } + + pktns = &conn->pktns; + aead = &conn->early.ctx.aead; + hp = &conn->early.ctx.hp; + ckm = conn->early.ckm; + hp_ctx = &conn->early.hp_ctx; + hp_mask = conn->callbacks.hp_mask; + decrypt = conn->callbacks.decrypt; + 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); + return (ngtcp2_ssize)pktlen; + } + } else { + nread = ngtcp2_pkt_decode_hd_short(&hd, pkt, pktlen, conn->oscid.datalen); + if (nread < 0) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "could not decode short header"); + return NGTCP2_ERR_DISCARD_PKT; + } + + if (conn_verify_fixed_bit(conn, &hd) != 0) { + return NGTCP2_ERR_DISCARD_PKT; + } + + pktns = &conn->pktns; + aead = &pktns->crypto.ctx.aead; + hp = &pktns->crypto.ctx.hp; + ckm = pktns->crypto.rx.ckm; + hp_ctx = &pktns->crypto.rx.hp_ctx; + hp_mask = conn->callbacks.hp_mask; + decrypt = conn->callbacks.decrypt; + } + + rv = conn_ensure_decrypt_hp_buffer(conn, (size_t)nread + 4); + if (rv != 0) { + return rv; + } + + nwrite = decrypt_hp(&hd, conn->crypto.decrypt_hp_buf.base, hp, pkt, pktlen, + (size_t)nread, hp_ctx, hp_mask); + if (nwrite < 0) { + if (ngtcp2_err_is_fatal((int)nwrite)) { + return nwrite; + } + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "could not decrypt packet number"); + return NGTCP2_ERR_DISCARD_PKT; + } + + hdpktlen = (size_t)nwrite; + payload = pkt + hdpktlen; + payloadlen = pktlen - hdpktlen; + + hd.pkt_num = ngtcp2_pkt_adjust_pkt_num(pktns->rx.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); + return NGTCP2_ERR_DISCARD_PKT; + } + + ngtcp2_log_rx_pkt_hd(&conn->log, &hd); + + if (hd.type == NGTCP2_PKT_1RTT) { + key_phase_bit_changed = conn_key_phase_changed(conn, &hd); + } + + rv = conn_ensure_decrypt_buffer(conn, payloadlen); + if (rv != 0) { + return rv; + } + + if (key_phase_bit_changed) { + assert(hd.type == NGTCP2_PKT_1RTT); + + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, "unexpected KEY_PHASE"); + + if (ckm->pkt_num > hd.pkt_num) { + if (conn->crypto.key_update.old_rx_ckm) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "decrypting with old key"); + ckm = conn->crypto.key_update.old_rx_ckm; + } else { + force_decrypt_failure = 1; + } + } else if (pktns->rx.max_pkt_num < hd.pkt_num) { + assert(ckm->pkt_num < hd.pkt_num); + if (!conn->crypto.key_update.new_rx_ckm) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "new key is not available"); + force_decrypt_failure = 1; + } else { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "decrypting with new key"); + ckm = conn->crypto.key_update.new_rx_ckm; + } + } else { + force_decrypt_failure = 1; + } + } + + nwrite = decrypt_pkt(conn->crypto.decrypt_buf.base, aead, payload, payloadlen, + conn->crypto.decrypt_hp_buf.base, hdpktlen, hd.pkt_num, + ckm, decrypt); + + if (force_decrypt_failure) { + nwrite = NGTCP2_ERR_DECRYPT; + } + + if (nwrite < 0) { + if (ngtcp2_err_is_fatal((int)nwrite)) { + return nwrite; + } + + assert(NGTCP2_ERR_DECRYPT == nwrite); + + if (hd.type == NGTCP2_PKT_1RTT && + ++conn->crypto.decryption_failure_count >= + pktns->crypto.ctx.max_decryption_failure) { + return NGTCP2_ERR_AEAD_LIMIT_REACHED; + } + + if (hd.flags & NGTCP2_PKT_FLAG_LONG_FORM) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "could not decrypt packet payload"); + return NGTCP2_ERR_DISCARD_PKT; + } + + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "could not decrypt packet payload"); + return NGTCP2_ERR_DISCARD_PKT; + } + + rv = ngtcp2_pkt_verify_reserved_bits(conn->crypto.decrypt_hp_buf.base[0]); + if (rv != 0) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "packet has incorrect reserved bits"); + + return NGTCP2_ERR_PROTO; + } + + if (pktns_pkt_num_is_duplicate(pktns, hd.pkt_num)) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "packet was discarded because of duplicated packet number"); + return NGTCP2_ERR_DISCARD_PKT; + } + + payload = conn->crypto.decrypt_buf.base; + payloadlen = (size_t)nwrite; + + if (payloadlen == 0) { + /* QUIC packet must contain at least one frame */ + return NGTCP2_ERR_PROTO; + } + + if (hd.flags & NGTCP2_PKT_FLAG_LONG_FORM) { + switch (hd.type) { + case NGTCP2_PKT_HANDSHAKE: + rv = conn_verify_dcid(conn, NULL, &hd); + if (rv != 0) { + if (ngtcp2_err_is_fatal(rv)) { + return rv; + } + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "packet was ignored because of mismatched DCID"); + return NGTCP2_ERR_DISCARD_PKT; + } + + rv = conn_recv_delayed_handshake_pkt(conn, pi, &hd, pktlen, payload, + payloadlen, pkt_ts, ts); + if (rv < 0) { + return (ngtcp2_ssize)rv; + } + + return (ngtcp2_ssize)pktlen; + case NGTCP2_PKT_0RTT: + if (!ngtcp2_cid_eq(&conn->rcid, &hd.dcid)) { + rv = conn_verify_dcid(conn, NULL, &hd); + if (rv != 0) { + if (ngtcp2_err_is_fatal(rv)) { + return rv; + } + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "packet was ignored because of mismatched DCID"); + return NGTCP2_ERR_DISCARD_PKT; + } + } + break; + default: + /* Unreachable */ + ngtcp2_unreachable(); + } + } else { + rv = conn_verify_dcid(conn, &new_cid_used, &hd); + if (rv != 0) { + if (ngtcp2_err_is_fatal(rv)) { + return rv; + } + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "packet was ignored because of mismatched DCID"); + return NGTCP2_ERR_DISCARD_PKT; + } + } + + ngtcp2_qlog_pkt_received_start(&conn->qlog); + + for (; payloadlen;) { + nread = ngtcp2_pkt_decode_frame(fr, payload, payloadlen); + if (nread < 0) { + return nread; + } + + payload += nread; + payloadlen -= (size_t)nread; + + switch (fr->type) { + case NGTCP2_FRAME_ACK: + case NGTCP2_FRAME_ACK_ECN: + if ((hd.flags & NGTCP2_PKT_FLAG_LONG_FORM) && + hd.type == NGTCP2_PKT_0RTT) { + return NGTCP2_ERR_PROTO; + } + assert(conn->remote.transport_params); + assign_recved_ack_delay_unscaled( + &fr->ack, conn->remote.transport_params->ack_delay_exponent); + break; + } + + ngtcp2_log_rx_fr(&conn->log, &hd, fr); + + if (hd.type == NGTCP2_PKT_0RTT) { + switch (fr->type) { + case NGTCP2_FRAME_PADDING: + case NGTCP2_FRAME_PING: + case NGTCP2_FRAME_RESET_STREAM: + case NGTCP2_FRAME_STOP_SENDING: + case NGTCP2_FRAME_STREAM: + case NGTCP2_FRAME_MAX_DATA: + case NGTCP2_FRAME_MAX_STREAM_DATA: + case NGTCP2_FRAME_MAX_STREAMS_BIDI: + case NGTCP2_FRAME_MAX_STREAMS_UNI: + case NGTCP2_FRAME_DATA_BLOCKED: + case NGTCP2_FRAME_STREAM_DATA_BLOCKED: + case NGTCP2_FRAME_STREAMS_BLOCKED_BIDI: + case NGTCP2_FRAME_STREAMS_BLOCKED_UNI: + case NGTCP2_FRAME_NEW_CONNECTION_ID: + case NGTCP2_FRAME_PATH_CHALLENGE: + case NGTCP2_FRAME_CONNECTION_CLOSE: + case NGTCP2_FRAME_CONNECTION_CLOSE_APP: + case NGTCP2_FRAME_DATAGRAM: + case NGTCP2_FRAME_DATAGRAM_LEN: + break; + default: + return NGTCP2_ERR_PROTO; + } + } + + switch (fr->type) { + case NGTCP2_FRAME_ACK: + case NGTCP2_FRAME_ACK_ECN: + case NGTCP2_FRAME_PADDING: + case NGTCP2_FRAME_CONNECTION_CLOSE: + case NGTCP2_FRAME_CONNECTION_CLOSE_APP: + break; + default: + require_ack = 1; + } + + switch (fr->type) { + case NGTCP2_FRAME_ACK: + case NGTCP2_FRAME_ACK_ECN: + if (!conn->server) { + conn->flags |= NGTCP2_CONN_FLAG_SERVER_ADDR_VERIFIED; + } + rv = conn_recv_ack(conn, pktns, &fr->ack, pkt_ts, ts); + if (rv != 0) { + return rv; + } + non_probing_pkt = 1; + break; + case NGTCP2_FRAME_STREAM: + rv = conn_recv_stream(conn, &fr->stream); + if (rv != 0) { + return rv; + } + non_probing_pkt = 1; + break; + case NGTCP2_FRAME_CRYPTO: + rv = conn_recv_crypto(conn, NGTCP2_ENCRYPTION_LEVEL_1RTT, + &pktns->crypto.strm, &fr->stream); + if (rv != 0) { + return rv; + } + non_probing_pkt = 1; + break; + case NGTCP2_FRAME_RESET_STREAM: + rv = conn_recv_reset_stream(conn, &fr->reset_stream); + if (rv != 0) { + return rv; + } + non_probing_pkt = 1; + break; + case NGTCP2_FRAME_STOP_SENDING: + rv = conn_recv_stop_sending(conn, &fr->stop_sending); + 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); + if (rv != 0) { + return rv; + } + non_probing_pkt = 1; + break; + case NGTCP2_FRAME_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); + if (rv != 0) { + return rv; + } + non_probing_pkt = 1; + break; + case NGTCP2_FRAME_CONNECTION_CLOSE: + case NGTCP2_FRAME_CONNECTION_CLOSE_APP: + rv = conn_recv_connection_close(conn, &fr->connection_close); + if (rv != 0) { + return rv; + } + break; + case NGTCP2_FRAME_PING: + non_probing_pkt = 1; + break; + case NGTCP2_FRAME_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, &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); + 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); + if (rv != 0) { + return rv; + } + non_probing_pkt = 1; + break; + case NGTCP2_FRAME_NEW_TOKEN: + rv = conn_recv_new_token(conn, &fr->new_token); + if (rv != 0) { + return rv; + } + non_probing_pkt = 1; + break; + case NGTCP2_FRAME_HANDSHAKE_DONE: + rv = conn_recv_handshake_done(conn, ts); + if (rv != 0) { + return rv; + } + non_probing_pkt = 1; + break; + case NGTCP2_FRAME_STREAMS_BLOCKED_BIDI: + 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); + 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); + if (rv != 0) { + return rv; + } + non_probing_pkt = 1; + break; + case NGTCP2_FRAME_DATA_BLOCKED: + rv = conn_recv_data_blocked(conn, &fr->data_blocked); + if (rv != 0) { + return rv; + } + non_probing_pkt = 1; + break; + case NGTCP2_FRAME_DATAGRAM: + case NGTCP2_FRAME_DATAGRAM_LEN: + if ((uint64_t)nread > + conn->local.transport_params.max_datagram_frame_size) { + return NGTCP2_ERR_PROTO; + } + rv = conn_recv_datagram(conn, &fr->datagram); + if (rv != 0) { + return rv; + } + non_probing_pkt = 1; + break; + } + + ngtcp2_qlog_write_frame(&conn->qlog, fr); + } + + ngtcp2_qlog_pkt_received_end(&conn->qlog, &hd, pktlen); + + if (recv_ncid) { + rv = conn_post_process_recv_new_connection_id(conn, ts); + if (rv != 0) { + return rv; + } + } + + if (conn->server && hd.type == NGTCP2_PKT_1RTT && + !ngtcp2_path_eq(&conn->dcid.current.ps.path, path)) { + if (non_probing_pkt && pktns->rx.max_pkt_num < hd.pkt_num && + !conn_path_validation_in_progress(conn, path)) { + rv = conn_recv_non_probing_pkt_on_new_path(conn, path, dgramlen, + new_cid_used, ts); + if (rv != 0) { + if (ngtcp2_err_is_fatal(rv)) { + return rv; + } + + /* DCID is not available. Just continue. */ + assert(NGTCP2_ERR_CONN_ID_BLOCKED == rv); + } + } else { + rv = conn_recv_pkt_from_new_path(conn, path, dgramlen, + path_challenge_recved, ts); + if (rv != 0) { + if (ngtcp2_err_is_fatal(rv)) { + return rv; + } + + /* DCID is not available. Just continue. */ + assert(NGTCP2_ERR_CONN_ID_BLOCKED == rv); + } + } + } + + if (hd.type == NGTCP2_PKT_1RTT) { + if (ckm == conn->crypto.key_update.new_rx_ckm) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, "rotate keys"); + conn_rotate_keys(conn, hd.pkt_num, /* initiator = */ 0); + } else if (ckm->pkt_num > hd.pkt_num) { + ckm->pkt_num = hd.pkt_num; + } + + if (conn->server && conn->early.ckm && + conn->early.discard_started_ts == UINT64_MAX) { + conn->early.discard_started_ts = ts; + } + + if (ngtcp2_path_eq(&conn->dcid.current.ps.path, path)) { + conn_update_keep_alive_last_ts(conn, ts); + } + } + + rv = pktns_commit_recv_pkt_num(pktns, hd.pkt_num, require_ack, pkt_ts); + if (rv != 0) { + return rv; + } + + pktns_increase_ecn_counts(pktns, pi); + + if (require_ack && + (++pktns->acktr.rx_npkt >= conn->local.settings.ack_thresh || + (pi->ecn & NGTCP2_ECN_MASK) == NGTCP2_ECN_CE)) { + ngtcp2_acktr_immediate_ack(&pktns->acktr); + } + + rv = ngtcp2_conn_sched_ack(conn, &pktns->acktr, hd.pkt_num, require_ack, + pkt_ts); + if (rv != 0) { + return rv; + } + + conn_restart_timer_on_read(conn, ts); + + ngtcp2_qlog_metrics_updated(&conn->qlog, &conn->cstat); + + return conn->state == NGTCP2_CS_DRAINING ? NGTCP2_ERR_DRAINING + : (ngtcp2_ssize)pktlen; +} + +/* + * conn_process_buffered_protected_pkt processes buffered 0RTT or 1RTT + * packets. + * + * This function returns 0 if it succeeds, or the same negative error + * codes from conn_recv_pkt. + */ +static int conn_process_buffered_protected_pkt(ngtcp2_conn *conn, + ngtcp2_pktns *pktns, + ngtcp2_tstamp ts) { + ngtcp2_ssize nread; + ngtcp2_pkt_chain **ppc, *next; + int rv; + + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, + "processing buffered protected packet"); + + for (ppc = &pktns->rx.buffed_pkts; *ppc;) { + next = (*ppc)->next; + nread = conn_recv_pkt(conn, &(*ppc)->path.path, &(*ppc)->pi, (*ppc)->pkt, + (*ppc)->pktlen, (*ppc)->dgramlen, (*ppc)->ts, ts); + if (nread < 0 && !ngtcp2_err_is_fatal((int)nread) && + nread != NGTCP2_ERR_DRAINING) { + /* TODO We don't know this is the first QUIC packet in a + datagram. */ + rv = conn_on_stateless_reset(conn, &(*ppc)->path.path, (*ppc)->pkt, + (*ppc)->pktlen); + if (rv == 0) { + ngtcp2_pkt_chain_del(*ppc, conn->mem); + *ppc = next; + return NGTCP2_ERR_DRAINING; + } + } + + ngtcp2_pkt_chain_del(*ppc, conn->mem); + *ppc = next; + if (nread < 0) { + if (nread == NGTCP2_ERR_DISCARD_PKT) { + continue; + } + return (int)nread; + } + } + + return 0; +} + +/* + * conn_process_buffered_handshake_pkt processes buffered Handshake + * packets. + * + * This function returns 0 if it succeeds, or the same negative error + * codes from conn_recv_handshake_pkt. + */ +static int conn_process_buffered_handshake_pkt(ngtcp2_conn *conn, + ngtcp2_tstamp ts) { + ngtcp2_pktns *pktns = conn->hs_pktns; + ngtcp2_ssize nread; + ngtcp2_pkt_chain **ppc, *next; + + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, + "processing buffered handshake packet"); + + for (ppc = &pktns->rx.buffed_pkts; *ppc;) { + next = (*ppc)->next; + nread = conn_recv_handshake_pkt(conn, &(*ppc)->path.path, &(*ppc)->pi, + (*ppc)->pkt, (*ppc)->pktlen, + (*ppc)->dgramlen, (*ppc)->ts, ts); + ngtcp2_pkt_chain_del(*ppc, conn->mem); + *ppc = next; + if (nread < 0) { + if (nread == NGTCP2_ERR_DISCARD_PKT) { + continue; + } + return (int)nread; + } + } + + return 0; +} + +static void conn_sync_stream_id_limit(ngtcp2_conn *conn) { + ngtcp2_transport_params *params = conn->remote.transport_params; + + assert(params); + + conn->local.bidi.max_streams = params->initial_max_streams_bidi; + conn->local.uni.max_streams = params->initial_max_streams_uni; +} + +static int strm_set_max_offset(void *data, void *ptr) { + ngtcp2_conn *conn = ptr; + ngtcp2_transport_params *params = conn->remote.transport_params; + ngtcp2_strm *strm = data; + uint64_t max_offset; + int rv; + + assert(params); + + if (!conn_local_stream(conn, strm->stream_id)) { + return 0; + } + + if (bidi_stream(strm->stream_id)) { + max_offset = params->initial_max_stream_data_bidi_remote; + } else { + max_offset = params->initial_max_stream_data_uni; + } + + if (strm->tx.max_offset < max_offset) { + strm->tx.max_offset = max_offset; + + /* Don't call callback if stream is half-closed local */ + if (strm->flags & NGTCP2_STRM_FLAG_SHUT_WR) { + return 0; + } + + rv = conn_call_extend_max_stream_data(conn, strm, strm->stream_id, + strm->tx.max_offset); + if (rv != 0) { + return rv; + } + } + + return 0; +} + +static int conn_sync_stream_data_limit(ngtcp2_conn *conn) { + return ngtcp2_map_each(&conn->strms, strm_set_max_offset, conn); +} + +/* + * conn_handshake_completed is called once cryptographic handshake has + * completed. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_CALLBACK_FAILURE + * User callback failed. + */ +static int conn_handshake_completed(ngtcp2_conn *conn) { + int rv; + + conn->flags |= NGTCP2_CONN_FLAG_HANDSHAKE_COMPLETED; + + rv = conn_call_handshake_completed(conn); + if (rv != 0) { + return rv; + } + + if (conn->local.bidi.max_streams > 0) { + rv = conn_call_extend_max_local_streams_bidi(conn, + conn->local.bidi.max_streams); + if (rv != 0) { + return rv; + } + } + if (conn->local.uni.max_streams > 0) { + rv = conn_call_extend_max_local_streams_uni(conn, + conn->local.uni.max_streams); + if (rv != 0) { + return rv; + } + } + + return 0; +} + +/* + * conn_recv_cpkt processes compound packet after handshake. The + * buffer pointed by |pkt| might contain multiple packets. The 1RTT + * packet must be the last one because it does not have payload length + * field. + * + * This function returns 0 if it succeeds, or the same negative error + * codes from conn_recv_pkt except for NGTCP2_ERR_DISCARD_PKT. + */ +static int conn_recv_cpkt(ngtcp2_conn *conn, const ngtcp2_path *path, + const ngtcp2_pkt_info *pi, const uint8_t *pkt, + size_t pktlen, ngtcp2_tstamp ts) { + ngtcp2_ssize nread; + int rv; + const uint8_t *origpkt = pkt; + size_t dgramlen = pktlen; + + if (ngtcp2_path_eq(&conn->dcid.current.ps.path, path)) { + conn->dcid.current.bytes_recv += dgramlen; + } + + while (pktlen) { + nread = conn_recv_pkt(conn, path, pi, pkt, pktlen, dgramlen, ts, ts); + if (nread < 0) { + if (ngtcp2_err_is_fatal((int)nread)) { + return (int)nread; + } + + if (nread == NGTCP2_ERR_DRAINING) { + return NGTCP2_ERR_DRAINING; + } + + if (origpkt == pkt) { + rv = conn_on_stateless_reset(conn, path, origpkt, dgramlen); + if (rv == 0) { + return NGTCP2_ERR_DRAINING; + } + } + if (nread == NGTCP2_ERR_DISCARD_PKT) { + return 0; + } + return (int)nread; + } + + assert(pktlen >= (size_t)nread); + pkt += nread; + pktlen -= (size_t)nread; + + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "read packet %td left %zu", nread, pktlen); + } + + return 0; +} + +/* + * conn_is_retired_path returns nonzero if |path| is included in + * retired path list. + */ +static int conn_is_retired_path(ngtcp2_conn *conn, const ngtcp2_path *path) { + size_t i, len = ngtcp2_ringbuf_len(&conn->dcid.retired.rb); + ngtcp2_dcid *dcid; + + for (i = 0; i < len; ++i) { + dcid = ngtcp2_ringbuf_get(&conn->dcid.retired.rb, i); + if (ngtcp2_path_eq(&dcid->ps.path, path)) { + return 1; + } + } + + return 0; +} + +/* + * conn_enqueue_handshake_done enqueues HANDSHAKE_DONE frame for + * transmission. + */ +static int conn_enqueue_handshake_done(ngtcp2_conn *conn) { + ngtcp2_pktns *pktns = &conn->pktns; + ngtcp2_frame_chain *nfrc; + int rv; + + assert(conn->server); + + rv = ngtcp2_frame_chain_objalloc_new(&nfrc, &conn->frc_objalloc); + if (rv != 0) { + return rv; + } + + nfrc->fr.type = NGTCP2_FRAME_HANDSHAKE_DONE; + nfrc->next = pktns->tx.frq; + pktns->tx.frq = nfrc; + + return 0; +} + +/** + * @function + * + * `conn_read_handshake` performs QUIC cryptographic handshake by + * reading given data. |pkt| points to the buffer to read and + * |pktlen| is the length of the buffer. |path| is the network path. + * + * This function returns the number of bytes processed. Unless the + * last packet is 1RTT packet and an application decryption key has + * been installed, it returns |pktlen| if it succeeds. If it finds + * 1RTT packet and an application decryption key has been installed, + * it returns the number of bytes just before 1RTT packet begins. + * + * This function returns the number of bytes processed if it succeeds, + * or one of the following negative error codes: (TBD). + */ +static ngtcp2_ssize conn_read_handshake(ngtcp2_conn *conn, + const ngtcp2_path *path, + const ngtcp2_pkt_info *pi, + const uint8_t *pkt, size_t pktlen, + ngtcp2_tstamp ts) { + int rv; + ngtcp2_ssize nread; + + switch (conn->state) { + case NGTCP2_CS_CLIENT_INITIAL: + /* TODO Better to log something when we ignore input */ + return (ngtcp2_ssize)pktlen; + case NGTCP2_CS_CLIENT_WAIT_HANDSHAKE: + nread = conn_recv_handshake_cpkt(conn, path, pi, pkt, pktlen, ts); + if (nread < 0) { + return nread; + } + + if (conn->state == NGTCP2_CS_CLIENT_INITIAL) { + /* Retry packet was received */ + return (ngtcp2_ssize)pktlen; + } + + assert(conn->hs_pktns); + + if (conn->hs_pktns->crypto.rx.ckm && conn->in_pktns) { + rv = conn_process_buffered_handshake_pkt(conn, ts); + if (rv != 0) { + return rv; + } + } + + if (conn_is_tls_handshake_completed(conn) && + !(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_COMPLETED)) { + rv = conn_handshake_completed(conn); + if (rv != 0) { + return rv; + } + + rv = conn_process_buffered_protected_pkt(conn, &conn->pktns, ts); + if (rv != 0) { + return rv; + } + } + + return nread; + case NGTCP2_CS_SERVER_INITIAL: + nread = conn_recv_handshake_cpkt(conn, path, pi, pkt, pktlen, ts); + if (nread < 0) { + return nread; + } + + /* + * Client Hello might not fit into single Initial packet (e.g., + * resuming session with client authentication). If we get Client + * Initial which does not increase offset or it is 0RTT packet + * buffered, perform address validation in order to buffer + * validated data only. + */ + if (ngtcp2_strm_rx_offset(&conn->in_pktns->crypto.strm) == 0) { + if (conn->in_pktns->crypto.strm.rx.rob && + ngtcp2_rob_data_buffered(conn->in_pktns->crypto.strm.rx.rob)) { + /* Address has been validated with token */ + if (conn->local.settings.tokenlen) { + return nread; + } + return NGTCP2_ERR_RETRY; + } + /* If CRYPTO frame is not processed, just drop connection. */ + return NGTCP2_ERR_DROP_CONN; + } + + /* Process re-ordered 0-RTT packets which arrived before Initial + packet. */ + if (conn->early.ckm) { + assert(conn->in_pktns); + + rv = conn_process_buffered_protected_pkt(conn, conn->in_pktns, ts); + if (rv != 0) { + return rv; + } + } + + return nread; + case NGTCP2_CS_SERVER_WAIT_HANDSHAKE: + nread = conn_recv_handshake_cpkt(conn, path, pi, pkt, pktlen, ts); + if (nread < 0) { + return nread; + } + + if (conn->hs_pktns->crypto.rx.ckm) { + rv = conn_process_buffered_handshake_pkt(conn, ts); + if (rv != 0) { + return rv; + } + } + + if (conn->hs_pktns->rx.max_pkt_num != -1) { + conn_discard_initial_state(conn, ts); + } + + if (!conn_is_tls_handshake_completed(conn)) { + /* If server hits amplification limit, it cancels loss detection + timer. If server receives a packet from client, the limit is + increased and server can send more. If server has + ack-eliciting Initial or Handshake packets, it should resend + it if timer fired but timer is not armed in this case. So + instead of resending Initial/Handshake packets, if server has + 1RTT data to send, it might send them and then might hit + amplification limit again until it hits stream data limit. + Initial/Handshake data is not resent. In order to avoid this + situation, try to arm loss detection and check the expiry + here so that on next write call, we can resend + Initial/Handshake first. */ + if (conn->cstat.loss_detection_timer == UINT64_MAX) { + ngtcp2_conn_set_loss_detection_timer(conn, ts); + if (ngtcp2_conn_loss_detection_expiry(conn) <= ts) { + rv = ngtcp2_conn_on_loss_detection_timer(conn, ts); + if (rv != 0) { + return rv; + } + } + } + + 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); + + rv = conn_buffer_pkt(conn, &conn->pktns, path, pi, pkt + nread, + pktlen - (size_t)nread, pktlen, ts); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + return rv; + } + + return (ngtcp2_ssize)pktlen; + } + + return nread; + } + + if (!(conn->flags & NGTCP2_CONN_FLAG_TRANSPORT_PARAM_RECVED)) { + return NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM; + } + + rv = conn_handshake_completed(conn); + if (rv != 0) { + return rv; + } + conn->state = NGTCP2_CS_POST_HANDSHAKE; + + rv = conn_call_activate_dcid(conn, &conn->dcid.current); + if (rv != 0) { + return rv; + } + + rv = conn_process_buffered_protected_pkt(conn, &conn->pktns, ts); + if (rv != 0) { + return rv; + } + + conn_discard_handshake_state(conn, ts); + + rv = conn_enqueue_handshake_done(conn); + if (rv != 0) { + return rv; + } + + if (!conn->local.settings.no_pmtud) { + rv = conn_start_pmtud(conn); + if (rv != 0) { + return rv; + } + } + + conn->pktns.rtb.persistent_congestion_start_ts = ts; + + /* Re-arm loss detection timer here after handshake has been + confirmed. */ + ngtcp2_conn_set_loss_detection_timer(conn, ts); + + return nread; + case NGTCP2_CS_CLOSING: + return NGTCP2_ERR_CLOSING; + case NGTCP2_CS_DRAINING: + return NGTCP2_ERR_DRAINING; + default: + return (ngtcp2_ssize)pktlen; + } +} + +int ngtcp2_conn_read_pkt_versioned(ngtcp2_conn *conn, const ngtcp2_path *path, + int pkt_info_version, + const ngtcp2_pkt_info *pi, + const uint8_t *pkt, size_t pktlen, + ngtcp2_tstamp ts) { + int rv = 0; + ngtcp2_ssize nread = 0; + const ngtcp2_pkt_info zero_pi = {0}; + (void)pkt_info_version; + + assert(!(conn->flags & NGTCP2_CONN_FLAG_PPE_PENDING)); + + conn_update_timestamp(conn, ts); + + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, "recv packet len=%zu", + pktlen); + + if (pktlen == 0) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + /* client does not expect a packet from unknown path. */ + if (!conn->server && !ngtcp2_path_eq(&conn->dcid.current.ps.path, path) && + (!conn->pv || !ngtcp2_path_eq(&conn->pv->dcid.ps.path, path)) && + !conn_is_retired_path(conn, path)) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, + "ignore packet from unknown path"); + return 0; + } + + if (!pi) { + pi = &zero_pi; + } + + switch (conn->state) { + case NGTCP2_CS_CLIENT_INITIAL: + case NGTCP2_CS_CLIENT_WAIT_HANDSHAKE: + nread = conn_read_handshake(conn, path, pi, pkt, pktlen, ts); + if (nread < 0) { + return (int)nread; + } + + if ((size_t)nread == pktlen) { + return 0; + } + + assert(conn->pktns.crypto.rx.ckm); + + pkt += nread; + pktlen -= (size_t)nread; + + break; + case NGTCP2_CS_SERVER_INITIAL: + case NGTCP2_CS_SERVER_WAIT_HANDSHAKE: + if (!ngtcp2_path_eq(&conn->dcid.current.ps.path, path)) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, + "ignore packet from unknown path during handshake"); + + if (conn->state == NGTCP2_CS_SERVER_INITIAL && + ngtcp2_strm_rx_offset(&conn->in_pktns->crypto.strm) == 0 && + (!conn->in_pktns->crypto.strm.rx.rob || + !ngtcp2_rob_data_buffered(conn->in_pktns->crypto.strm.rx.rob))) { + return NGTCP2_ERR_DROP_CONN; + } + + return 0; + } + + nread = conn_read_handshake(conn, path, pi, pkt, pktlen, ts); + if (nread < 0) { + return (int)nread; + } + + if ((size_t)nread == pktlen) { + return 0; + } + + assert(conn->pktns.crypto.rx.ckm); + + pkt += nread; + pktlen -= (size_t)nread; + + break; + case NGTCP2_CS_CLOSING: + return NGTCP2_ERR_CLOSING; + case NGTCP2_CS_DRAINING: + return NGTCP2_ERR_DRAINING; + case NGTCP2_CS_POST_HANDSHAKE: + rv = conn_prepare_key_update(conn, ts); + if (rv != 0) { + return rv; + } + break; + default: + ngtcp2_unreachable(); + } + + return conn_recv_cpkt(conn, path, pi, pkt, pktlen, ts); +} + +/* + * conn_check_pkt_num_exhausted returns nonzero if packet number is + * exhausted in at least one of packet number space. + */ +static int conn_check_pkt_num_exhausted(ngtcp2_conn *conn) { + ngtcp2_pktns *in_pktns = conn->in_pktns; + ngtcp2_pktns *hs_pktns = conn->hs_pktns; + + return (in_pktns && in_pktns->tx.last_pkt_num == NGTCP2_MAX_PKT_NUM) || + (hs_pktns && hs_pktns->tx.last_pkt_num == NGTCP2_MAX_PKT_NUM) || + conn->pktns.tx.last_pkt_num == NGTCP2_MAX_PKT_NUM; +} + +/* + * conn_retransmit_retry_early retransmits 0RTT packet after Retry is + * received from server. + */ +static ngtcp2_ssize conn_retransmit_retry_early(ngtcp2_conn *conn, + ngtcp2_pkt_info *pi, + uint8_t *dest, size_t destlen, + uint8_t flags, + ngtcp2_tstamp ts) { + return conn_write_pkt(conn, pi, dest, destlen, NULL, NGTCP2_PKT_0RTT, flags, + ts); +} + +/* + * conn_handshake_probe_left returns nonzero if there are probe + * packets to be sent for Initial or Handshake packet number space + * left. + */ +static int conn_handshake_probe_left(ngtcp2_conn *conn) { + return (conn->in_pktns && conn->in_pktns->rtb.probe_pkt_left) || + conn->hs_pktns->rtb.probe_pkt_left; +} + +/* + * conn_validate_early_transport_params_limits validates that the + * limits in transport parameters remembered by client for early data + * are not reduced. This function is only used by client and should + * only be called when early data is accepted by server. + */ +static int conn_validate_early_transport_params_limits(ngtcp2_conn *conn) { + const ngtcp2_transport_params *params = conn->remote.transport_params; + + assert(!conn->server); + assert(params); + + if (conn->early.transport_params.active_connection_id_limit > + params->active_connection_id_limit || + conn->early.transport_params.initial_max_data > + params->initial_max_data || + 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_streams_bidi > + params->initial_max_streams_bidi || + conn->early.transport_params.initial_max_streams_uni > + params->initial_max_streams_uni || + conn->early.transport_params.max_datagram_frame_size > + params->max_datagram_frame_size) { + return NGTCP2_ERR_PROTO; + } + + return 0; +} + +/* + * conn_write_handshake writes QUIC handshake packets to the buffer + * pointed by |dest| of length |destlen|. |write_datalen| specifies + * the expected length of 0RTT packet payload. Specify 0 to + * |write_datalen| if there is no such data. + * + * This function returns the number of bytes written to the buffer, or + * one of the following negative error codes: + * + * NGTCP2_ERR_PKT_NUM_EXHAUSTED + * Packet number is exhausted. + * NGTCP2_ERR_NOMEM + * Out of memory + * NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM + * Required transport parameter is missing. + * NGTCP2_CS_CLOSING + * Connection is in closing state. + * NGTCP2_CS_DRAINING + * Connection is in draining state. + * + * In addition to the above negative error codes, the same error codes + * from conn_recv_pkt may also be returned. + */ +static ngtcp2_ssize conn_write_handshake(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, + uint8_t *dest, size_t destlen, + uint64_t write_datalen, + ngtcp2_tstamp ts) { + int rv; + ngtcp2_ssize res = 0, nwrite = 0, early_spktlen = 0; + size_t origlen = destlen; + uint64_t pending_early_datalen; + ngtcp2_dcid *dcid; + ngtcp2_preferred_addr *paddr; + + switch (conn->state) { + case NGTCP2_CS_CLIENT_INITIAL: + pending_early_datalen = conn_retry_early_payloadlen(conn); + if (pending_early_datalen) { + write_datalen = pending_early_datalen; + } + + if (!(conn->flags & NGTCP2_CONN_FLAG_RECV_RETRY)) { + nwrite = + conn_write_client_initial(conn, pi, dest, destlen, write_datalen, ts); + if (nwrite <= 0) { + return nwrite; + } + } else { + nwrite = conn_write_handshake_pkt( + conn, pi, dest, destlen, NGTCP2_PKT_INITIAL, + NGTCP2_WRITE_PKT_FLAG_NONE, write_datalen, ts); + if (nwrite < 0) { + return nwrite; + } + } + + if (pending_early_datalen) { + early_spktlen = conn_retransmit_retry_early( + conn, pi, dest + nwrite, destlen - (size_t)nwrite, + nwrite ? NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING + : NGTCP2_WRITE_PKT_FLAG_NONE, + ts); + + if (early_spktlen < 0) { + assert(ngtcp2_err_is_fatal((int)early_spktlen)); + return early_spktlen; + } + } + + conn->state = NGTCP2_CS_CLIENT_WAIT_HANDSHAKE; + + res = nwrite + early_spktlen; + + return res; + case NGTCP2_CS_CLIENT_WAIT_HANDSHAKE: + if (!conn_handshake_probe_left(conn) && conn_cwnd_is_zero(conn)) { + destlen = 0; + } else { + if (!(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_COMPLETED)) { + pending_early_datalen = conn_retry_early_payloadlen(conn); + if (pending_early_datalen) { + write_datalen = pending_early_datalen; + } + } + + nwrite = + conn_write_handshake_pkts(conn, pi, dest, destlen, write_datalen, ts); + if (nwrite < 0) { + return nwrite; + } + + res += nwrite; + dest += nwrite; + destlen -= (size_t)nwrite; + } + + if (!conn_is_tls_handshake_completed(conn)) { + if (!(conn->flags & NGTCP2_CONN_FLAG_EARLY_DATA_REJECTED)) { + nwrite = conn_retransmit_retry_early(conn, pi, dest, destlen, + NGTCP2_WRITE_PKT_FLAG_NONE, ts); + if (nwrite < 0) { + return nwrite; + } + + res += nwrite; + } + + if (res == 0) { + nwrite = conn_write_handshake_ack_pkts(conn, pi, dest, origlen, ts); + if (nwrite < 0) { + return nwrite; + } + res = nwrite; + } + + return res; + } + + if (!(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_COMPLETED)) { + return res; + } + + if (!(conn->flags & NGTCP2_CONN_FLAG_TRANSPORT_PARAM_RECVED)) { + return NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM; + } + + if ((conn->flags & NGTCP2_CONN_FLAG_EARLY_KEY_INSTALLED) && + !(conn->flags & NGTCP2_CONN_FLAG_EARLY_DATA_REJECTED)) { + rv = conn_validate_early_transport_params_limits(conn); + if (rv != 0) { + return rv; + } + } + + /* Server might increase stream data limits. Extend it if we have + streams created for early data. */ + rv = conn_sync_stream_data_limit(conn); + if (rv != 0) { + return rv; + } + + conn->state = NGTCP2_CS_POST_HANDSHAKE; + + assert(conn->remote.transport_params); + + if (conn->remote.transport_params->preferred_addr_present) { + assert(!ngtcp2_ringbuf_full(&conn->dcid.unused.rb)); + + paddr = &conn->remote.transport_params->preferred_addr; + dcid = ngtcp2_ringbuf_push_back(&conn->dcid.unused.rb); + ngtcp2_dcid_init(dcid, 1, &paddr->cid, paddr->stateless_reset_token); + + rv = ngtcp2_gaptr_push(&conn->dcid.seqgap, 1, 1); + if (rv != 0) { + return (ngtcp2_ssize)rv; + } + } + + if (conn->remote.transport_params->stateless_reset_token_present) { + assert(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); + } + + rv = conn_call_activate_dcid(conn, &conn->dcid.current); + if (rv != 0) { + return rv; + } + + conn_process_early_rtb(conn); + + if (!conn->local.settings.no_pmtud) { + rv = conn_start_pmtud(conn); + if (rv != 0) { + return rv; + } + } + + return res; + case NGTCP2_CS_SERVER_INITIAL: + nwrite = + conn_write_handshake_pkts(conn, pi, dest, destlen, write_datalen, ts); + if (nwrite < 0) { + return nwrite; + } + + if (nwrite) { + conn->state = NGTCP2_CS_SERVER_WAIT_HANDSHAKE; + } + + return nwrite; + case NGTCP2_CS_SERVER_WAIT_HANDSHAKE: + if (conn_handshake_probe_left(conn) || !conn_cwnd_is_zero(conn)) { + nwrite = + conn_write_handshake_pkts(conn, pi, dest, destlen, write_datalen, ts); + if (nwrite < 0) { + return nwrite; + } + + res += nwrite; + dest += nwrite; + destlen -= (size_t)nwrite; + } + + if (res == 0) { + nwrite = conn_write_handshake_ack_pkts(conn, pi, dest, origlen, ts); + if (nwrite < 0) { + return nwrite; + } + + res += nwrite; + dest += nwrite; + origlen -= (size_t)nwrite; + } + + return res; + case NGTCP2_CS_CLOSING: + return NGTCP2_ERR_CLOSING; + case NGTCP2_CS_DRAINING: + return NGTCP2_ERR_DRAINING; + default: + return 0; + } +} + +/** + * @function + * + * `conn_client_write_handshake` writes client side handshake data and + * 0RTT packet. + * + * In order to send STREAM data in 0RTT packet, specify + * |vmsg|->stream. |vmsg|->stream.strm, |vmsg|->stream.fin, + * |vmsg|->stream.data, and |vmsg|->stream.datacnt are stream to which + * 0-RTT data is sent, whether it is a last data chunk in this stream, + * a vector of 0-RTT data, and its number of elements respectively. + * The amount of 0RTT data sent is assigned to + * *|vmsg|->stream.pdatalen. If no data is sent, -1 is assigned. + * Note that 0 length STREAM frame is allowed in QUIC, so 0 might be + * assigned to *|vmsg|->stream.pdatalen. + * + * This function returns 0 if it cannot write any frame because buffer + * is too small, or packet is congestion limited. Application should + * keep reading and wait for congestion window to grow. + * + * This function returns the number of bytes written to the buffer + * pointed by |dest| if it succeeds, or one of the following negative + * error codes: (TBD). + */ +static ngtcp2_ssize conn_client_write_handshake(ngtcp2_conn *conn, + ngtcp2_pkt_info *pi, + uint8_t *dest, size_t destlen, + ngtcp2_vmsg *vmsg, + ngtcp2_tstamp ts) { + int send_stream = 0; + int send_datagram = 0; + ngtcp2_ssize spktlen, early_spktlen; + uint64_t datalen; + uint64_t write_datalen = 0; + uint8_t wflags = NGTCP2_WRITE_PKT_FLAG_NONE; + int ppe_pending = (conn->flags & NGTCP2_CONN_FLAG_PPE_PENDING) != 0; + uint32_t version; + + assert(!conn->server); + + /* conn->early.ckm might be created in the first call of + conn_handshake(). Check it later. */ + if (vmsg) { + switch (vmsg->type) { + case NGTCP2_VMSG_TYPE_STREAM: + datalen = ngtcp2_vec_len(vmsg->stream.data, vmsg->stream.datacnt); + send_stream = conn_retry_early_payloadlen(conn) == 0; + if (send_stream) { + write_datalen = ngtcp2_min(datalen + NGTCP2_STREAM_OVERHEAD, + NGTCP2_MIN_COALESCED_PAYLOADLEN); + + if (vmsg->stream.flags & NGTCP2_WRITE_STREAM_FLAG_MORE) { + wflags |= NGTCP2_WRITE_PKT_FLAG_MORE; + } + } else { + vmsg = NULL; + } + break; + case NGTCP2_VMSG_TYPE_DATAGRAM: + datalen = ngtcp2_vec_len(vmsg->datagram.data, vmsg->datagram.datacnt); + send_datagram = conn_retry_early_payloadlen(conn) == 0; + if (send_datagram) { + write_datalen = datalen + NGTCP2_DATAGRAM_OVERHEAD; + + if (vmsg->datagram.flags & NGTCP2_WRITE_DATAGRAM_FLAG_MORE) { + wflags |= NGTCP2_WRITE_PKT_FLAG_MORE; + } + } else { + vmsg = NULL; + } + break; + } + } + + if (!ppe_pending) { + spktlen = conn_write_handshake(conn, pi, dest, destlen, write_datalen, ts); + + if (spktlen < 0) { + return spktlen; + } + + if ((conn->flags & NGTCP2_CONN_FLAG_EARLY_DATA_REJECTED) || + !conn->early.ckm || (!send_stream && !send_datagram)) { + return spktlen; + } + + /* If spktlen > 0, we are making a compound packet. If Initial + packet is written, we have to pad bytes to 0-RTT packet. */ + version = conn->negotiated_version ? conn->negotiated_version + : conn->client_chosen_version; + if (spktlen > 0 && + ngtcp2_pkt_get_type_long(version, dest[0]) == NGTCP2_PKT_INITIAL) { + wflags |= NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING; + conn->pkt.require_padding = 1; + } + } else { + assert(!conn->pktns.crypto.rx.ckm); + assert(!conn->pktns.crypto.tx.ckm); + assert(conn->early.ckm); + + if (conn->pkt.require_padding) { + wflags |= NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING; + } + spktlen = conn->pkt.hs_spktlen; + } + + dest += spktlen; + destlen -= (size_t)spktlen; + + if (conn_cwnd_is_zero(conn)) { + return spktlen; + } + + early_spktlen = conn_write_pkt(conn, pi, dest, destlen, vmsg, NGTCP2_PKT_0RTT, + wflags, ts); + if (early_spktlen < 0) { + switch (early_spktlen) { + case NGTCP2_ERR_STREAM_DATA_BLOCKED: + if (!(wflags & NGTCP2_WRITE_PKT_FLAG_MORE)) { + if (spktlen) { + return spktlen; + } + + break; + } + /* fall through */ + case NGTCP2_ERR_WRITE_MORE: + conn->pkt.hs_spktlen = spktlen; + break; + } + return early_spktlen; + } + + return spktlen + early_spktlen; +} + +void ngtcp2_conn_tls_handshake_completed(ngtcp2_conn *conn) { + conn->flags |= NGTCP2_CONN_FLAG_TLS_HANDSHAKE_COMPLETED; + if (conn->server) { + conn->flags |= NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED; + } +} + +int ngtcp2_conn_get_handshake_completed(ngtcp2_conn *conn) { + return conn_is_tls_handshake_completed(conn) && + (conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_COMPLETED); +} + +int ngtcp2_conn_sched_ack(ngtcp2_conn *conn, ngtcp2_acktr *acktr, + int64_t pkt_num, int active_ack, ngtcp2_tstamp ts) { + int rv; + (void)conn; + + rv = ngtcp2_acktr_add(acktr, pkt_num, active_ack, ts); + if (rv != 0) { + assert(rv != NGTCP2_ERR_INVALID_ARGUMENT); + return rv; + } + + return 0; +} + +int ngtcp2_accept(ngtcp2_pkt_hd *dest, const uint8_t *pkt, size_t pktlen) { + ngtcp2_ssize nread; + ngtcp2_pkt_hd hd, *p; + + if (dest) { + p = dest; + } else { + p = &hd; + } + + if (pktlen == 0 || (pkt[0] & NGTCP2_HEADER_FORM_BIT) == 0) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + nread = ngtcp2_pkt_decode_hd_long(p, pkt, pktlen); + if (nread < 0) { + return (int)nread; + } + + switch (p->type) { + case NGTCP2_PKT_INITIAL: + break; + case NGTCP2_PKT_0RTT: + /* 0-RTT packet may arrive before Initial packet due to + re-ordering. ngtcp2 does not buffer 0RTT packet unless the + very first Initial packet is received or token is received. + Previously, we returned NGTCP2_ERR_RETRY here, so that client + can resend 0RTT data. But it incurs 1RTT already and + diminishes the value of 0RTT. Therefore, we just discard the + packet here for now. */ + default: + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + if (pktlen < NGTCP2_MAX_UDP_PAYLOAD_SIZE || + (p->tokenlen == 0 && p->dcid.datalen < NGTCP2_MIN_INITIAL_DCIDLEN)) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + return 0; +} + +int ngtcp2_conn_install_initial_key( + ngtcp2_conn *conn, const ngtcp2_crypto_aead_ctx *rx_aead_ctx, + const uint8_t *rx_iv, const ngtcp2_crypto_cipher_ctx *rx_hp_ctx, + const ngtcp2_crypto_aead_ctx *tx_aead_ctx, const uint8_t *tx_iv, + const ngtcp2_crypto_cipher_ctx *tx_hp_ctx, size_t ivlen) { + ngtcp2_pktns *pktns = conn->in_pktns; + int rv; + + assert(ivlen >= 8); + assert(pktns); + + conn_call_delete_crypto_cipher_ctx(conn, &pktns->crypto.rx.hp_ctx); + pktns->crypto.rx.hp_ctx.native_handle = NULL; + + if (pktns->crypto.rx.ckm) { + conn_call_delete_crypto_aead_ctx(conn, &pktns->crypto.rx.ckm->aead_ctx); + ngtcp2_crypto_km_del(pktns->crypto.rx.ckm, conn->mem); + pktns->crypto.rx.ckm = NULL; + } + + conn_call_delete_crypto_cipher_ctx(conn, &pktns->crypto.tx.hp_ctx); + pktns->crypto.tx.hp_ctx.native_handle = NULL; + + if (pktns->crypto.tx.ckm) { + conn_call_delete_crypto_aead_ctx(conn, &pktns->crypto.tx.ckm->aead_ctx); + ngtcp2_crypto_km_del(pktns->crypto.tx.ckm, conn->mem); + pktns->crypto.tx.ckm = NULL; + } + + rv = ngtcp2_crypto_km_new(&pktns->crypto.rx.ckm, NULL, 0, NULL, rx_iv, ivlen, + conn->mem); + if (rv != 0) { + return rv; + } + + rv = ngtcp2_crypto_km_new(&pktns->crypto.tx.ckm, NULL, 0, NULL, tx_iv, ivlen, + conn->mem); + if (rv != 0) { + return rv; + } + + /* Take owner ship after we are sure that no failure occurs, so that + caller can delete these contexts on failure. */ + pktns->crypto.rx.ckm->aead_ctx = *rx_aead_ctx; + pktns->crypto.rx.hp_ctx = *rx_hp_ctx; + pktns->crypto.tx.ckm->aead_ctx = *tx_aead_ctx; + pktns->crypto.tx.hp_ctx = *tx_hp_ctx; + + return 0; +} + +int ngtcp2_conn_install_vneg_initial_key( + ngtcp2_conn *conn, uint32_t version, + const ngtcp2_crypto_aead_ctx *rx_aead_ctx, const uint8_t *rx_iv, + const ngtcp2_crypto_cipher_ctx *rx_hp_ctx, + const ngtcp2_crypto_aead_ctx *tx_aead_ctx, const uint8_t *tx_iv, + const ngtcp2_crypto_cipher_ctx *tx_hp_ctx, size_t ivlen) { + int rv; + + assert(ivlen >= 8); + + conn_call_delete_crypto_cipher_ctx(conn, &conn->vneg.rx.hp_ctx); + conn->vneg.rx.hp_ctx.native_handle = NULL; + + if (conn->vneg.rx.ckm) { + conn_call_delete_crypto_aead_ctx(conn, &conn->vneg.rx.ckm->aead_ctx); + ngtcp2_crypto_km_del(conn->vneg.rx.ckm, conn->mem); + conn->vneg.rx.ckm = NULL; + } + + conn_call_delete_crypto_cipher_ctx(conn, &conn->vneg.tx.hp_ctx); + conn->vneg.tx.hp_ctx.native_handle = NULL; + + if (conn->vneg.tx.ckm) { + conn_call_delete_crypto_aead_ctx(conn, &conn->vneg.tx.ckm->aead_ctx); + ngtcp2_crypto_km_del(conn->vneg.tx.ckm, conn->mem); + conn->vneg.tx.ckm = NULL; + } + + rv = ngtcp2_crypto_km_new(&conn->vneg.rx.ckm, NULL, 0, NULL, rx_iv, ivlen, + conn->mem); + if (rv != 0) { + return rv; + } + + rv = ngtcp2_crypto_km_new(&conn->vneg.tx.ckm, NULL, 0, NULL, tx_iv, ivlen, + conn->mem); + if (rv != 0) { + return rv; + } + + /* Take owner ship after we are sure that no failure occurs, so that + caller can delete these contexts on failure. */ + conn->vneg.rx.ckm->aead_ctx = *rx_aead_ctx; + conn->vneg.rx.hp_ctx = *rx_hp_ctx; + conn->vneg.tx.ckm->aead_ctx = *tx_aead_ctx; + conn->vneg.tx.hp_ctx = *tx_hp_ctx; + conn->vneg.version = version; + + return 0; +} + +int ngtcp2_conn_install_rx_handshake_key( + ngtcp2_conn *conn, const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *iv, size_t ivlen, const ngtcp2_crypto_cipher_ctx *hp_ctx) { + ngtcp2_pktns *pktns = conn->hs_pktns; + int rv; + + assert(ivlen >= 8); + assert(pktns); + assert(!pktns->crypto.rx.hp_ctx.native_handle); + assert(!pktns->crypto.rx.ckm); + + rv = ngtcp2_crypto_km_new(&pktns->crypto.rx.ckm, NULL, 0, aead_ctx, iv, ivlen, + conn->mem); + if (rv != 0) { + return rv; + } + + pktns->crypto.rx.hp_ctx = *hp_ctx; + + rv = conn_call_recv_rx_key(conn, NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE); + 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)); + + return rv; + } + + return 0; +} + +int ngtcp2_conn_install_tx_handshake_key( + ngtcp2_conn *conn, const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *iv, size_t ivlen, const ngtcp2_crypto_cipher_ctx *hp_ctx) { + ngtcp2_pktns *pktns = conn->hs_pktns; + int rv; + + assert(ivlen >= 8); + assert(pktns); + assert(!pktns->crypto.tx.hp_ctx.native_handle); + assert(!pktns->crypto.tx.ckm); + + rv = ngtcp2_crypto_km_new(&pktns->crypto.tx.ckm, NULL, 0, aead_ctx, iv, ivlen, + conn->mem); + if (rv != 0) { + return rv; + } + + pktns->crypto.tx.hp_ctx = *hp_ctx; + + if (conn->server) { + rv = ngtcp2_conn_commit_local_transport_params(conn); + if (rv != 0) { + return rv; + } + } + + 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; + } + + return 0; +} + +int ngtcp2_conn_install_0rtt_key(ngtcp2_conn *conn, + const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *iv, size_t ivlen, + const ngtcp2_crypto_cipher_ctx *hp_ctx) { + int rv; + + assert(ivlen >= 8); + assert(!conn->early.hp_ctx.native_handle); + assert(!conn->early.ckm); + + rv = ngtcp2_crypto_km_new(&conn->early.ckm, NULL, 0, aead_ctx, iv, ivlen, + conn->mem); + if (rv != 0) { + return rv; + } + + conn->early.hp_ctx = *hp_ctx; + + conn->flags |= NGTCP2_CONN_FLAG_EARLY_KEY_INSTALLED; + + if (conn->server) { + rv = conn_call_recv_rx_key(conn, NGTCP2_ENCRYPTION_LEVEL_0RTT); + } else { + rv = conn_call_recv_tx_key(conn, NGTCP2_ENCRYPTION_LEVEL_0RTT); + } + 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)); + + return rv; + } + + return 0; +} + +int ngtcp2_conn_install_rx_key(ngtcp2_conn *conn, const uint8_t *secret, + size_t secretlen, + const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *iv, size_t ivlen, + const ngtcp2_crypto_cipher_ctx *hp_ctx) { + ngtcp2_pktns *pktns = &conn->pktns; + int rv; + + assert(ivlen >= 8); + assert(!pktns->crypto.rx.hp_ctx.native_handle); + assert(!pktns->crypto.rx.ckm); + + rv = ngtcp2_crypto_km_new(&pktns->crypto.rx.ckm, secret, secretlen, aead_ctx, + iv, ivlen, conn->mem); + if (rv != 0) { + return rv; + } + + pktns->crypto.rx.hp_ctx = *hp_ctx; + + if (!conn->server) { + if (conn->remote.pending_transport_params) { + ngtcp2_transport_params_del(conn->remote.transport_params, conn->mem); + + conn->remote.transport_params = conn->remote.pending_transport_params; + conn->remote.pending_transport_params = NULL; + conn_sync_stream_id_limit(conn); + conn->tx.max_offset = conn->remote.transport_params->initial_max_data; + } + + if (conn->early.ckm) { + conn_discard_early_key(conn); + } + } + + rv = conn_call_recv_rx_key(conn, NGTCP2_ENCRYPTION_LEVEL_1RTT); + 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)); + + return rv; + } + + return 0; +} + +int ngtcp2_conn_install_tx_key(ngtcp2_conn *conn, const uint8_t *secret, + size_t secretlen, + const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *iv, size_t ivlen, + const ngtcp2_crypto_cipher_ctx *hp_ctx) { + ngtcp2_pktns *pktns = &conn->pktns; + int rv; + + assert(ivlen >= 8); + assert(!pktns->crypto.tx.hp_ctx.native_handle); + assert(!pktns->crypto.tx.ckm); + + rv = ngtcp2_crypto_km_new(&pktns->crypto.tx.ckm, secret, secretlen, aead_ctx, + iv, ivlen, conn->mem); + if (rv != 0) { + return rv; + } + + pktns->crypto.tx.hp_ctx = *hp_ctx; + + if (conn->server) { + if (conn->remote.pending_transport_params) { + ngtcp2_transport_params_del(conn->remote.transport_params, conn->mem); + + conn->remote.transport_params = conn->remote.pending_transport_params; + conn->remote.pending_transport_params = NULL; + conn_sync_stream_id_limit(conn); + conn->tx.max_offset = conn->remote.transport_params->initial_max_data; + } + } else if (conn->early.ckm) { + conn_discard_early_key(conn); + } + + rv = conn_call_recv_tx_key(conn, NGTCP2_ENCRYPTION_LEVEL_1RTT); + 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; + } + + return 0; +} + +static int conn_initiate_key_update(ngtcp2_conn *conn, ngtcp2_tstamp ts) { + ngtcp2_tstamp confirmed_ts = conn->crypto.key_update.confirmed_ts; + ngtcp2_duration pto = conn_compute_pto(conn, &conn->pktns); + + assert(conn->state == NGTCP2_CS_POST_HANDSHAKE); + + if (!(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED) || + (conn->flags & NGTCP2_CONN_FLAG_KEY_UPDATE_NOT_CONFIRMED) || + !conn->crypto.key_update.new_tx_ckm || + !conn->crypto.key_update.new_rx_ckm || + ngtcp2_tstamp_not_elapsed(confirmed_ts, 3 * pto, ts)) { + return NGTCP2_ERR_INVALID_STATE; + } + + conn_rotate_keys(conn, NGTCP2_MAX_PKT_NUM, /* initiator = */ 1); + + return 0; +} + +int ngtcp2_conn_initiate_key_update(ngtcp2_conn *conn, ngtcp2_tstamp ts) { + conn_update_timestamp(conn, ts); + + return conn_initiate_key_update(conn, ts); +} + +/* + * conn_retire_stale_bound_dcid retires stale destination connection + * ID in conn->dcid.bound to keep some unused destination connection + * IDs available. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +static int conn_retire_stale_bound_dcid(ngtcp2_conn *conn, + ngtcp2_duration timeout, + ngtcp2_tstamp ts) { + size_t i; + ngtcp2_dcid *dcid, *last; + int rv; + + for (i = 0; i < ngtcp2_ringbuf_len(&conn->dcid.bound.rb);) { + dcid = ngtcp2_ringbuf_get(&conn->dcid.bound.rb, i); + + assert(dcid->cid.datalen); + + if (ngtcp2_tstamp_not_elapsed(dcid->bound_ts, timeout, ts)) { + ++i; + continue; + } + + rv = conn_retire_dcid_seq(conn, dcid->seq); + if (rv != 0) { + return rv; + } + + if (i == 0) { + ngtcp2_ringbuf_pop_front(&conn->dcid.bound.rb); + continue; + } + + if (i == ngtcp2_ringbuf_len(&conn->dcid.bound.rb) - 1) { + ngtcp2_ringbuf_pop_back(&conn->dcid.bound.rb); + break; + } + + last = ngtcp2_ringbuf_get(&conn->dcid.bound.rb, + ngtcp2_ringbuf_len(&conn->dcid.bound.rb) - 1); + ngtcp2_dcid_copy(dcid, last); + ngtcp2_ringbuf_pop_back(&conn->dcid.bound.rb); + } + + return 0; +} + +ngtcp2_tstamp ngtcp2_conn_loss_detection_expiry(ngtcp2_conn *conn) { + return conn->cstat.loss_detection_timer; +} + +ngtcp2_tstamp ngtcp2_conn_internal_expiry(ngtcp2_conn *conn) { + ngtcp2_tstamp res = UINT64_MAX, t; + ngtcp2_duration pto = conn_compute_pto(conn, &conn->pktns); + ngtcp2_scid *scid; + ngtcp2_dcid *dcid; + size_t i, len; + + if (conn->pv) { + res = ngtcp2_pv_next_expiry(conn->pv); + } + + if (conn->pmtud) { + res = ngtcp2_min(res, conn->pmtud->expiry); + } + + if (!ngtcp2_pq_empty(&conn->scid.used)) { + scid = ngtcp2_struct_of(ngtcp2_pq_top(&conn->scid.used), ngtcp2_scid, pe); + if (scid->retired_ts != UINT64_MAX) { + t = scid->retired_ts + pto; + res = ngtcp2_min(res, t); + } + } + + if (ngtcp2_ringbuf_len(&conn->dcid.retired.rb)) { + dcid = ngtcp2_ringbuf_get(&conn->dcid.retired.rb, 0); + t = dcid->retired_ts + pto; + res = ngtcp2_min(res, t); + } + + if (conn->dcid.current.cid.datalen) { + len = ngtcp2_ringbuf_len(&conn->dcid.bound.rb); + for (i = 0; i < len; ++i) { + dcid = ngtcp2_ringbuf_get(&conn->dcid.bound.rb, i); + + assert(dcid->cid.datalen); + assert(dcid->bound_ts != UINT64_MAX); + + t = dcid->bound_ts + 3 * pto; + res = ngtcp2_min(res, t); + } + } + + if (conn->server && conn->early.ckm && + conn->early.discard_started_ts != UINT64_MAX) { + t = conn->early.discard_started_ts + 3 * pto; + res = ngtcp2_min(res, t); + } + + return res; +} + +ngtcp2_tstamp ngtcp2_conn_ack_delay_expiry(ngtcp2_conn *conn) { + ngtcp2_acktr *acktr = &conn->pktns.acktr; + + if (!(acktr->flags & NGTCP2_ACKTR_FLAG_CANCEL_TIMER) && + acktr->first_unacked_ts != UINT64_MAX) { + return acktr->first_unacked_ts + conn_compute_ack_delay(conn); + } + return UINT64_MAX; +} + +static ngtcp2_tstamp conn_handshake_expiry(ngtcp2_conn *conn) { + if (conn_is_tls_handshake_completed(conn) || + conn->local.settings.handshake_timeout == UINT64_MAX || + conn->local.settings.initial_ts >= + UINT64_MAX - conn->local.settings.handshake_timeout) { + return UINT64_MAX; + } + + return conn->local.settings.initial_ts + + conn->local.settings.handshake_timeout; +} + +ngtcp2_tstamp ngtcp2_conn_get_expiry(ngtcp2_conn *conn) { + ngtcp2_tstamp t1 = ngtcp2_conn_loss_detection_expiry(conn); + ngtcp2_tstamp t2 = ngtcp2_conn_ack_delay_expiry(conn); + ngtcp2_tstamp t3 = ngtcp2_conn_internal_expiry(conn); + ngtcp2_tstamp t4 = ngtcp2_conn_lost_pkt_expiry(conn); + ngtcp2_tstamp t5 = conn_keep_alive_expiry(conn); + ngtcp2_tstamp t6 = conn_handshake_expiry(conn); + ngtcp2_tstamp t7 = ngtcp2_conn_get_idle_expiry(conn); + ngtcp2_tstamp res = ngtcp2_min(t1, t2); + res = ngtcp2_min(res, t3); + res = ngtcp2_min(res, t4); + res = ngtcp2_min(res, t5); + res = ngtcp2_min(res, t6); + res = ngtcp2_min(res, t7); + return ngtcp2_min(res, conn->tx.pacing.next_ts); +} + +int ngtcp2_conn_handle_expiry(ngtcp2_conn *conn, ngtcp2_tstamp ts) { + int rv; + ngtcp2_duration pto; + + conn_update_timestamp(conn, ts); + + pto = conn_compute_pto(conn, &conn->pktns); + + assert(!(conn->flags & NGTCP2_CONN_FLAG_PPE_PENDING)); + + if (ngtcp2_conn_get_idle_expiry(conn) <= ts) { + return NGTCP2_ERR_IDLE_CLOSE; + } + + ngtcp2_conn_cancel_expired_ack_delay_timer(conn, ts); + + conn_cancel_expired_keep_alive_timer(conn, ts); + + conn_cancel_expired_pkt_tx_timer(conn, ts); + + ngtcp2_conn_remove_lost_pkt(conn, ts); + + if (conn->pv) { + ngtcp2_pv_cancel_expired_timer(conn->pv, ts); + } + + if (conn->pmtud) { + ngtcp2_pmtud_handle_expiry(conn->pmtud, ts); + if (ngtcp2_pmtud_finished(conn->pmtud)) { + ngtcp2_conn_stop_pmtud(conn); + } + } + + if (ngtcp2_conn_loss_detection_expiry(conn) <= ts) { + rv = ngtcp2_conn_on_loss_detection_timer(conn, ts); + if (rv != 0) { + return rv; + } + } + + if (conn->dcid.current.cid.datalen) { + rv = conn_retire_stale_bound_dcid(conn, 3 * pto, ts); + if (rv != 0) { + return rv; + } + } + + rv = conn_remove_retired_connection_id(conn, pto, ts); + if (rv != 0) { + return rv; + } + + if (conn->server && conn->early.ckm && + ngtcp2_tstamp_elapsed(conn->early.discard_started_ts, 3 * pto, ts)) { + conn_discard_early_key(conn); + } + + if (!conn_is_tls_handshake_completed(conn) && + ngtcp2_tstamp_elapsed(conn->local.settings.initial_ts, + conn->local.settings.handshake_timeout, ts)) { + return NGTCP2_ERR_HANDSHAKE_TIMEOUT; + } + + return 0; +} + +static void acktr_cancel_expired_ack_delay_timer(ngtcp2_acktr *acktr, + ngtcp2_duration max_ack_delay, + ngtcp2_tstamp ts) { + if (!(acktr->flags & NGTCP2_ACKTR_FLAG_CANCEL_TIMER) && + ngtcp2_tstamp_elapsed(acktr->first_unacked_ts, max_ack_delay, ts)) { + acktr->flags |= NGTCP2_ACKTR_FLAG_CANCEL_TIMER; + } +} + +void ngtcp2_conn_cancel_expired_ack_delay_timer(ngtcp2_conn *conn, + ngtcp2_tstamp ts) { + ngtcp2_duration ack_delay = conn_compute_ack_delay(conn); + + if (conn->in_pktns) { + acktr_cancel_expired_ack_delay_timer(&conn->in_pktns->acktr, 0, ts); + } + if (conn->hs_pktns) { + acktr_cancel_expired_ack_delay_timer(&conn->hs_pktns->acktr, 0, ts); + } + acktr_cancel_expired_ack_delay_timer(&conn->pktns.acktr, ack_delay, ts); +} + +ngtcp2_tstamp ngtcp2_conn_lost_pkt_expiry(ngtcp2_conn *conn) { + ngtcp2_tstamp res = UINT64_MAX, ts; + + if (conn->in_pktns) { + ts = ngtcp2_rtb_lost_pkt_ts(&conn->in_pktns->rtb); + if (ts != UINT64_MAX) { + ts += conn_compute_pto(conn, conn->in_pktns); + res = ngtcp2_min(res, ts); + } + } + + if (conn->hs_pktns) { + ts = ngtcp2_rtb_lost_pkt_ts(&conn->hs_pktns->rtb); + if (ts != UINT64_MAX) { + ts += conn_compute_pto(conn, conn->hs_pktns); + res = ngtcp2_min(res, ts); + } + } + + ts = ngtcp2_rtb_lost_pkt_ts(&conn->pktns.rtb); + if (ts != UINT64_MAX) { + ts += conn_compute_pto(conn, &conn->pktns); + res = ngtcp2_min(res, ts); + } + + return res; +} + +void ngtcp2_conn_remove_lost_pkt(ngtcp2_conn *conn, ngtcp2_tstamp ts) { + ngtcp2_duration pto; + + if (conn->in_pktns) { + pto = conn_compute_pto(conn, conn->in_pktns); + ngtcp2_rtb_remove_expired_lost_pkt(&conn->in_pktns->rtb, pto, ts); + } + if (conn->hs_pktns) { + pto = conn_compute_pto(conn, conn->hs_pktns); + ngtcp2_rtb_remove_expired_lost_pkt(&conn->hs_pktns->rtb, pto, ts); + } + pto = conn_compute_pto(conn, &conn->pktns); + ngtcp2_rtb_remove_expired_lost_pkt(&conn->pktns.rtb, pto, ts); +} + +/* + * select_preferred_version selects the most preferred version. + * |fallback_version| is chosen if no preference is made, or + * |preferred_versions| does not include any of |chosen_version| or + * |available_versions|. |chosen_version| is treated as an extra + * other version. + */ +static uint32_t select_preferred_version(const uint32_t *preferred_versions, + size_t preferred_versionslen, + uint32_t chosen_version, + const uint8_t *available_versions, + size_t available_versionslen, + uint32_t fallback_version) { + size_t i, j; + const uint8_t *p; + uint32_t v; + + if (!preferred_versionslen || + (!available_versionslen && chosen_version == fallback_version)) { + return fallback_version; + } + + for (i = 0; i < preferred_versionslen; ++i) { + if (preferred_versions[i] == chosen_version) { + return chosen_version; + } + for (j = 0, p = available_versions; j < available_versionslen; + j += sizeof(uint32_t)) { + p = ngtcp2_get_uint32(&v, p); + + if (preferred_versions[i] == v) { + return v; + } + } + } + + return fallback_version; +} + +/* + * conn_client_validate_transport_params validates |params| as client. + * |params| must be sent with Encrypted Extensions. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_TRANSPORT_PARAM + * params contains preferred address but server chose zero-length + * connection ID. + * NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE + * Validation against version negotiation parameters failed. + */ +static int +conn_client_validate_transport_params(ngtcp2_conn *conn, + const ngtcp2_transport_params *params) { + if (!params->original_dcid_present) { + return NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM; + } + + if (!ngtcp2_cid_eq(&conn->rcid, ¶ms->original_dcid)) { + return NGTCP2_ERR_TRANSPORT_PARAM; + } + + if (conn->flags & NGTCP2_CONN_FLAG_RECV_RETRY) { + if (!params->retry_scid_present) { + return NGTCP2_ERR_TRANSPORT_PARAM; + } + if (!ngtcp2_cid_eq(&conn->retry_scid, ¶ms->retry_scid)) { + return NGTCP2_ERR_TRANSPORT_PARAM; + } + } else if (params->retry_scid_present) { + return NGTCP2_ERR_TRANSPORT_PARAM; + } + + if (params->preferred_addr_present && conn->dcid.current.cid.datalen == 0) { + return NGTCP2_ERR_TRANSPORT_PARAM; + } + + if (params->version_info_present) { + if (conn->negotiated_version != params->version_info.chosen_version) { + return NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE; + } + + assert(vneg_available_versions_includes(conn->vneg.available_versions, + conn->vneg.available_versionslen, + conn->negotiated_version)); + } else if (conn->client_chosen_version != conn->negotiated_version) { + return NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE; + } + + /* When client reacted upon Version Negotiation */ + if (conn->local.settings.original_version != conn->client_chosen_version) { + if (!params->version_info_present) { + assert(conn->client_chosen_version == conn->negotiated_version); + + /* QUIC v1 is treated specially. If version_info is missing, no + further validation is necessary. See + https://datatracker.ietf.org/doc/html/rfc9368#section-8 + */ + if (conn->client_chosen_version == NGTCP2_PROTO_VER_V1) { + return 0; + } + + return NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE; + } + + /* Server choose original version after Version Negotiation. RFC + 9368 does not say this particular case, but this smells like + misbehaved server because server should accept original_version + in the original connection. */ + if (conn->local.settings.original_version == + params->version_info.chosen_version) { + return NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE; + } + + /* Check version downgrade on incompatible version negotiation. */ + if (params->version_info.available_versionslen == 0) { + return NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE; + } + + if (conn->client_chosen_version != + select_preferred_version(conn->vneg.preferred_versions, + conn->vneg.preferred_versionslen, + params->version_info.chosen_version, + params->version_info.available_versions, + params->version_info.available_versionslen, + /* fallback_version = */ 0)) { + return NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE; + } + } + + return 0; +} + +uint32_t +ngtcp2_conn_server_negotiate_version(ngtcp2_conn *conn, + const ngtcp2_version_info *version_info) { + assert(conn->server); + assert(conn->client_chosen_version == version_info->chosen_version); + + return select_preferred_version( + conn->vneg.preferred_versions, conn->vneg.preferred_versionslen, + version_info->chosen_version, version_info->available_versions, + version_info->available_versionslen, version_info->chosen_version); +} + +int ngtcp2_conn_set_remote_transport_params( + ngtcp2_conn *conn, const ngtcp2_transport_params *params) { + int rv; + + /* We expect this function is called once per QUIC connection, but + GnuTLS server seems to call TLS extension callback twice if it + sends HelloRetryRequest. In practice, same QUIC transport + parameters are sent in the 2nd client flight, just returning 0 + would cause no harm. */ + if (conn->flags & NGTCP2_CONN_FLAG_TRANSPORT_PARAM_RECVED) { + return 0; + } + + if (!params->initial_scid_present) { + return NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM; + } + + /* Assume that ngtcp2_transport_params_decode sets default value if + active_connection_id_limit is omitted. */ + if (params->active_connection_id_limit < + NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT) { + return NGTCP2_ERR_TRANSPORT_PARAM; + } + + /* We assume that conn->dcid.current.cid is still the initial one. + This requires that transport parameter must be fed into + ngtcp2_conn as early as possible. */ + if (!ngtcp2_cid_eq(&conn->dcid.current.cid, ¶ms->initial_scid)) { + return NGTCP2_ERR_TRANSPORT_PARAM; + } + + if (params->max_udp_payload_size < NGTCP2_MAX_UDP_PAYLOAD_SIZE) { + return NGTCP2_ERR_TRANSPORT_PARAM; + } + + if (conn->server) { + if (params->original_dcid_present || + params->stateless_reset_token_present || + params->preferred_addr_present || params->retry_scid_present) { + return NGTCP2_ERR_TRANSPORT_PARAM; + } + + if (params->version_info_present) { + if (!vneg_available_versions_includes( + params->version_info.available_versions, + params->version_info.available_versionslen, + params->version_info.chosen_version)) { + return NGTCP2_ERR_TRANSPORT_PARAM; + } + + if (params->version_info.chosen_version != conn->client_chosen_version) { + return NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE; + } + + conn->negotiated_version = + ngtcp2_conn_server_negotiate_version(conn, ¶ms->version_info); + if (conn->negotiated_version != conn->client_chosen_version) { + rv = conn_call_version_negotiation(conn, conn->negotiated_version, + &conn->rcid); + if (rv != 0) { + return rv; + } + } + } else { + conn->negotiated_version = conn->client_chosen_version; + } + + 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); + } else { + rv = conn_client_validate_transport_params(conn, params); + if (rv != 0) { + return rv; + } + } + + ngtcp2_log_remote_tp(&conn->log, params); + + ngtcp2_qlog_parameters_set_transport_params(&conn->qlog, params, conn->server, + NGTCP2_QLOG_SIDE_REMOTE); + + if ((conn->server && conn->pktns.crypto.tx.ckm) || + (!conn->server && conn->pktns.crypto.rx.ckm)) { + ngtcp2_transport_params_del(conn->remote.transport_params, conn->mem); + conn->remote.transport_params = NULL; + + rv = ngtcp2_transport_params_copy_new(&conn->remote.transport_params, + params, conn->mem); + if (rv != 0) { + return rv; + } + conn_sync_stream_id_limit(conn); + conn->tx.max_offset = conn->remote.transport_params->initial_max_data; + } else { + assert(!conn->remote.pending_transport_params); + + rv = ngtcp2_transport_params_copy_new( + &conn->remote.pending_transport_params, params, conn->mem); + if (rv != 0) { + return rv; + } + } + + conn->flags |= NGTCP2_CONN_FLAG_TRANSPORT_PARAM_RECVED; + + return 0; +} + +int ngtcp2_conn_decode_and_set_remote_transport_params(ngtcp2_conn *conn, + const uint8_t *data, + size_t datalen) { + ngtcp2_transport_params params; + int rv; + + rv = ngtcp2_transport_params_decode(¶ms, data, datalen); + if (rv != 0) { + return rv; + } + + return ngtcp2_conn_set_remote_transport_params(conn, ¶ms); +} + +const ngtcp2_transport_params * +ngtcp2_conn_get_remote_transport_params(ngtcp2_conn *conn) { + if (conn->remote.pending_transport_params) { + return conn->remote.pending_transport_params; + } + + return conn->remote.transport_params; +} + +ngtcp2_ssize ngtcp2_conn_encode_0rtt_transport_params(ngtcp2_conn *conn, + uint8_t *dest, + size_t destlen) { + ngtcp2_transport_params params, *src; + + if (conn->server) { + src = &conn->local.transport_params; + } else { + assert(conn->remote.transport_params); + + src = conn->remote.transport_params; + } + + ngtcp2_transport_params_default(¶ms); + + params.initial_max_streams_bidi = src->initial_max_streams_bidi; + params.initial_max_streams_uni = src->initial_max_streams_uni; + params.initial_max_stream_data_bidi_local = + src->initial_max_stream_data_bidi_local; + params.initial_max_stream_data_bidi_remote = + src->initial_max_stream_data_bidi_remote; + params.initial_max_stream_data_uni = src->initial_max_stream_data_uni; + params.initial_max_data = src->initial_max_data; + params.active_connection_id_limit = src->active_connection_id_limit; + params.max_datagram_frame_size = src->max_datagram_frame_size; + + if (conn->server) { + params.max_idle_timeout = src->max_idle_timeout; + params.max_udp_payload_size = src->max_udp_payload_size; + params.disable_active_migration = src->disable_active_migration; + } + + return ngtcp2_transport_params_encode(dest, destlen, ¶ms); +} + +int ngtcp2_conn_decode_and_set_0rtt_transport_params(ngtcp2_conn *conn, + const uint8_t *data, + size_t datalen) { + ngtcp2_transport_params params; + int rv; + + rv = ngtcp2_transport_params_decode(¶ms, data, datalen); + if (rv != 0) { + return rv; + } + + return ngtcp2_conn_set_0rtt_remote_transport_params(conn, ¶ms); +} + +int ngtcp2_conn_set_0rtt_remote_transport_params( + ngtcp2_conn *conn, const ngtcp2_transport_params *params) { + ngtcp2_transport_params *p; + + assert(!conn->server); + assert(!conn->remote.transport_params); + + /* Assume that all pointer fields in p are NULL */ + p = ngtcp2_mem_calloc(conn->mem, 1, sizeof(*p)); + if (p == NULL) { + return NGTCP2_ERR_NOMEM; + } + + conn->remote.transport_params = p; + + ngtcp2_transport_params_default(conn->remote.transport_params); + + p->initial_max_streams_bidi = params->initial_max_streams_bidi; + p->initial_max_streams_uni = params->initial_max_streams_uni; + p->initial_max_stream_data_bidi_local = + params->initial_max_stream_data_bidi_local; + p->initial_max_stream_data_bidi_remote = + params->initial_max_stream_data_bidi_remote; + p->initial_max_stream_data_uni = params->initial_max_stream_data_uni; + p->initial_max_data = params->initial_max_data; + /* we might hit garbage, then set the sane default. */ + p->active_connection_id_limit = + ngtcp2_max(NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT, + params->active_connection_id_limit); + p->max_datagram_frame_size = params->max_datagram_frame_size; + + /* we might hit garbage, then set the sane default. */ + if (params->max_udp_payload_size) { + p->max_udp_payload_size = + ngtcp2_max(NGTCP2_MAX_UDP_PAYLOAD_SIZE, params->max_udp_payload_size); + } + + /* 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_sync_stream_id_limit(conn); + + conn->tx.max_offset = p->initial_max_data; + + ngtcp2_qlog_parameters_set_transport_params(&conn->qlog, p, conn->server, + NGTCP2_QLOG_SIDE_REMOTE); + + return 0; +} + +int ngtcp2_conn_set_local_transport_params_versioned( + ngtcp2_conn *conn, int transport_params_version, + const ngtcp2_transport_params *params) { + ngtcp2_transport_params paramsbuf; + + params = ngtcp2_transport_params_convert_to_latest( + ¶msbuf, transport_params_version, params); + + assert(conn->server); + assert(params->active_connection_id_limit >= + NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT); + assert(params->active_connection_id_limit <= NGTCP2_MAX_DCID_POOL_SIZE); + + if (conn->hs_pktns == NULL || conn->hs_pktns->crypto.tx.ckm) { + return NGTCP2_ERR_INVALID_STATE; + } + + conn_set_local_transport_params(conn, params); + + return 0; +} + +int ngtcp2_conn_commit_local_transport_params(ngtcp2_conn *conn) { + const ngtcp2_mem *mem = conn->mem; + ngtcp2_transport_params *params = &conn->local.transport_params; + ngtcp2_scid *scident; + int rv; + + assert(1 == ngtcp2_ksl_len(&conn->scid.set)); + + params->initial_scid = conn->oscid; + params->initial_scid_present = 1; + + if (conn->oscid.datalen == 0) { + params->preferred_addr_present = 0; + } + + if (conn->server && params->preferred_addr_present) { + scident = ngtcp2_mem_malloc(mem, sizeof(*scident)); + if (scident == NULL) { + return NGTCP2_ERR_NOMEM; + } + + ngtcp2_scid_init(scident, 1, ¶ms->preferred_addr.cid); + + rv = ngtcp2_ksl_insert(&conn->scid.set, NULL, &scident->cid, scident); + if (rv != 0) { + ngtcp2_mem_free(mem, scident); + return rv; + } + + conn->scid.last_seq = 1; + } + + conn->rx.window = conn->rx.unsent_max_offset = conn->rx.max_offset = + params->initial_max_data; + conn->remote.bidi.unsent_max_streams = params->initial_max_streams_bidi; + conn->remote.bidi.max_streams = params->initial_max_streams_bidi; + conn->remote.uni.unsent_max_streams = params->initial_max_streams_uni; + conn->remote.uni.max_streams = params->initial_max_streams_uni; + + conn->flags |= NGTCP2_CONN_FLAG_LOCAL_TRANSPORT_PARAMS_COMMITTED; + + ngtcp2_qlog_parameters_set_transport_params(&conn->qlog, params, conn->server, + NGTCP2_QLOG_SIDE_LOCAL); + + return 0; +} + +const ngtcp2_transport_params * +ngtcp2_conn_get_local_transport_params(ngtcp2_conn *conn) { + return &conn->local.transport_params; +} + +ngtcp2_ssize ngtcp2_conn_encode_local_transport_params(ngtcp2_conn *conn, + uint8_t *dest, + size_t destlen) { + return ngtcp2_transport_params_encode(dest, destlen, + &conn->local.transport_params); +} + +int ngtcp2_conn_open_bidi_stream(ngtcp2_conn *conn, int64_t *pstream_id, + void *stream_user_data) { + int rv; + ngtcp2_strm *strm; + + if (ngtcp2_conn_get_streams_bidi_left(conn) == 0) { + return NGTCP2_ERR_STREAM_ID_BLOCKED; + } + + strm = ngtcp2_objalloc_strm_get(&conn->strm_objalloc); + if (strm == NULL) { + return NGTCP2_ERR_NOMEM; + } + + rv = ngtcp2_conn_init_stream(conn, strm, conn->local.bidi.next_stream_id, + stream_user_data); + if (rv != 0) { + ngtcp2_objalloc_strm_release(&conn->strm_objalloc, strm); + return rv; + } + + *pstream_id = conn->local.bidi.next_stream_id; + conn->local.bidi.next_stream_id += 4; + + return 0; +} + +int ngtcp2_conn_open_uni_stream(ngtcp2_conn *conn, int64_t *pstream_id, + void *stream_user_data) { + int rv; + ngtcp2_strm *strm; + + if (ngtcp2_conn_get_streams_uni_left(conn) == 0) { + return NGTCP2_ERR_STREAM_ID_BLOCKED; + } + + strm = ngtcp2_objalloc_strm_get(&conn->strm_objalloc); + if (strm == NULL) { + return NGTCP2_ERR_NOMEM; + } + + rv = ngtcp2_conn_init_stream(conn, strm, conn->local.uni.next_stream_id, + stream_user_data); + if (rv != 0) { + ngtcp2_objalloc_strm_release(&conn->strm_objalloc, strm); + return rv; + } + ngtcp2_strm_shutdown(strm, NGTCP2_STRM_FLAG_SHUT_RD); + + *pstream_id = conn->local.uni.next_stream_id; + conn->local.uni.next_stream_id += 4; + + return 0; +} + +ngtcp2_strm *ngtcp2_conn_find_stream(ngtcp2_conn *conn, int64_t stream_id) { + return ngtcp2_map_find(&conn->strms, (uint64_t)stream_id); +} + +ngtcp2_ssize ngtcp2_conn_write_stream_versioned( + ngtcp2_conn *conn, ngtcp2_path *path, int pkt_info_version, + ngtcp2_pkt_info *pi, uint8_t *dest, size_t destlen, ngtcp2_ssize *pdatalen, + uint32_t flags, int64_t stream_id, const uint8_t *data, size_t datalen, + ngtcp2_tstamp ts) { + ngtcp2_vec datav; + + datav.len = datalen; + datav.base = (uint8_t *)data; + + return ngtcp2_conn_writev_stream_versioned(conn, path, pkt_info_version, pi, + dest, destlen, pdatalen, flags, + stream_id, &datav, 1, ts); +} + +static ngtcp2_ssize conn_write_vmsg_wrapper(ngtcp2_conn *conn, + ngtcp2_path *path, + int pkt_info_version, + ngtcp2_pkt_info *pi, uint8_t *dest, + size_t destlen, ngtcp2_vmsg *vmsg, + ngtcp2_tstamp ts) { + ngtcp2_conn_stat *cstat = &conn->cstat; + ngtcp2_ssize nwrite; + + nwrite = ngtcp2_conn_write_vmsg(conn, path, pkt_info_version, pi, dest, + destlen, vmsg, ts); + if (nwrite < 0) { + return nwrite; + } + + if (cstat->bytes_in_flight >= cstat->cwnd) { + conn->rst.is_cwnd_limited = 1; + } + + if (nwrite == 0 && cstat->bytes_in_flight < cstat->cwnd) { + 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; + } + } + + return nwrite; +} + +ngtcp2_ssize ngtcp2_conn_writev_stream_versioned( + ngtcp2_conn *conn, ngtcp2_path *path, int pkt_info_version, + ngtcp2_pkt_info *pi, uint8_t *dest, size_t destlen, ngtcp2_ssize *pdatalen, + uint32_t flags, int64_t stream_id, const ngtcp2_vec *datav, size_t datavcnt, + ngtcp2_tstamp ts) { + ngtcp2_vmsg vmsg, *pvmsg; + ngtcp2_strm *strm; + int64_t datalen; + + if (pdatalen) { + *pdatalen = -1; + } + + if (stream_id != -1) { + strm = ngtcp2_conn_find_stream(conn, stream_id); + if (strm == NULL) { + return NGTCP2_ERR_STREAM_NOT_FOUND; + } + + if (strm->flags & NGTCP2_STRM_FLAG_SHUT_WR) { + return NGTCP2_ERR_STREAM_SHUT_WR; + } + + datalen = ngtcp2_vec_len_varint(datav, datavcnt); + if (datalen == -1) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + if ((uint64_t)datalen > NGTCP2_MAX_VARINT - strm->tx.offset || + (uint64_t)datalen > NGTCP2_MAX_VARINT - conn->tx.offset) { + 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; + + pvmsg = &vmsg; + } else { + pvmsg = NULL; + } + + return conn_write_vmsg_wrapper(conn, path, pkt_info_version, pi, dest, + destlen, pvmsg, ts); +} + +ngtcp2_ssize ngtcp2_conn_write_datagram_versioned( + ngtcp2_conn *conn, ngtcp2_path *path, int pkt_info_version, + ngtcp2_pkt_info *pi, uint8_t *dest, size_t destlen, int *paccepted, + uint32_t flags, uint64_t dgram_id, const uint8_t *data, size_t datalen, + ngtcp2_tstamp ts) { + ngtcp2_vec datav; + + datav.len = datalen; + datav.base = (uint8_t *)data; + + return ngtcp2_conn_writev_datagram_versioned(conn, path, pkt_info_version, pi, + dest, destlen, paccepted, flags, + dgram_id, &datav, 1, ts); +} + +ngtcp2_ssize ngtcp2_conn_writev_datagram_versioned( + ngtcp2_conn *conn, ngtcp2_path *path, int pkt_info_version, + ngtcp2_pkt_info *pi, uint8_t *dest, size_t destlen, int *paccepted, + uint32_t flags, uint64_t dgram_id, const ngtcp2_vec *datav, size_t datavcnt, + ngtcp2_tstamp ts) { + ngtcp2_vmsg vmsg; + int64_t datalen; + + if (paccepted) { + *paccepted = 0; + } + + if (conn->remote.transport_params == NULL || + conn->remote.transport_params->max_datagram_frame_size == 0) { + return NGTCP2_ERR_INVALID_STATE; + } + + datalen = ngtcp2_vec_len_varint(datav, datavcnt); + if (datalen == -1 +#if UINT64_MAX > SIZE_MAX + || (uint64_t)datalen > SIZE_MAX +#endif /* UINT64_MAX > SIZE_MAX */ + ) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + if (conn->remote.transport_params->max_datagram_frame_size < + ngtcp2_pkt_datagram_framelen((size_t)datalen)) { + 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; + + return conn_write_vmsg_wrapper(conn, path, pkt_info_version, pi, dest, + destlen, &vmsg, ts); +} + +ngtcp2_ssize ngtcp2_conn_write_vmsg(ngtcp2_conn *conn, ngtcp2_path *path, + int pkt_info_version, ngtcp2_pkt_info *pi, + uint8_t *dest, size_t destlen, + ngtcp2_vmsg *vmsg, ngtcp2_tstamp ts) { + ngtcp2_ssize nwrite; + size_t origlen; + size_t origdestlen = destlen; + int rv; + uint8_t wflags = NGTCP2_WRITE_PKT_FLAG_NONE; + int ppe_pending = (conn->flags & NGTCP2_CONN_FLAG_PPE_PENDING) != 0; + ngtcp2_conn_stat *cstat = &conn->cstat; + ngtcp2_ssize res = 0; + uint64_t server_tx_left; + int64_t prev_in_pkt_num = -1; + ngtcp2_ksl_it it; + ngtcp2_rtb_entry *rtbent; + (void)pkt_info_version; + + conn_update_timestamp(conn, ts); + + if (path) { + ngtcp2_path_copy(path, &conn->dcid.current.ps.path); + } + + origlen = destlen = + conn_shape_udp_payload(conn, &conn->dcid.current, destlen); + + if (!ppe_pending && pi) { + pi->ecn = NGTCP2_ECN_NOT_ECT; + } + + switch (conn->state) { + case NGTCP2_CS_CLIENT_INITIAL: + case NGTCP2_CS_CLIENT_WAIT_HANDSHAKE: + if (!conn_pacing_pkt_tx_allowed(conn, ts)) { + assert(!ppe_pending); + + return conn_write_handshake_ack_pkts(conn, pi, dest, origlen, ts); + } + + nwrite = conn_client_write_handshake(conn, pi, dest, destlen, 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) { + 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 (ngtcp2_pkt_get_type_long(conn->negotiated_version, dest[0]) == + NGTCP2_PKT_INITIAL) { + /* We have added padding already, but in that case, there is no + space left to write 1RTT packet. */ + wflags |= NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING; + } + + res = nwrite; + dest += nwrite; + destlen -= (size_t)nwrite; + /* Break here so that we can coalesces 1RTT packet. */ + break; + case NGTCP2_CS_SERVER_INITIAL: + case NGTCP2_CS_SERVER_WAIT_HANDSHAKE: + if (!conn_pacing_pkt_tx_allowed(conn, ts)) { + assert(!ppe_pending); + + if (!(conn->dcid.current.flags & NGTCP2_DCID_FLAG_PATH_VALIDATED)) { + server_tx_left = conn_server_tx_left(conn, &conn->dcid.current); + if (server_tx_left == 0) { + return 0; + } + + origlen = (size_t)ngtcp2_min((uint64_t)origlen, server_tx_left); + } + + return conn_write_handshake_ack_pkts(conn, pi, dest, origlen, ts); + } + + if (!ppe_pending) { + if (!(conn->dcid.current.flags & NGTCP2_DCID_FLAG_PATH_VALIDATED)) { + server_tx_left = conn_server_tx_left(conn, &conn->dcid.current); + if (server_tx_left == 0) { + if (cstat->loss_detection_timer != UINT64_MAX) { + ngtcp2_log_info( + &conn->log, NGTCP2_LOG_EVENT_LDC, + "loss detection timer canceled due to amplification limit"); + cstat->loss_detection_timer = UINT64_MAX; + } + + return 0; + } + + destlen = (size_t)ngtcp2_min((uint64_t)destlen, server_tx_left); + } + + if (conn->in_pktns) { + it = ngtcp2_rtb_head(&conn->in_pktns->rtb); + if (!ngtcp2_ksl_it_end(&it)) { + rtbent = ngtcp2_ksl_it_get(&it); + prev_in_pkt_num = rtbent->hd.pkt_num; + } + } + + nwrite = conn_write_handshake(conn, pi, dest, destlen, + /* write_datalen = */ 0, ts); + if (nwrite < 0) { + return nwrite; + } + + res = nwrite; + dest += nwrite; + destlen -= (size_t)nwrite; + + 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); + if (rtbent->hd.pkt_num != prev_in_pkt_num && + (rtbent->flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING)) { + /* We have added padding already, but in that case, there + is no space left to write 1RTT packet. */ + wflags |= NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING; + } + } + } + } + if (conn->pktns.crypto.tx.ckm == NULL) { + return res; + } + break; + case NGTCP2_CS_POST_HANDSHAKE: + if (!conn_pacing_pkt_tx_allowed(conn, ts)) { + assert(!ppe_pending); + + if (conn->server && + !(conn->dcid.current.flags & NGTCP2_DCID_FLAG_PATH_VALIDATED)) { + server_tx_left = conn_server_tx_left(conn, &conn->dcid.current); + if (server_tx_left == 0) { + return 0; + } + + origlen = (size_t)ngtcp2_min((uint64_t)origlen, server_tx_left); + } + + return conn_write_ack_pkt(conn, pi, dest, origlen, NGTCP2_PKT_1RTT, ts); + } + + break; + case NGTCP2_CS_CLOSING: + return NGTCP2_ERR_CLOSING; + case NGTCP2_CS_DRAINING: + return NGTCP2_ERR_DRAINING; + default: + return 0; + } + + assert(conn->pktns.crypto.tx.ckm); + + if (conn_check_pkt_num_exhausted(conn)) { + return NGTCP2_ERR_PKT_NUM_EXHAUSTED; + } + + if (vmsg) { + switch (vmsg->type) { + case NGTCP2_VMSG_TYPE_STREAM: + if (vmsg->stream.flags & NGTCP2_WRITE_STREAM_FLAG_MORE) { + wflags |= NGTCP2_WRITE_PKT_FLAG_MORE; + } + break; + case NGTCP2_VMSG_TYPE_DATAGRAM: + if (vmsg->datagram.flags & NGTCP2_WRITE_DATAGRAM_FLAG_MORE) { + wflags |= NGTCP2_WRITE_PKT_FLAG_MORE; + } + break; + default: + break; + } + } + + if (ppe_pending) { + res = conn->pkt.hs_spktlen; + if (conn->pkt.require_padding) { + wflags |= NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING; + } + /* dest and destlen have already been adjusted in ppe in the first + run. They are adjusted for probe packet later. */ + nwrite = conn_write_pkt(conn, pi, dest, destlen, vmsg, NGTCP2_PKT_1RTT, + wflags, ts); + goto fin; + } else { + conn->pkt.require_padding = + (wflags & NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING); + + if (conn->state == NGTCP2_CS_POST_HANDSHAKE) { + rv = conn_prepare_key_update(conn, ts); + if (rv != 0) { + return rv; + } + } + + if (!conn->pktns.rtb.probe_pkt_left && conn_cwnd_is_zero(conn)) { + destlen = 0; + } else { + if (res == 0) { + nwrite = + conn_write_path_response(conn, path, pi, dest, origdestlen, ts); + if (nwrite) { + goto fin; + } + + if (conn->pv) { + nwrite = + conn_write_path_challenge(conn, path, pi, dest, origdestlen, ts); + if (nwrite) { + goto fin; + } + } + + if (conn->pmtud && + (conn->dcid.current.flags & NGTCP2_DCID_FLAG_PATH_VALIDATED) && + (!conn->hs_pktns || + ngtcp2_strm_streamfrq_empty(&conn->hs_pktns->crypto.strm))) { + nwrite = conn_write_pmtud_probe(conn, pi, dest, origdestlen, ts); + if (nwrite) { + goto fin; + } + } + } + } + + if (conn->server && + !(conn->dcid.current.flags & NGTCP2_DCID_FLAG_PATH_VALIDATED)) { + server_tx_left = conn_server_tx_left(conn, &conn->dcid.current); + origlen = (size_t)ngtcp2_min((uint64_t)origlen, server_tx_left); + destlen = (size_t)ngtcp2_min((uint64_t)destlen, server_tx_left); + + if (server_tx_left == 0 && + conn->cstat.loss_detection_timer != UINT64_MAX) { + ngtcp2_log_info( + &conn->log, NGTCP2_LOG_EVENT_LDC, + "loss detection timer canceled due to amplification limit"); + conn->cstat.loss_detection_timer = UINT64_MAX; + } + } + } + + if (res == 0) { + if (conn_handshake_remnants_left(conn)) { + if (conn_handshake_probe_left(conn) || + /* Allow exceeding CWND if an Handshake packet needs to be + sent in order to avoid dead lock. In some situation, + typically for client, 1 RTT packets may occupy in-flight + bytes (e.g., some large requests and PMTUD), and + Handshake packet loss shrinks CWND, and we may get in the + situation that we are unable to send Handshake packet. */ + (conn->hs_pktns->rtb.num_pto_eliciting == 0 && + !ngtcp2_strm_streamfrq_empty(&conn->hs_pktns->crypto.strm))) { + destlen = origlen; + } + nwrite = conn_write_handshake_pkts(conn, pi, dest, destlen, + /* write_datalen = */ 0, ts); + if (nwrite < 0) { + return nwrite; + } + if (nwrite > 0) { + res = nwrite; + dest += nwrite; + destlen -= (size_t)nwrite; + } else if (destlen == 0) { + res = conn_write_handshake_ack_pkts(conn, pi, dest, origlen, ts); + if (res) { + return res; + } + } + } + } + + 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); + + nwrite = conn_write_pkt(conn, pi, dest, destlen, vmsg, NGTCP2_PKT_1RTT, + wflags, ts); + + goto fin; + } + + nwrite = conn_write_pkt(conn, pi, dest, destlen, vmsg, NGTCP2_PKT_1RTT, + wflags, ts); + if (nwrite) { + assert(nwrite != NGTCP2_ERR_NOBUF); + goto fin; + } + + if (res == 0) { + nwrite = conn_write_ack_pkt(conn, pi, dest, origlen, NGTCP2_PKT_1RTT, ts); + } + +fin: + if (nwrite >= 0) { + res += nwrite; + return res; + } + + switch (nwrite) { + case NGTCP2_ERR_STREAM_DATA_BLOCKED: + if (!(wflags & NGTCP2_WRITE_PKT_FLAG_MORE)) { + if (res) { + return res; + } + + break; + } + /* fall through */ + case NGTCP2_ERR_WRITE_MORE: + conn->pkt.hs_spktlen = res; + break; + } + + return nwrite; +} + +static ngtcp2_ssize +conn_write_connection_close(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, + uint8_t *dest, size_t destlen, uint8_t pkt_type, + uint64_t error_code, const uint8_t *reason, + size_t reasonlen, ngtcp2_tstamp ts) { + ngtcp2_pktns *in_pktns = conn->in_pktns; + ngtcp2_pktns *hs_pktns = conn->hs_pktns; + ngtcp2_ssize res = 0, nwrite; + 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; + + if (!(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED) && + pkt_type != NGTCP2_PKT_INITIAL) { + if (in_pktns && conn->server) { + nwrite = ngtcp2_conn_write_single_frame_pkt( + conn, pi, dest, destlen, NGTCP2_PKT_INITIAL, + NGTCP2_WRITE_PKT_FLAG_NONE, &conn->dcid.current.cid, &fr, + NGTCP2_RTB_ENTRY_FLAG_NONE, NULL, ts); + if (nwrite < 0) { + return nwrite; + } + + dest += nwrite; + destlen -= (size_t)nwrite; + res += nwrite; + } + + if (pkt_type != NGTCP2_PKT_HANDSHAKE && hs_pktns && + hs_pktns->crypto.tx.ckm) { + nwrite = ngtcp2_conn_write_single_frame_pkt( + conn, pi, dest, destlen, NGTCP2_PKT_HANDSHAKE, + NGTCP2_WRITE_PKT_FLAG_NONE, &conn->dcid.current.cid, &fr, + NGTCP2_RTB_ENTRY_FLAG_NONE, NULL, ts); + if (nwrite < 0) { + return nwrite; + } + + dest += nwrite; + destlen -= (size_t)nwrite; + res += nwrite; + } + } + + if (!conn->server && pkt_type == NGTCP2_PKT_INITIAL) { + flags = NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING; + } + + nwrite = ngtcp2_conn_write_single_frame_pkt( + conn, pi, dest, destlen, pkt_type, flags, &conn->dcid.current.cid, &fr, + NGTCP2_RTB_ENTRY_FLAG_NONE, NULL, ts); + + if (nwrite < 0) { + return nwrite; + } + + res += nwrite; + + if (res == 0) { + return NGTCP2_ERR_NOBUF; + } + + return res; +} + +ngtcp2_ssize ngtcp2_conn_write_connection_close_pkt( + ngtcp2_conn *conn, ngtcp2_path *path, ngtcp2_pkt_info *pi, uint8_t *dest, + size_t destlen, uint64_t error_code, const uint8_t *reason, + size_t reasonlen, ngtcp2_tstamp ts) { + ngtcp2_pktns *in_pktns = conn->in_pktns; + ngtcp2_pktns *hs_pktns = conn->hs_pktns; + uint8_t pkt_type; + ngtcp2_ssize nwrite; + uint64_t server_tx_left; + + if (conn_check_pkt_num_exhausted(conn)) { + return NGTCP2_ERR_PKT_NUM_EXHAUSTED; + } + + switch (conn->state) { + case NGTCP2_CS_CLIENT_INITIAL: + return NGTCP2_ERR_INVALID_STATE; + case NGTCP2_CS_CLOSING: + case NGTCP2_CS_DRAINING: + return 0; + default: + break; + } + + if (path) { + ngtcp2_path_copy(path, &conn->dcid.current.ps.path); + } + + destlen = conn_shape_udp_payload(conn, &conn->dcid.current, destlen); + + if (pi) { + pi->ecn = NGTCP2_ECN_NOT_ECT; + } + + if (conn->server) { + server_tx_left = conn_server_tx_left(conn, &conn->dcid.current); + destlen = (size_t)ngtcp2_min((uint64_t)destlen, server_tx_left); + } + + if (conn->state == NGTCP2_CS_POST_HANDSHAKE || + (conn->server && conn->pktns.crypto.tx.ckm)) { + pkt_type = NGTCP2_PKT_1RTT; + } else if (hs_pktns && hs_pktns->crypto.tx.ckm) { + pkt_type = NGTCP2_PKT_HANDSHAKE; + } else if (in_pktns && in_pktns->crypto.tx.ckm) { + pkt_type = NGTCP2_PKT_INITIAL; + } else { + /* This branch is taken if server has not read any Initial packet + from client. */ + return NGTCP2_ERR_INVALID_STATE; + } + + nwrite = conn_write_connection_close(conn, pi, dest, destlen, pkt_type, + error_code, reason, reasonlen, ts); + if (nwrite < 0) { + return nwrite; + } + + conn->state = NGTCP2_CS_CLOSING; + + return nwrite; +} + +ngtcp2_ssize ngtcp2_conn_write_application_close_pkt( + ngtcp2_conn *conn, ngtcp2_path *path, ngtcp2_pkt_info *pi, uint8_t *dest, + size_t destlen, uint64_t app_error_code, const uint8_t *reason, + size_t reasonlen, ngtcp2_tstamp ts) { + ngtcp2_ssize nwrite; + ngtcp2_ssize res = 0; + ngtcp2_frame fr; + uint64_t server_tx_left; + + if (conn_check_pkt_num_exhausted(conn)) { + return NGTCP2_ERR_PKT_NUM_EXHAUSTED; + } + + switch (conn->state) { + case NGTCP2_CS_CLIENT_INITIAL: + return NGTCP2_ERR_INVALID_STATE; + case NGTCP2_CS_CLOSING: + case NGTCP2_CS_DRAINING: + return 0; + default: + break; + } + + if (path) { + ngtcp2_path_copy(path, &conn->dcid.current.ps.path); + } + + destlen = conn_shape_udp_payload(conn, &conn->dcid.current, destlen); + + if (pi) { + pi->ecn = NGTCP2_ECN_NOT_ECT; + } + + if (conn->server) { + server_tx_left = conn_server_tx_left(conn, &conn->dcid.current); + destlen = (size_t)ngtcp2_min((uint64_t)destlen, server_tx_left); + } + + if (!(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED)) { + nwrite = conn_write_connection_close(conn, pi, dest, destlen, + conn->hs_pktns->crypto.tx.ckm + ? NGTCP2_PKT_HANDSHAKE + : NGTCP2_PKT_INITIAL, + NGTCP2_APPLICATION_ERROR, NULL, 0, ts); + if (nwrite < 0) { + return nwrite; + } + res = nwrite; + dest += nwrite; + destlen -= (size_t)nwrite; + } + + if (conn->state != NGTCP2_CS_POST_HANDSHAKE) { + assert(res); + + if (!conn->server || !conn->pktns.crypto.tx.ckm) { + return res; + } + } + + 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; + + nwrite = ngtcp2_conn_write_single_frame_pkt( + conn, pi, dest, destlen, NGTCP2_PKT_1RTT, NGTCP2_WRITE_PKT_FLAG_NONE, + &conn->dcid.current.cid, &fr, NGTCP2_RTB_ENTRY_FLAG_NONE, NULL, ts); + + if (nwrite < 0) { + return nwrite; + } + + res += nwrite; + + if (res == 0) { + return NGTCP2_ERR_NOBUF; + } + + conn->state = NGTCP2_CS_CLOSING; + + return res; +} + +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; +} + +void ngtcp2_ccerr_default(ngtcp2_ccerr *ccerr) { + ccerr_init(ccerr, NGTCP2_CCERR_TYPE_TRANSPORT, NGTCP2_NO_ERROR, NULL, 0); +} + +void ngtcp2_ccerr_set_transport_error(ngtcp2_ccerr *ccerr, uint64_t error_code, + const uint8_t *reason, size_t reasonlen) { + ccerr_init(ccerr, NGTCP2_CCERR_TYPE_TRANSPORT, error_code, reason, reasonlen); +} + +void ngtcp2_ccerr_set_liberr(ngtcp2_ccerr *ccerr, int liberr, + const uint8_t *reason, size_t reasonlen) { + switch (liberr) { + case NGTCP2_ERR_RECV_VERSION_NEGOTIATION: + ccerr_init(ccerr, NGTCP2_CCERR_TYPE_VERSION_NEGOTIATION, NGTCP2_NO_ERROR, + reason, reasonlen); + + return; + case NGTCP2_ERR_IDLE_CLOSE: + ccerr_init(ccerr, NGTCP2_CCERR_TYPE_IDLE_CLOSE, NGTCP2_NO_ERROR, reason, + reasonlen); + + return; + }; + + ngtcp2_ccerr_set_transport_error( + ccerr, ngtcp2_err_infer_quic_transport_error_code(liberr), reason, + reasonlen); +} + +void ngtcp2_ccerr_set_tls_alert(ngtcp2_ccerr *ccerr, uint8_t tls_alert, + const uint8_t *reason, size_t reasonlen) { + ngtcp2_ccerr_set_transport_error(ccerr, NGTCP2_CRYPTO_ERROR | tls_alert, + reason, reasonlen); +} + +void ngtcp2_ccerr_set_application_error(ngtcp2_ccerr *ccerr, + uint64_t error_code, + const uint8_t *reason, + size_t reasonlen) { + ccerr_init(ccerr, NGTCP2_CCERR_TYPE_APPLICATION, error_code, reason, + reasonlen); +} + +ngtcp2_ssize ngtcp2_conn_write_connection_close_versioned( + ngtcp2_conn *conn, ngtcp2_path *path, int pkt_info_version, + ngtcp2_pkt_info *pi, uint8_t *dest, size_t destlen, + const ngtcp2_ccerr *ccerr, ngtcp2_tstamp ts) { + (void)pkt_info_version; + + conn_update_timestamp(conn, ts); + + switch (ccerr->type) { + case NGTCP2_CCERR_TYPE_TRANSPORT: + return ngtcp2_conn_write_connection_close_pkt( + conn, path, pi, dest, destlen, ccerr->error_code, ccerr->reason, + ccerr->reasonlen, ts); + case NGTCP2_CCERR_TYPE_APPLICATION: + return ngtcp2_conn_write_application_close_pkt( + conn, path, pi, dest, destlen, ccerr->error_code, ccerr->reason, + ccerr->reasonlen, ts); + default: + return 0; + } +} + +int ngtcp2_conn_in_closing_period(ngtcp2_conn *conn) { + return conn->state == NGTCP2_CS_CLOSING; +} + +int ngtcp2_conn_in_draining_period(ngtcp2_conn *conn) { + return conn->state == NGTCP2_CS_DRAINING; +} + +int ngtcp2_conn_close_stream(ngtcp2_conn *conn, ngtcp2_strm *strm) { + int rv; + + rv = conn_call_stream_close(conn, strm); + if (rv != 0) { + return rv; + } + + rv = ngtcp2_map_remove(&conn->strms, (ngtcp2_map_key_type)strm->stream_id); + if (rv != 0) { + assert(rv != NGTCP2_ERR_INVALID_ARGUMENT); + return rv; + } + + if (ngtcp2_strm_is_tx_queued(strm)) { + ngtcp2_pq_remove(&conn->tx.strmq, &strm->pe); + } + + ngtcp2_strm_free(strm); + ngtcp2_objalloc_strm_release(&conn->strm_objalloc, strm); + + return 0; +} + +int ngtcp2_conn_close_stream_if_shut_rdwr(ngtcp2_conn *conn, + ngtcp2_strm *strm) { + if ((strm->flags & NGTCP2_STRM_FLAG_SHUT_RDWR) == + NGTCP2_STRM_FLAG_SHUT_RDWR && + ((strm->flags & NGTCP2_STRM_FLAG_RESET_STREAM_RECVED) || + ngtcp2_strm_rx_offset(strm) == strm->rx.last_offset) && + (((strm->flags & NGTCP2_STRM_FLAG_RESET_STREAM) && + (strm->flags & NGTCP2_STRM_FLAG_RESET_STREAM_ACKED)) || + ngtcp2_strm_is_all_tx_data_fin_acked(strm))) { + return ngtcp2_conn_close_stream(conn, strm); + } + return 0; +} + +/* + * conn_shutdown_stream_write closes send stream with error code + * |app_error_code|. RESET_STREAM frame is scheduled. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +static int conn_shutdown_stream_write(ngtcp2_conn *conn, ngtcp2_strm *strm, + uint64_t app_error_code) { + ngtcp2_strm_set_app_error_code(strm, app_error_code); + + if ((strm->flags & NGTCP2_STRM_FLAG_RESET_STREAM) || + ngtcp2_strm_is_all_tx_data_fin_acked(strm)) { + return 0; + } + + /* Set this flag so that we don't accidentally send DATA to this + stream. */ + strm->flags |= NGTCP2_STRM_FLAG_SHUT_WR | NGTCP2_STRM_FLAG_RESET_STREAM; + + ngtcp2_strm_streamfrq_clear(strm); + + return conn_reset_stream(conn, strm, app_error_code); +} + +/* + * conn_shutdown_stream_read closes read stream with error code + * |app_error_code|. STOP_SENDING frame is scheduled. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +static int conn_shutdown_stream_read(ngtcp2_conn *conn, ngtcp2_strm *strm, + uint64_t app_error_code) { + ngtcp2_strm_set_app_error_code(strm, app_error_code); + + if (strm->flags & + (NGTCP2_STRM_FLAG_STOP_SENDING | NGTCP2_STRM_FLAG_RESET_STREAM_RECVED)) { + return 0; + } + if ((strm->flags & NGTCP2_STRM_FLAG_SHUT_RD) && + ngtcp2_strm_rx_offset(strm) == strm->rx.last_offset) { + return 0; + } + + /* Extend connection flow control window for the amount of data + which are not passed to application. */ + if (!(strm->flags & (NGTCP2_STRM_FLAG_STOP_SENDING | + NGTCP2_STRM_FLAG_RESET_STREAM_RECVED))) { + ngtcp2_conn_extend_max_offset(conn, strm->rx.last_offset - + ngtcp2_strm_rx_offset(strm)); + } + + strm->flags |= NGTCP2_STRM_FLAG_STOP_SENDING; + + return conn_stop_sending(conn, strm, app_error_code); +} + +int ngtcp2_conn_shutdown_stream(ngtcp2_conn *conn, uint32_t flags, + int64_t stream_id, uint64_t app_error_code) { + int rv; + ngtcp2_strm *strm; + (void)flags; + + strm = ngtcp2_conn_find_stream(conn, stream_id); + if (strm == NULL) { + return 0; + } + + if (bidi_stream(stream_id) || !conn_local_stream(conn, stream_id)) { + rv = conn_shutdown_stream_read(conn, strm, app_error_code); + if (rv != 0) { + return rv; + } + } + + if (bidi_stream(stream_id) || conn_local_stream(conn, stream_id)) { + rv = conn_shutdown_stream_write(conn, strm, app_error_code); + if (rv != 0) { + return rv; + } + } + + return 0; +} + +int ngtcp2_conn_shutdown_stream_write(ngtcp2_conn *conn, uint32_t flags, + int64_t stream_id, + uint64_t app_error_code) { + ngtcp2_strm *strm; + (void)flags; + + if (!bidi_stream(stream_id) && !conn_local_stream(conn, stream_id)) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + strm = ngtcp2_conn_find_stream(conn, stream_id); + if (strm == NULL) { + return 0; + } + + return conn_shutdown_stream_write(conn, strm, app_error_code); +} + +int ngtcp2_conn_shutdown_stream_read(ngtcp2_conn *conn, uint32_t flags, + int64_t stream_id, + uint64_t app_error_code) { + ngtcp2_strm *strm; + (void)flags; + + if (!bidi_stream(stream_id) && conn_local_stream(conn, stream_id)) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + strm = ngtcp2_conn_find_stream(conn, stream_id); + if (strm == NULL) { + return 0; + } + + return conn_shutdown_stream_read(conn, strm, app_error_code); +} + +/* + * conn_extend_max_stream_offset extends stream level flow control + * window by |datalen| of the stream denoted by |strm|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +static int conn_extend_max_stream_offset(ngtcp2_conn *conn, ngtcp2_strm *strm, + uint64_t datalen) { + ngtcp2_strm *top; + + if (datalen > NGTCP2_MAX_VARINT || + strm->rx.unsent_max_offset > NGTCP2_MAX_VARINT - datalen) { + strm->rx.unsent_max_offset = NGTCP2_MAX_VARINT; + } else { + strm->rx.unsent_max_offset += datalen; + } + + if (!(strm->flags & + (NGTCP2_STRM_FLAG_SHUT_RD | NGTCP2_STRM_FLAG_STOP_SENDING)) && + !ngtcp2_strm_is_tx_queued(strm) && + conn_should_send_max_stream_data(conn, strm)) { + if (!ngtcp2_pq_empty(&conn->tx.strmq)) { + top = ngtcp2_conn_tx_strmq_top(conn); + strm->cycle = top->cycle; + } + strm->cycle = conn_tx_strmq_first_cycle(conn); + return ngtcp2_conn_tx_strmq_push(conn, strm); + } + + return 0; +} + +int ngtcp2_conn_extend_max_stream_offset(ngtcp2_conn *conn, int64_t stream_id, + uint64_t datalen) { + ngtcp2_strm *strm; + + if (!bidi_stream(stream_id) && conn_local_stream(conn, stream_id)) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + strm = ngtcp2_conn_find_stream(conn, stream_id); + if (strm == NULL) { + return 0; + } + + return conn_extend_max_stream_offset(conn, strm, datalen); +} + +void ngtcp2_conn_extend_max_offset(ngtcp2_conn *conn, uint64_t datalen) { + if (NGTCP2_MAX_VARINT < datalen || + conn->rx.unsent_max_offset > NGTCP2_MAX_VARINT - datalen) { + conn->rx.unsent_max_offset = NGTCP2_MAX_VARINT; + return; + } + + conn->rx.unsent_max_offset += datalen; +} + +void ngtcp2_conn_extend_max_streams_bidi(ngtcp2_conn *conn, size_t n) { + handle_max_remote_streams_extension(&conn->remote.bidi.unsent_max_streams, n); +} + +void ngtcp2_conn_extend_max_streams_uni(ngtcp2_conn *conn, size_t n) { + handle_max_remote_streams_extension(&conn->remote.uni.unsent_max_streams, n); +} + +const ngtcp2_cid *ngtcp2_conn_get_dcid(ngtcp2_conn *conn) { + return &conn->dcid.current.cid; +} + +const ngtcp2_cid *ngtcp2_conn_get_client_initial_dcid(ngtcp2_conn *conn) { + return &conn->rcid; +} + +uint32_t ngtcp2_conn_get_client_chosen_version(ngtcp2_conn *conn) { + return conn->client_chosen_version; +} + +uint32_t ngtcp2_conn_get_negotiated_version(ngtcp2_conn *conn) { + return conn->negotiated_version; +} + +static int delete_strms_pq_each(void *data, void *ptr) { + ngtcp2_conn *conn = ptr; + ngtcp2_strm *s = data; + + if (ngtcp2_strm_is_tx_queued(s)) { + ngtcp2_pq_remove(&conn->tx.strmq, &s->pe); + } + + ngtcp2_strm_free(s); + ngtcp2_objalloc_strm_release(&conn->strm_objalloc, s); + + return 0; +} + +/* + * conn_discard_early_data_state discards any connection states which + * are altered by any operations during early data transfer. + */ +static void conn_discard_early_data_state(ngtcp2_conn *conn) { + ngtcp2_frame_chain **pfrc, *frc; + + ngtcp2_rtb_remove_early_data(&conn->pktns.rtb, &conn->cstat); + + ngtcp2_map_each_free(&conn->strms, delete_strms_pq_each, conn); + ngtcp2_map_clear(&conn->strms); + + conn->tx.offset = 0; + conn->tx.last_blocked_offset = UINT64_MAX; + + conn->rx.unsent_max_offset = conn->rx.max_offset = + conn->local.transport_params.initial_max_data; + + conn->remote.bidi.unsent_max_streams = conn->remote.bidi.max_streams = + conn->local.transport_params.initial_max_streams_bidi; + + conn->remote.uni.unsent_max_streams = conn->remote.uni.max_streams = + conn->local.transport_params.initial_max_streams_uni; + + if (conn->server) { + conn->local.bidi.next_stream_id = 1; + conn->local.uni.next_stream_id = 3; + } else { + conn->local.bidi.next_stream_id = 0; + conn->local.uni.next_stream_id = 2; + } + + for (pfrc = &conn->pktns.tx.frq; *pfrc;) { + frc = *pfrc; + *pfrc = (*pfrc)->next; + ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem); + } +} + +int ngtcp2_conn_tls_early_data_rejected(ngtcp2_conn *conn) { + if (conn->flags & NGTCP2_CONN_FLAG_EARLY_DATA_REJECTED) { + return 0; + } + + conn->flags |= NGTCP2_CONN_FLAG_EARLY_DATA_REJECTED; + + conn_discard_early_data_state(conn); + + if (conn->callbacks.tls_early_data_rejected) { + return conn->callbacks.tls_early_data_rejected(conn, conn->user_data); + } + + if (conn->early.ckm) { + conn_discard_early_key(conn); + } + + return 0; +} + +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) { + ngtcp2_conn_stat *cstat = &conn->cstat; + + if (cstat->min_rtt == UINT64_MAX) { + cstat->latest_rtt = rtt; + cstat->min_rtt = rtt; + cstat->smoothed_rtt = rtt; + cstat->rttvar = rtt / 2; + cstat->first_rtt_sample_ts = ts; + } else { + if (conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED) { + assert(conn->remote.transport_params); + + ack_delay = + ngtcp2_min(ack_delay, conn->remote.transport_params->max_ack_delay); + } else if (ack_delay > 0 && rtt >= cstat->min_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( + &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; + } + + cstat->latest_rtt = rtt; + cstat->min_rtt = ngtcp2_min(cstat->min_rtt, rtt); + + if (rtt >= cstat->min_rtt + ack_delay) { + rtt -= ack_delay; + } + + cstat->rttvar = (cstat->rttvar * 3 + (cstat->smoothed_rtt < rtt + ? rtt - cstat->smoothed_rtt + : cstat->smoothed_rtt - rtt)) / + 4; + cstat->smoothed_rtt = (cstat->smoothed_rtt * 7 + rtt) / 8; + } + + ngtcp2_log_info( + &conn->log, NGTCP2_LOG_EVENT_LDC, + "latest_rtt=%" PRIu64 " min_rtt=%" PRIu64 " smoothed_rtt=%" PRIu64 + " rttvar=%" PRIu64 " ack_delay=%" PRIu64, + cstat->latest_rtt / NGTCP2_MILLISECONDS, + 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; +} + +static void conn_get_loss_time_and_pktns(ngtcp2_conn *conn, + ngtcp2_tstamp *ploss_time, + ngtcp2_pktns **ppktns) { + ngtcp2_pktns *const ns[] = {conn->hs_pktns, &conn->pktns}; + ngtcp2_conn_stat *cstat = &conn->cstat; + ngtcp2_duration *loss_time = cstat->loss_time + 1; + ngtcp2_tstamp earliest_loss_time = cstat->loss_time[NGTCP2_PKTNS_ID_INITIAL]; + ngtcp2_pktns *pktns = conn->in_pktns; + size_t i; + + for (i = 0; i < ngtcp2_arraylen(ns); ++i) { + if (ns[i] == NULL || loss_time[i] >= earliest_loss_time) { + continue; + } + + earliest_loss_time = loss_time[i]; + pktns = ns[i]; + } + + if (ploss_time) { + *ploss_time = earliest_loss_time; + } + if (ppktns) { + *ppktns = pktns; + } +} + +static ngtcp2_tstamp conn_get_earliest_pto_expiry(ngtcp2_conn *conn, + ngtcp2_tstamp ts) { + ngtcp2_pktns *ns[] = {conn->in_pktns, conn->hs_pktns, &conn->pktns}; + size_t i; + ngtcp2_tstamp earliest_ts = UINT64_MAX, t; + ngtcp2_conn_stat *cstat = &conn->cstat; + ngtcp2_tstamp *times = cstat->last_tx_pkt_ts; + ngtcp2_duration duration = + compute_pto(cstat->smoothed_rtt, cstat->rttvar, /* max_ack_delay = */ 0) * + (1ULL << cstat->pto_count); + + for (i = NGTCP2_PKTNS_ID_INITIAL; i < NGTCP2_PKTNS_ID_MAX; ++i) { + if (ns[i] == NULL || ns[i]->rtb.num_pto_eliciting == 0 || + (times[i] == UINT64_MAX || + (i == NGTCP2_PKTNS_ID_APPLICATION && + !(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED)))) { + continue; + } + + t = times[i] + duration; + + if (i == NGTCP2_PKTNS_ID_APPLICATION) { + assert(conn->remote.transport_params); + t += conn->remote.transport_params->max_ack_delay * + (1ULL << cstat->pto_count); + } + + if (t < earliest_ts) { + earliest_ts = t; + } + } + + if (earliest_ts == UINT64_MAX) { + return ts + duration; + } + + return earliest_ts; +} + +void ngtcp2_conn_set_loss_detection_timer(ngtcp2_conn *conn, ngtcp2_tstamp ts) { + ngtcp2_conn_stat *cstat = &conn->cstat; + ngtcp2_duration timeout; + ngtcp2_pktns *in_pktns = conn->in_pktns; + ngtcp2_pktns *hs_pktns = conn->hs_pktns; + ngtcp2_pktns *pktns = &conn->pktns; + ngtcp2_tstamp earliest_loss_time; + + conn_get_loss_time_and_pktns(conn, &earliest_loss_time, NULL); + + 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); + return; + } + + if ((!in_pktns || in_pktns->rtb.num_pto_eliciting == 0) && + (!hs_pktns || hs_pktns->rtb.num_pto_eliciting == 0) && + (pktns->rtb.num_pto_eliciting == 0 || + !(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED)) && + (conn->server || + (conn->flags & (NGTCP2_CONN_FLAG_SERVER_ADDR_VERIFIED | + NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED)))) { + if (cstat->loss_detection_timer != UINT64_MAX) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_LDC, + "loss detection timer canceled"); + cstat->loss_detection_timer = UINT64_MAX; + cstat->pto_count = 0; + } + return; + } + + cstat->loss_detection_timer = conn_get_earliest_pto_expiry(conn, 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); +} + +int ngtcp2_conn_on_loss_detection_timer(ngtcp2_conn *conn, ngtcp2_tstamp ts) { + ngtcp2_conn_stat *cstat = &conn->cstat; + int rv; + ngtcp2_pktns *in_pktns = conn->in_pktns; + ngtcp2_pktns *hs_pktns = conn->hs_pktns; + ngtcp2_tstamp earliest_loss_time; + ngtcp2_pktns *loss_pktns = NULL; + + switch (conn->state) { + case NGTCP2_CS_CLOSING: + case NGTCP2_CS_DRAINING: + cstat->loss_detection_timer = UINT64_MAX; + cstat->pto_count = 0; + return 0; + default: + break; + } + + if (cstat->loss_detection_timer == UINT64_MAX) { + return 0; + } + + conn_get_loss_time_and_pktns(conn, &earliest_loss_time, &loss_pktns); + + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_LDC, + "loss detection timer fired"); + + if (earliest_loss_time != UINT64_MAX) { + assert(loss_pktns); + + rv = ngtcp2_conn_detect_lost_pkt(conn, loss_pktns, cstat, ts); + if (rv != 0) { + return rv; + } + ngtcp2_conn_set_loss_detection_timer(conn, ts); + return 0; + } + + if (!conn->server && !conn_is_tls_handshake_completed(conn)) { + if (hs_pktns->crypto.tx.ckm) { + hs_pktns->rtb.probe_pkt_left = 1; + } else { + in_pktns->rtb.probe_pkt_left = 1; + } + } else { + if (in_pktns && in_pktns->rtb.num_pto_eliciting) { + in_pktns->rtb.probe_pkt_left = 1; + + assert(hs_pktns); + + if (conn->server && hs_pktns->rtb.num_pto_eliciting) { + /* let server coalesce packets */ + hs_pktns->rtb.probe_pkt_left = 1; + } + } else if (hs_pktns && hs_pktns->rtb.num_pto_eliciting) { + hs_pktns->rtb.probe_pkt_left = 2; + } else { + conn->pktns.rtb.probe_pkt_left = 2; + } + } + + ++cstat->pto_count; + + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_LDC, "pto_count=%zu", + cstat->pto_count); + + ngtcp2_conn_set_loss_detection_timer(conn, ts); + + return 0; +} + +static int conn_buffer_crypto_data(ngtcp2_conn *conn, const uint8_t **pdata, + ngtcp2_pktns *pktns, const uint8_t *data, + size_t datalen) { + int rv; + ngtcp2_buf_chain **pbufchain = &pktns->crypto.tx.data; + + if (*pbufchain) { + for (; (*pbufchain)->next; pbufchain = &(*pbufchain)->next) + ; + + if (ngtcp2_buf_left(&(*pbufchain)->buf) < datalen) { + pbufchain = &(*pbufchain)->next; + } + } + + if (!*pbufchain) { + rv = ngtcp2_buf_chain_new(pbufchain, ngtcp2_max(1024, datalen), conn->mem); + if (rv != 0) { + return rv; + } + } + + *pdata = (*pbufchain)->buf.last; + (*pbufchain)->buf.last = ngtcp2_cpymem((*pbufchain)->buf.last, data, datalen); + + return 0; +} + +int ngtcp2_conn_submit_crypto_data(ngtcp2_conn *conn, + ngtcp2_encryption_level encryption_level, + const uint8_t *data, const size_t datalen) { + ngtcp2_pktns *pktns; + ngtcp2_frame_chain *frc; + ngtcp2_stream *fr; + int rv; + + if (datalen == 0) { + return 0; + } + + switch (encryption_level) { + case NGTCP2_ENCRYPTION_LEVEL_INITIAL: + assert(conn->in_pktns); + pktns = conn->in_pktns; + break; + case NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE: + assert(conn->hs_pktns); + pktns = conn->hs_pktns; + break; + case NGTCP2_ENCRYPTION_LEVEL_1RTT: + pktns = &conn->pktns; + break; + default: + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + rv = conn_buffer_crypto_data(conn, &data, pktns, data, datalen); + if (rv != 0) { + return rv; + } + + rv = ngtcp2_frame_chain_objalloc_new(&frc, &conn->frc_objalloc); + 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; + + rv = ngtcp2_strm_streamfrq_push(&pktns->crypto.strm, frc); + if (rv != 0) { + ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem); + return rv; + } + + pktns->crypto.strm.tx.offset += datalen; + pktns->crypto.tx.offset += datalen; + + return 0; +} + +int ngtcp2_conn_submit_new_token(ngtcp2_conn *conn, const uint8_t *token, + size_t tokenlen) { + int rv; + ngtcp2_frame_chain *nfrc; + + assert(conn->server); + assert(token); + assert(tokenlen); + + rv = ngtcp2_frame_chain_new_token_objalloc_new( + &nfrc, token, tokenlen, &conn->frc_objalloc, conn->mem); + if (rv != 0) { + return rv; + } + + nfrc->next = conn->pktns.tx.frq; + conn->pktns.tx.frq = nfrc; + + return 0; +} + +ngtcp2_strm *ngtcp2_conn_tx_strmq_top(ngtcp2_conn *conn) { + assert(!ngtcp2_pq_empty(&conn->tx.strmq)); + return ngtcp2_struct_of(ngtcp2_pq_top(&conn->tx.strmq), ngtcp2_strm, pe); +} + +void ngtcp2_conn_tx_strmq_pop(ngtcp2_conn *conn) { + ngtcp2_strm *strm = ngtcp2_conn_tx_strmq_top(conn); + assert(strm); + ngtcp2_pq_pop(&conn->tx.strmq); + strm->pe.index = NGTCP2_PQ_BAD_INDEX; +} + +int ngtcp2_conn_tx_strmq_push(ngtcp2_conn *conn, ngtcp2_strm *strm) { + return ngtcp2_pq_push(&conn->tx.strmq, &strm->pe); +} + +static int conn_has_uncommitted_preferred_addr_cid(ngtcp2_conn *conn) { + return conn->server && + !(conn->flags & NGTCP2_CONN_FLAG_LOCAL_TRANSPORT_PARAMS_COMMITTED) && + conn->oscid.datalen && + conn->local.transport_params.preferred_addr_present; +} + +size_t ngtcp2_conn_get_scid(ngtcp2_conn *conn, ngtcp2_cid *dest) { + ngtcp2_cid *origdest = dest; + ngtcp2_ksl_it it; + ngtcp2_scid *scid; + + if (dest == NULL) { + return ngtcp2_ksl_len(&conn->scid.set) + + (size_t)conn_has_uncommitted_preferred_addr_cid(conn); + } + + for (it = ngtcp2_ksl_begin(&conn->scid.set); !ngtcp2_ksl_it_end(&it); + ngtcp2_ksl_it_next(&it)) { + scid = ngtcp2_ksl_it_get(&it); + *dest++ = scid->cid; + } + + if (conn_has_uncommitted_preferred_addr_cid(conn)) { + *dest++ = conn->local.transport_params.preferred_addr.cid; + } + + return (size_t)(dest - origdest); +} + +static size_t conn_get_num_active_dcid(ngtcp2_conn *conn) { + size_t n = 1; /* for conn->dcid.current */ + ngtcp2_pv *pv = conn->pv; + + if (pv) { + if (pv->dcid.seq != conn->dcid.current.seq) { + ++n; + } + if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) && + pv->fallback_dcid.seq != conn->dcid.current.seq && + pv->fallback_dcid.seq != pv->dcid.seq) { + ++n; + } + } + + n += ngtcp2_ringbuf_len(&conn->dcid.retired.rb); + + return n; +} + +static void copy_dcid_to_cid_token(ngtcp2_cid_token *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); + } +} + +size_t ngtcp2_conn_get_active_dcid(ngtcp2_conn *conn, ngtcp2_cid_token *dest) { + ngtcp2_pv *pv = conn->pv; + ngtcp2_cid_token *orig = dest; + ngtcp2_dcid *dcid; + size_t len, i; + + if (!(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_COMPLETED)) { + return 0; + } + + if (dest == NULL) { + return conn_get_num_active_dcid(conn); + } + + copy_dcid_to_cid_token(dest, &conn->dcid.current); + ++dest; + + if (pv) { + if (pv->dcid.seq != conn->dcid.current.seq) { + copy_dcid_to_cid_token(dest, &pv->dcid); + ++dest; + } + if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) && + pv->fallback_dcid.seq != conn->dcid.current.seq && + pv->fallback_dcid.seq != pv->dcid.seq) { + copy_dcid_to_cid_token(dest, &pv->fallback_dcid); + ++dest; + } + } + + len = ngtcp2_ringbuf_len(&conn->dcid.retired.rb); + for (i = 0; i < len; ++i) { + dcid = ngtcp2_ringbuf_get(&conn->dcid.retired.rb, i); + copy_dcid_to_cid_token(dest, dcid); + ++dest; + } + + return (size_t)(dest - orig); +} + +void ngtcp2_conn_set_local_addr(ngtcp2_conn *conn, const ngtcp2_addr *addr) { + ngtcp2_addr *dest = &conn->dcid.current.ps.path.local; + + assert(addr->addrlen <= + (ngtcp2_socklen)sizeof(conn->dcid.current.ps.local_addrbuf)); + ngtcp2_addr_copy(dest, addr); +} + +void ngtcp2_conn_set_path_user_data(ngtcp2_conn *conn, void *path_user_data) { + conn->dcid.current.ps.path.user_data = path_user_data; +} + +const ngtcp2_path *ngtcp2_conn_get_path(ngtcp2_conn *conn) { + return &conn->dcid.current.ps.path; +} + +size_t ngtcp2_conn_get_max_tx_udp_payload_size(ngtcp2_conn *conn) { + return conn->local.settings.max_tx_udp_payload_size; +} + +size_t ngtcp2_conn_get_path_max_tx_udp_payload_size(ngtcp2_conn *conn) { + if (conn->local.settings.no_tx_udp_payload_size_shaping) { + return ngtcp2_conn_get_max_tx_udp_payload_size(conn); + } + + return conn->dcid.current.max_udp_payload_size; +} + +static int conn_initiate_migration_precheck(ngtcp2_conn *conn, + const ngtcp2_addr *local_addr) { + if (!(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED) || + conn->remote.transport_params->disable_active_migration || + conn->dcid.current.cid.datalen == 0 || + (conn->pv && (conn->pv->flags & NGTCP2_PV_FLAG_PREFERRED_ADDR))) { + return NGTCP2_ERR_INVALID_STATE; + } + + if (ngtcp2_ringbuf_len(&conn->dcid.unused.rb) == 0) { + return NGTCP2_ERR_CONN_ID_BLOCKED; + } + + if (ngtcp2_addr_eq(&conn->dcid.current.ps.path.local, local_addr)) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + return 0; +} + +int ngtcp2_conn_initiate_immediate_migration(ngtcp2_conn *conn, + const ngtcp2_path *path, + ngtcp2_tstamp ts) { + int rv; + ngtcp2_dcid *dcid; + ngtcp2_pv *pv; + + assert(!conn->server); + + conn_update_timestamp(conn, ts); + + rv = conn_initiate_migration_precheck(conn, &path->local); + if (rv != 0) { + return rv; + } + + ngtcp2_conn_stop_pmtud(conn); + + if (conn->pv) { + rv = conn_abort_pv(conn, ts); + if (rv != 0) { + return rv; + } + } + + rv = conn_retire_dcid(conn, &conn->dcid.current, ts); + if (rv != 0) { + return rv; + } + + dcid = ngtcp2_ringbuf_get(&conn->dcid.unused.rb, 0); + ngtcp2_dcid_set_path(dcid, path); + + ngtcp2_dcid_copy(&conn->dcid.current, dcid); + ngtcp2_ringbuf_pop_front(&conn->dcid.unused.rb); + + conn_reset_congestion_state(conn, ts); + conn_reset_ecn_validation_state(conn); + + /* TODO It might be better to add a new flag which indicates that a + connection should be closed if this path validation failed. The + current design allows an application to continue, by migrating + into yet another path. */ + rv = ngtcp2_pv_new(&pv, dcid, conn_compute_pv_timeout(conn), + NGTCP2_PV_FLAG_NONE, &conn->log, conn->mem); + if (rv != 0) { + return rv; + } + + conn->pv = pv; + + return conn_call_activate_dcid(conn, &conn->dcid.current); +} + +int ngtcp2_conn_initiate_migration(ngtcp2_conn *conn, const ngtcp2_path *path, + ngtcp2_tstamp ts) { + int rv; + ngtcp2_dcid *dcid; + ngtcp2_pv *pv; + + assert(!conn->server); + + conn_update_timestamp(conn, ts); + + rv = conn_initiate_migration_precheck(conn, &path->local); + if (rv != 0) { + return rv; + } + + if (conn->pv) { + rv = conn_abort_pv(conn, ts); + if (rv != 0) { + return rv; + } + } + + dcid = ngtcp2_ringbuf_get(&conn->dcid.unused.rb, 0); + ngtcp2_dcid_set_path(dcid, path); + + rv = ngtcp2_pv_new(&pv, dcid, conn_compute_pv_timeout(conn), + NGTCP2_PV_FLAG_NONE, &conn->log, conn->mem); + if (rv != 0) { + return rv; + } + + ngtcp2_ringbuf_pop_front(&conn->dcid.unused.rb); + conn->pv = pv; + + return conn_call_activate_dcid(conn, &pv->dcid); +} + +uint64_t ngtcp2_conn_get_max_data_left(ngtcp2_conn *conn) { + return conn->tx.max_offset - conn->tx.offset; +} + +uint64_t ngtcp2_conn_get_max_stream_data_left(ngtcp2_conn *conn, + int64_t stream_id) { + ngtcp2_strm *strm = ngtcp2_conn_find_stream(conn, stream_id); + + if (strm == NULL) { + return 0; + } + + return strm->tx.max_offset - strm->tx.offset; +} + +uint64_t ngtcp2_conn_get_streams_bidi_left(ngtcp2_conn *conn) { + uint64_t n = ngtcp2_ord_stream_id(conn->local.bidi.next_stream_id); + + return n > conn->local.bidi.max_streams + ? 0 + : conn->local.bidi.max_streams - n + 1; +} + +uint64_t ngtcp2_conn_get_streams_uni_left(ngtcp2_conn *conn) { + uint64_t n = ngtcp2_ord_stream_id(conn->local.uni.next_stream_id); + + return n > conn->local.uni.max_streams ? 0 + : conn->local.uni.max_streams - n + 1; +} + +uint64_t ngtcp2_conn_get_cwnd_left(ngtcp2_conn *conn) { + uint64_t bytes_in_flight = conn->cstat.bytes_in_flight; + uint64_t cwnd = conn_get_cwnd(conn); + + if (cwnd > bytes_in_flight) { + return cwnd - bytes_in_flight; + } + + return 0; +} + +ngtcp2_tstamp ngtcp2_conn_get_idle_expiry(ngtcp2_conn *conn) { + ngtcp2_duration trpto; + ngtcp2_duration idle_timeout; + + /* TODO Remote max_idle_timeout becomes effective after handshake + completion. */ + + if (!conn_is_tls_handshake_completed(conn) || + conn->remote.transport_params->max_idle_timeout == 0 || + (conn->local.transport_params.max_idle_timeout && + conn->local.transport_params.max_idle_timeout < + conn->remote.transport_params->max_idle_timeout)) { + idle_timeout = conn->local.transport_params.max_idle_timeout; + } else { + idle_timeout = conn->remote.transport_params->max_idle_timeout; + } + + if (idle_timeout == 0) { + return UINT64_MAX; + } + + trpto = 3 * conn_compute_pto(conn, conn_is_tls_handshake_completed(conn) + ? &conn->pktns + : conn->hs_pktns); + + idle_timeout = ngtcp2_max(idle_timeout, trpto); + + if (conn->idle_ts >= UINT64_MAX - idle_timeout) { + return UINT64_MAX; + } + + return conn->idle_ts + idle_timeout; +} + +ngtcp2_duration ngtcp2_conn_get_pto(ngtcp2_conn *conn) { + return conn_compute_pto(conn, conn_is_tls_handshake_completed(conn) + ? &conn->pktns + : conn->hs_pktns); +} + +void ngtcp2_conn_set_initial_crypto_ctx(ngtcp2_conn *conn, + const ngtcp2_crypto_ctx *ctx) { + assert(conn->in_pktns); + conn->in_pktns->crypto.ctx = *ctx; +} + +const ngtcp2_crypto_ctx *ngtcp2_conn_get_initial_crypto_ctx(ngtcp2_conn *conn) { + assert(conn->in_pktns); + return &conn->in_pktns->crypto.ctx; +} + +void ngtcp2_conn_set_retry_aead(ngtcp2_conn *conn, + const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx) { + assert(!conn->crypto.retry_aead_ctx.native_handle); + + conn->crypto.retry_aead = *aead; + conn->crypto.retry_aead_ctx = *aead_ctx; +} + +void ngtcp2_conn_set_crypto_ctx(ngtcp2_conn *conn, + const ngtcp2_crypto_ctx *ctx) { + assert(conn->hs_pktns); + conn->hs_pktns->crypto.ctx = *ctx; + conn->pktns.crypto.ctx = *ctx; +} + +const ngtcp2_crypto_ctx *ngtcp2_conn_get_crypto_ctx(ngtcp2_conn *conn) { + return &conn->pktns.crypto.ctx; +} + +void ngtcp2_conn_set_0rtt_crypto_ctx(ngtcp2_conn *conn, + const ngtcp2_crypto_ctx *ctx) { + conn->early.ctx = *ctx; +} + +const ngtcp2_crypto_ctx *ngtcp2_conn_get_0rtt_crypto_ctx(ngtcp2_conn *conn) { + return &conn->early.ctx; +} + +void *ngtcp2_conn_get_tls_native_handle(ngtcp2_conn *conn) { + return conn->crypto.tls_native_handle; +} + +void ngtcp2_conn_set_tls_native_handle(ngtcp2_conn *conn, + void *tls_native_handle) { + conn->crypto.tls_native_handle = tls_native_handle; +} + +const ngtcp2_ccerr *ngtcp2_conn_get_ccerr(ngtcp2_conn *conn) { + return &conn->rx.ccerr; +} + +void ngtcp2_conn_set_tls_error(ngtcp2_conn *conn, int liberr) { + conn->crypto.tls_error = liberr; +} + +int ngtcp2_conn_get_tls_error(ngtcp2_conn *conn) { + return conn->crypto.tls_error; +} + +void ngtcp2_conn_set_tls_alert(ngtcp2_conn *conn, uint8_t alert) { + conn->crypto.tls_alert = alert; +} + +uint8_t ngtcp2_conn_get_tls_alert(ngtcp2_conn *conn) { + return conn->crypto.tls_alert; +} + +int ngtcp2_conn_is_local_stream(ngtcp2_conn *conn, int64_t stream_id) { + return conn_local_stream(conn, stream_id); +} + +int ngtcp2_conn_is_server(ngtcp2_conn *conn) { return conn->server; } + +int ngtcp2_conn_after_retry(ngtcp2_conn *conn) { + return (conn->flags & NGTCP2_CONN_FLAG_RECV_RETRY) != 0; +} + +int ngtcp2_conn_set_stream_user_data(ngtcp2_conn *conn, int64_t stream_id, + void *stream_user_data) { + ngtcp2_strm *strm = ngtcp2_conn_find_stream(conn, stream_id); + + if (strm == NULL) { + return NGTCP2_ERR_STREAM_NOT_FOUND; + } + + strm->stream_user_data = stream_user_data; + + return 0; +} + +void ngtcp2_conn_update_pkt_tx_time(ngtcp2_conn *conn, ngtcp2_tstamp ts) { + ngtcp2_duration pacing_interval; + ngtcp2_duration wait; + + conn_update_timestamp(conn, ts); + + if (conn->tx.pacing.pktlen == 0) { + return; + } + + if (conn->cstat.pacing_interval) { + pacing_interval = conn->cstat.pacing_interval; + } else { + /* 1.25 is the under-utilization avoidance factor described in + https://datatracker.ietf.org/doc/html/rfc9002#section-7.7 */ + pacing_interval = (conn->cstat.first_rtt_sample_ts == UINT64_MAX + ? NGTCP2_MILLISECONDS + : conn->cstat.smoothed_rtt) * + 100 / 125 / conn->cstat.cwnd; + } + + wait = (ngtcp2_duration)(conn->tx.pacing.pktlen * pacing_interval); + + conn->tx.pacing.next_ts = ts + wait; + conn->tx.pacing.pktlen = 0; +} + +size_t ngtcp2_conn_get_send_quantum(ngtcp2_conn *conn) { + return conn->cstat.send_quantum; +} + +int ngtcp2_conn_track_retired_dcid_seq(ngtcp2_conn *conn, uint64_t seq) { + size_t i; + + if (conn->dcid.retire_unacked.len >= + ngtcp2_arraylen(conn->dcid.retire_unacked.seqs)) { + return NGTCP2_ERR_CONNECTION_ID_LIMIT; + } + + /* Make sure that we do not have a duplicate */ + for (i = 0; i < conn->dcid.retire_unacked.len; ++i) { + if (conn->dcid.retire_unacked.seqs[i] == seq) { + ngtcp2_unreachable(); + } + } + + conn->dcid.retire_unacked.seqs[conn->dcid.retire_unacked.len++] = seq; + + return 0; +} + +void ngtcp2_conn_untrack_retired_dcid_seq(ngtcp2_conn *conn, uint64_t seq) { + size_t i; + + for (i = 0; i < conn->dcid.retire_unacked.len; ++i) { + if (conn->dcid.retire_unacked.seqs[i] != seq) { + continue; + } + + if (i != conn->dcid.retire_unacked.len - 1) { + conn->dcid.retire_unacked.seqs[i] = + conn->dcid.retire_unacked.seqs[conn->dcid.retire_unacked.len - 1]; + } + + --conn->dcid.retire_unacked.len; + + return; + } +} + +size_t ngtcp2_conn_get_stream_loss_count(ngtcp2_conn *conn, int64_t stream_id) { + ngtcp2_strm *strm = ngtcp2_conn_find_stream(conn, stream_id); + + if (strm == NULL) { + return 0; + } + + return strm->tx.loss_count; +} + +void ngtcp2_path_challenge_entry_init(ngtcp2_path_challenge_entry *pcent, + const ngtcp2_path *path, + const uint8_t *data) { + ngtcp2_path_storage_init2(&pcent->ps, path); + memcpy(pcent->data, data, sizeof(pcent->data)); +} + +void ngtcp2_settings_default_versioned(int settings_version, + ngtcp2_settings *settings) { + (void)settings_version; + + memset(settings, 0, sizeof(*settings)); + settings->cc_algo = NGTCP2_CC_ALGO_CUBIC; + settings->initial_rtt = NGTCP2_DEFAULT_INITIAL_RTT; + settings->ack_thresh = 2; + settings->max_tx_udp_payload_size = 1500 - 48; + settings->handshake_timeout = UINT64_MAX; +} + +void ngtcp2_transport_params_default_versioned( + int transport_params_version, ngtcp2_transport_params *params) { + size_t len; + + switch (transport_params_version) { + case NGTCP2_TRANSPORT_PARAMS_VERSION: + len = sizeof(*params); + + break; + default: + ngtcp2_unreachable(); + } + + memset(params, 0, len); + + switch (transport_params_version) { + case NGTCP2_TRANSPORT_PARAMS_VERSION: + params->max_udp_payload_size = NGTCP2_DEFAULT_MAX_RECV_UDP_PAYLOAD_SIZE; + params->active_connection_id_limit = + NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT; + params->ack_delay_exponent = NGTCP2_DEFAULT_ACK_DELAY_EXPONENT; + params->max_ack_delay = NGTCP2_DEFAULT_MAX_ACK_DELAY; + + break; + } +} + +/* The functions prefixed with ngtcp2_pkt_ are usually put inside + ngtcp2_pkt.c. This function uses encryption construct and uses + test data defined only in ngtcp2_conn_test.c, so it is written + here. */ +ngtcp2_ssize ngtcp2_pkt_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, + size_t reasonlen, ngtcp2_encrypt encrypt, const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx, const uint8_t *iv, + ngtcp2_hp_mask hp_mask, const ngtcp2_crypto_cipher *hp, + const ngtcp2_crypto_cipher_ctx *hp_ctx) { + ngtcp2_pkt_hd hd; + ngtcp2_crypto_km ckm; + ngtcp2_crypto_cc cc; + ngtcp2_ppe ppe; + ngtcp2_frame fr = {0}; + int rv; + + ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_LONG_FORM, NGTCP2_PKT_INITIAL, dcid, + scid, /* pkt_num = */ 0, /* pkt_numlen = */ 1, version, + /* len = */ 0); + + ngtcp2_vec_init(&ckm.secret, NULL, 0); + ngtcp2_vec_init(&ckm.iv, iv, 12); + ckm.aead_ctx = *aead_ctx; + ckm.pkt_num = 0; + ckm.flags = NGTCP2_CRYPTO_KM_FLAG_NONE; + + cc.aead = *aead; + cc.hp = *hp; + cc.ckm = &ckm; + cc.hp_ctx = *hp_ctx; + cc.encrypt = encrypt; + cc.hp_mask = hp_mask; + + ngtcp2_ppe_init(&ppe, dest, destlen, &cc); + + rv = ngtcp2_ppe_encode_hd(&ppe, &hd); + if (rv != 0) { + assert(NGTCP2_ERR_NOBUF == rv); + return rv; + } + + if (!ngtcp2_ppe_ensure_hp_sample(&ppe)) { + return NGTCP2_ERR_NOBUF; + } + + fr.type = NGTCP2_FRAME_CONNECTION_CLOSE; + fr.connection_close.error_code = error_code; + fr.connection_close.reasonlen = reasonlen; + fr.connection_close.reason = (uint8_t *)reason; + + rv = ngtcp2_ppe_encode_frame(&ppe, &fr); + if (rv != 0) { + assert(NGTCP2_ERR_NOBUF == rv); + return rv; + } + + return ngtcp2_ppe_final(&ppe, NULL); +} + +int ngtcp2_is_bidi_stream(int64_t stream_id) { return bidi_stream(stream_id); } + +uint32_t ngtcp2_select_version(const uint32_t *preferred_versions, + size_t preferred_versionslen, + const uint32_t *offered_versions, + size_t offered_versionslen) { + size_t i, j; + + if (!preferred_versionslen || !offered_versionslen) { + return 0; + } + + for (i = 0; i < preferred_versionslen; ++i) { + assert(ngtcp2_is_supported_version(preferred_versions[i])); + + for (j = 0; j < offered_versionslen; ++j) { + if (preferred_versions[i] == offered_versions[j]) { + return preferred_versions[i]; + } + } + } + + return 0; +} diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_conn.h b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_conn.h new file mode 100644 index 0000000..4ed6787 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_conn.h @@ -0,0 +1,1159 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 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_H +#define NGTCP2_CONN_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_mem.h" +#include "ngtcp2_crypto.h" +#include "ngtcp2_acktr.h" +#include "ngtcp2_rtb.h" +#include "ngtcp2_strm.h" +#include "ngtcp2_idtr.h" +#include "ngtcp2_str.h" +#include "ngtcp2_pkt.h" +#include "ngtcp2_log.h" +#include "ngtcp2_pq.h" +#include "ngtcp2_cc.h" +#include "ngtcp2_bbr.h" +#include "ngtcp2_pv.h" +#include "ngtcp2_pmtud.h" +#include "ngtcp2_cid.h" +#include "ngtcp2_buf.h" +#include "ngtcp2_ppe.h" +#include "ngtcp2_qlog.h" +#include "ngtcp2_rst.h" +#include "ngtcp2_conn_stat.h" + +typedef enum { + /* Client specific handshake states */ + NGTCP2_CS_CLIENT_INITIAL, + NGTCP2_CS_CLIENT_WAIT_HANDSHAKE, + /* Server specific handshake states */ + NGTCP2_CS_SERVER_INITIAL, + NGTCP2_CS_SERVER_WAIT_HANDSHAKE, + /* Shared by both client and server */ + NGTCP2_CS_POST_HANDSHAKE, + NGTCP2_CS_CLOSING, + NGTCP2_CS_DRAINING, +} ngtcp2_conn_state; + +/* NGTCP2_MAX_STREAMS is the maximum number of streams. */ +#define NGTCP2_MAX_STREAMS (1LL << 60) + +/* NGTCP2_MAX_NUM_BUFFED_RX_PKTS is the maximum number of buffered + reordered packets. */ +#define NGTCP2_MAX_NUM_BUFFED_RX_PKTS 4 + +/* NGTCP2_MAX_REORDERED_CRYPTO_DATA is the maximum offset of crypto + data which is not continuous. In other words, there is a gap of + unreceived data. */ +#define NGTCP2_MAX_REORDERED_CRYPTO_DATA 65536 + +/* NGTCP2_MAX_RX_INITIAL_CRYPTO_DATA is the maximum offset of received + crypto stream in Initial packet. We set this hard limit here + because crypto stream is unbounded. */ +#define NGTCP2_MAX_RX_INITIAL_CRYPTO_DATA 65536 +/* NGTCP2_MAX_RX_HANDSHAKE_CRYPTO_DATA is the maximum offset of + received crypto stream in Handshake packet. We set this hard limit + here because crypto stream is unbounded. */ +#define NGTCP2_MAX_RX_HANDSHAKE_CRYPTO_DATA 65536 + +/* NGTCP2_MAX_RETRIES is the number of Retry packet which client can + accept. */ +#define NGTCP2_MAX_RETRIES 3 + +/* NGTCP2_MAX_BOUND_DCID_POOL_SIZE is the maximum number of + destination connection ID which have been bound to a particular + path, but not yet used as primary path and path validation is not + performed from the local endpoint. */ +#define NGTCP2_MAX_BOUND_DCID_POOL_SIZE 4 +/* NGTCP2_MAX_DCID_POOL_SIZE is the maximum number of destination + connection ID the remote endpoint provides to store. It must be + the power of 2. */ +#define NGTCP2_MAX_DCID_POOL_SIZE 8 +/* NGTCP2_MAX_DCID_RETIRED_SIZE is the maximum number of retired DCID + kept to catch in-flight packet on retired path. */ +#define NGTCP2_MAX_DCID_RETIRED_SIZE 2 +/* 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 + tells the maximum value. The value can be quite large, and we have + to put the sane limit.*/ +#define NGTCP2_MAX_SCID_POOL_SIZE 8 + +/* NGTCP2_ECN_MAX_NUM_VALIDATION_PKTS is the maximum number of ECN marked + packets sent in NGTCP2_ECN_STATE_TESTING period. */ +#define NGTCP2_ECN_MAX_NUM_VALIDATION_PKTS 10 + +/* NGTCP2_CCERR_MAX_REASONLEN is the maximum length of reason phrase + to remember. If the received reason phrase is longer than this + value, it is truncated. */ +#define NGTCP2_CCERR_MAX_REASONLEN 1024 + +/* NGTCP2_WRITE_PKT_FLAG_NONE indicates that no flag is set. */ +#define NGTCP2_WRITE_PKT_FLAG_NONE 0x00u +/* NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING indicates that packet other + than Initial packet should be padded. Initial packet might be + padded based on QUIC requirement regardless of this flag. */ +#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 + +/* + * 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; + +typedef struct ngtcp2_path_challenge_entry { + ngtcp2_path_storage ps; + uint8_t data[8]; +} ngtcp2_path_challenge_entry; + +void ngtcp2_path_challenge_entry_init(ngtcp2_path_challenge_entry *pcent, + const ngtcp2_path *path, + const uint8_t *data); + +/* NGTCP2_CONN_FLAG_NONE indicates that no flag is set. */ +#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 + not indicate the completion of QUIC handshake. Some + 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 +/* 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 +/* NGTCP2_CONN_FLAG_TRANSPORT_PARAM_RECVED is set if transport + parameters are received. */ +#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 +/* NGTCP2_CONN_FLAG_RECV_RETRY is set when a client receives Retry + packet. */ +#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 +/* 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 +/* NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED is set when an endpoint + confirmed completion of handshake. */ +#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 +/* 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 +/* 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 +/* 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 +/* 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 +/* 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 +/* 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 +/* 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 +/* NGTCP2_CONN_FLAG_KEY_UPDATE_INITIATOR is set when the local + endpoint has initiated key update. */ +#define NGTCP2_CONN_FLAG_KEY_UPDATE_INITIATOR 0x10000u + +typedef struct ngtcp2_pktns { + struct { + /* last_pkt_num is the packet number which the local endpoint sent + last time.*/ + int64_t last_pkt_num; + ngtcp2_frame_chain *frq; + /* non_ack_pkt_start_ts is the timestamp since the local endpoint + starts sending continuous non ACK-eliciting packets. */ + ngtcp2_tstamp non_ack_pkt_start_ts; + + struct { + /* ect0 is the number of QUIC packets, not UDP datagram, which + are sent in UDP datagram with ECT0 marking. */ + size_t ect0; + /* start_pkt_num is the lowest packet number that are sent + during ECN validation period. */ + int64_t start_pkt_num; + /* validation_pkt_sent is the number of QUIC packets sent during + validation period. */ + size_t validation_pkt_sent; + /* validation_pkt_lost is the number of QUIC packets lost during + validation period. */ + size_t validation_pkt_lost; + } ecn; + } tx; + + struct { + /* pngap tracks received packet number in order to suppress + duplicated packet number. */ + ngtcp2_gaptr pngap; + /* max_pkt_num is the largest packet number received so far. */ + int64_t max_pkt_num; + /* max_pkt_ts is the timestamp when max_pkt_num packet is + received. */ + ngtcp2_tstamp max_pkt_ts; + /* max_ack_eliciting_pkt_num is the largest ack-eliciting packet + number received so far. */ + int64_t max_ack_eliciting_pkt_num; + /* + * buffed_pkts is buffered packets which cannot be decrypted with + * the current encryption level. + * + * In server Initial encryption level, 0-RTT packet may be buffered. + * In server Handshake encryption level, Short packet may be buffered. + * + * In client Initial encryption level, Handshake or Short packet may + * be buffered. In client Handshake encryption level, Short packet + * may be buffered. + * + * - 0-RTT packet is only buffered in server Initial encryption + * level ngtcp2_pktns. + * + * - Handshake packet is only buffered in client Handshake + * encryption level ngtcp2_pktns. + * + * - Short packet is only buffered in Short encryption level + * ngtcp2_pktns. + */ + ngtcp2_pkt_chain *buffed_pkts; + + struct { + /* ect0, ect1, and ce are the number of QUIC packets received + with those markings. */ + size_t ect0; + size_t ect1; + size_t ce; + struct { + /* ect0, ect1, ce are the ECN counts received in the latest + ACK frame. */ + uint64_t ect0; + uint64_t ect1; + uint64_t ce; + } ack; + } ecn; + } rx; + + struct { + struct { + /* offset is the offset of crypto stream in this packet number + space. */ + uint64_t offset; + /* ckm is a cryptographic key, and iv to encrypt outgoing + packets. */ + ngtcp2_crypto_km *ckm; + /* hp_ctx is cipher context for packet header protection. */ + ngtcp2_crypto_cipher_ctx hp_ctx; + /* data is the submitted crypto data. */ + ngtcp2_buf_chain *data; + } tx; + + struct { + /* ckm is a cryptographic key, and iv to decrypt incoming + packets. */ + ngtcp2_crypto_km *ckm; + /* hp_ctx is cipher context for packet header protection. */ + ngtcp2_crypto_cipher_ctx hp_ctx; + } rx; + + ngtcp2_strm strm; + ngtcp2_crypto_ctx ctx; + } crypto; + + ngtcp2_acktr acktr; + ngtcp2_rtb rtb; +} ngtcp2_pktns; + +typedef enum ngtcp2_ecn_state { + NGTCP2_ECN_STATE_TESTING, + NGTCP2_ECN_STATE_UNKNOWN, + NGTCP2_ECN_STATE_FAILED, + NGTCP2_ECN_STATE_CAPABLE, +} ngtcp2_ecn_state; + +/* ngtcp2_early_transport_params is the values remembered by client + from the previous session. */ +typedef struct ngtcp2_early_transport_params { + uint64_t initial_max_streams_bidi; + uint64_t initial_max_streams_uni; + uint64_t initial_max_stream_data_bidi_local; + uint64_t initial_max_stream_data_bidi_remote; + uint64_t initial_max_stream_data_uni; + uint64_t initial_max_data; + uint64_t active_connection_id_limit; + uint64_t max_datagram_frame_size; +} ngtcp2_early_transport_params; + +ngtcp2_static_ringbuf_def(dcid_bound, NGTCP2_MAX_BOUND_DCID_POOL_SIZE, + sizeof(ngtcp2_dcid)); +ngtcp2_static_ringbuf_def(dcid_unused, NGTCP2_MAX_DCID_POOL_SIZE, + sizeof(ngtcp2_dcid)); +ngtcp2_static_ringbuf_def(dcid_retired, NGTCP2_MAX_DCID_RETIRED_SIZE, + sizeof(ngtcp2_dcid)); +ngtcp2_static_ringbuf_def(path_challenge, 4, + sizeof(ngtcp2_path_challenge_entry)); + +ngtcp2_objalloc_decl(strm, ngtcp2_strm, oplent); + +struct ngtcp2_conn { + ngtcp2_objalloc frc_objalloc; + ngtcp2_objalloc rtb_entry_objalloc; + ngtcp2_objalloc strm_objalloc; + ngtcp2_conn_state state; + ngtcp2_callbacks callbacks; + /* rcid is a connection ID present in Initial or 0-RTT packet from + client as destination connection ID. Server uses this field to + check that duplicated Initial or 0-RTT packet are indeed sent to + this connection. Client uses this field to validate + original_destination_connection_id transport parameter. */ + ngtcp2_cid rcid; + /* oscid is the source connection ID initially used by the local + endpoint. */ + ngtcp2_cid oscid; + /* retry_scid is the source connection ID from Retry packet. Client + records it in order to verify retry_source_connection_id + transport parameter. Server does not use this field. */ + ngtcp2_cid retry_scid; + ngtcp2_pktns *in_pktns; + ngtcp2_pktns *hs_pktns; + ngtcp2_pktns pktns; + + struct { + /* current is the current destination connection ID. */ + ngtcp2_dcid current; + /* bound is a set of destination connection IDs which are bound to + particular paths. These paths are not validated yet. */ + ngtcp2_static_ringbuf_dcid_bound bound; + /* unused is a set of unused CID received from peer. */ + ngtcp2_static_ringbuf_dcid_unused unused; + /* retired is a set of CID retired by local endpoint. Keep them + in 3*PTO to catch packets in flight along the old path. */ + ngtcp2_static_ringbuf_dcid_retired retired; + /* seqgap tracks received sequence numbers in order to ignore + retransmitted duplicated NEW_CONNECTION_ID frame. */ + ngtcp2_gaptr seqgap; + /* retire_prior_to is the largest retire_prior_to received so + far. */ + uint64_t retire_prior_to; + struct { + /* seqs contains sequence number of Connection ID whose + retirement is not acknowledged by the remote endpoint yet. */ + uint64_t seqs[NGTCP2_MAX_DCID_POOL_SIZE * 2]; + /* len is the number of sequence numbers that seq contains. */ + size_t len; + } retire_unacked; + /* zerolen_seq is a pseudo sequence number of zero-length + Destination Connection ID in order to distinguish between + them. */ + uint64_t zerolen_seq; + } dcid; + + struct { + /* set is a set of CID sent to peer. The peer can use any CIDs in + this set. This includes used CID as well as unused ones. */ + ngtcp2_ksl set; + /* used is a set of CID used by peer. The sort function of this + priority queue takes timestamp when CID is retired and sorts + them in ascending order. */ + ngtcp2_pq used; + /* last_seq is the last sequence number of connection ID. */ + uint64_t last_seq; + /* num_retired is the number of retired Connection ID still + included in set. */ + size_t num_retired; + /* num_in_flight is the number of NEW_CONNECTION_ID frames that + are in-flight and not acknowledged yet. */ + size_t num_in_flight; + } scid; + + struct { + /* strmq contains ngtcp2_strm which has frames to send. */ + ngtcp2_pq strmq; + /* ack is ACK frame. The underlying buffer is reused. */ + ngtcp2_frame *ack; + /* max_ack_ranges is the number of additional ngtcp2_ack_range + which ack can contain. */ + size_t max_ack_ranges; + /* offset is the offset the local endpoint has sent to the remote + endpoint. */ + uint64_t offset; + /* max_offset is the maximum offset that local endpoint can + send. */ + uint64_t max_offset; + /* last_blocked_offset is the largest offset where the + transmission of stream data is blocked. */ + uint64_t last_blocked_offset; + /* last_max_data_ts is the timestamp when last MAX_DATA frame is + sent. */ + 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; + } ecn; + + struct { + /* pktlen is the number of bytes written before calling + ngtcp2_conn_update_pkt_tx_time which resets this field to + 0. */ + size_t pktlen; + /* next_ts is the time to send next packet. It is UINT64_MAX if + packet pacing is disabled or expired.*/ + ngtcp2_tstamp next_ts; + } pacing; + } tx; + + struct { + /* unsent_max_offset is the maximum offset that remote endpoint + can send without extending MAX_DATA. This limit is not yet + notified to the remote endpoint. */ + uint64_t unsent_max_offset; + /* offset is the cumulative sum of stream data received for this + connection. */ + uint64_t offset; + /* max_offset is the maximum offset that remote endpoint can + send. */ + uint64_t max_offset; + /* window is the connection-level flow control window size. */ + uint64_t window; + /* path_challenge stores received PATH_CHALLENGE data. */ + ngtcp2_static_ringbuf_path_challenge path_challenge; + /* ccerr is the received connection close error. */ + ngtcp2_ccerr ccerr; + } rx; + + struct { + ngtcp2_crypto_km *ckm; + ngtcp2_crypto_cipher_ctx hp_ctx; + ngtcp2_crypto_ctx ctx; + /* discard_started_ts is the timestamp when the timer to discard + early key has started. Used by server only. */ + ngtcp2_tstamp discard_started_ts; + /* transport_params is the values remembered by client from the + previous session. These are set by + ngtcp2_conn_set_early_remote_transport_params(). Server does + not use this field. Server must not set values for these + parameters that are smaller than the remembered values. */ + ngtcp2_early_transport_params transport_params; + } early; + + struct { + ngtcp2_settings settings; + /* transport_params is the local transport parameters. It is used + for Short packet only. */ + ngtcp2_transport_params transport_params; + struct { + /* max_streams is the maximum number of bidirectional streams which + the local endpoint can open. */ + uint64_t max_streams; + /* next_stream_id is the bidirectional stream ID which the local + endpoint opens next. */ + int64_t next_stream_id; + } bidi; + + struct { + /* max_streams is the maximum number of unidirectional streams + which the local endpoint can open. */ + uint64_t max_streams; + /* next_stream_id is the unidirectional stream ID which the + local endpoint opens next. */ + int64_t next_stream_id; + } uni; + } local; + + struct { + /* transport_params is the received transport parameters during + handshake. It is used for Short packet only. */ + ngtcp2_transport_params *transport_params; + /* pending_transport_params is received transport parameters + during handshake. It is copied to transport_params when 1RTT + key is available. */ + ngtcp2_transport_params *pending_transport_params; + struct { + ngtcp2_idtr idtr; + /* unsent_max_streams is the maximum number of streams of peer + initiated bidirectional stream which the local endpoint can + accept. This limit is not yet notified to the remote + endpoint. */ + uint64_t unsent_max_streams; + /* max_streams is the maximum number of streams of peer + initiated bidirectional stream which the local endpoint can + accept. */ + uint64_t max_streams; + } bidi; + + struct { + ngtcp2_idtr idtr; + /* unsent_max_streams is the maximum number of streams of peer + initiated unidirectional stream which the local endpoint can + accept. This limit is not yet notified to the remote + endpoint. */ + uint64_t unsent_max_streams; + /* max_streams is the maximum number of streams of peer + initiated unidirectional stream which the local endpoint can + accept. */ + uint64_t max_streams; + } uni; + } remote; + + struct { + struct { + /* new_tx_ckm is a new sender 1RTT key which has not been + used. */ + ngtcp2_crypto_km *new_tx_ckm; + /* new_rx_ckm is a new receiver 1RTT key which has not + successfully decrypted incoming packet yet. */ + ngtcp2_crypto_km *new_rx_ckm; + /* old_rx_ckm is an old receiver 1RTT key. */ + ngtcp2_crypto_km *old_rx_ckm; + /* confirmed_ts is the time instant when the key update is + confirmed by the local endpoint last time. UINT64_MAX means + undefined value. */ + ngtcp2_tstamp confirmed_ts; + } key_update; + + /* tls_native_handle is a native handle to TLS session object. */ + void *tls_native_handle; + /* decrypt_hp_buf is a buffer which is used to write unprotected + packet header. */ + ngtcp2_vec decrypt_hp_buf; + /* decrypt_buf is a buffer which is used to write decrypted data. */ + ngtcp2_vec decrypt_buf; + /* retry_aead is AEAD to verify Retry packet integrity. It is + used by client only. */ + ngtcp2_crypto_aead retry_aead; + /* 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; + /* 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 + NGTCP2_WRITE_STREAM_FLAG_MORE */ + struct { + ngtcp2_crypto_cc cc; + ngtcp2_pkt_hd hd; + ngtcp2_ppe ppe; + ngtcp2_frame_chain **pfrc; + int pkt_empty; + int hd_logged; + /* 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 { + /* last_ts is a timestamp when a last packet is sent or received + on a current path. */ + ngtcp2_tstamp last_ts; + /* timeout is keep-alive timeout. When it expires, a packet + should be sent to a current path to keep connection alive. It + might be used to keep NAT binding intact. If 0 is set, + keep-alive timer is disabled. */ + ngtcp2_duration timeout; + } keep_alive; + + struct { + /* Initial keys for negotiated version. If original version == + negotiated version, these fields are not used. */ + struct { + ngtcp2_crypto_km *ckm; + ngtcp2_crypto_cipher_ctx hp_ctx; + } rx; + struct { + ngtcp2_crypto_km *ckm; + ngtcp2_crypto_cipher_ctx hp_ctx; + } tx; + /* version is QUIC version that the above Initial keys are created + for. */ + uint32_t version; + /* preferred_versions is the array of versions that are preferred + by the local endpoint. Server negotiates one of those versions + in this array if a client initially selects a less preferred + version. Client uses this field and original_version field to + prevent version downgrade attack if it reacted upon Version + Negotiation packet. */ + uint32_t *preferred_versions; + /* preferred_versionslen is the number of versions stored in the + array pointed by preferred_versions. This field is only used + by server. */ + size_t preferred_versionslen; + /* available_versions is the versions that the local endpoint + sends in version_information transport parameter. This is the + wire image of available_versions field of version_information + transport parameter. */ + uint8_t *available_versions; + /* available_versionslen is the length of data pointed by + available_versions field. */ + size_t available_versionslen; + } vneg; + + ngtcp2_map strms; + ngtcp2_conn_stat cstat; + ngtcp2_pv *pv; + ngtcp2_pmtud *pmtud; + ngtcp2_log log; + ngtcp2_qlog qlog; + ngtcp2_rst rst; + ngtcp2_cc_algo cc_algo; + union { + ngtcp2_cc cc; + ngtcp2_cc_reno reno; + ngtcp2_cc_cubic cubic; + ngtcp2_cc_bbr bbr; + }; + const ngtcp2_mem *mem; + /* idle_ts is the time instant when idle timer started. */ + ngtcp2_tstamp idle_ts; + 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; + int server; +}; + +typedef enum ngtcp2_vmsg_type { + NGTCP2_VMSG_TYPE_STREAM, + NGTCP2_VMSG_TYPE_DATAGRAM, +} 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; + /* datacnt is the number of ngtcp2_vec pointed by data. */ + size_t datacnt; + /* pdatalen is the pointer to the variable which the number of bytes + written is assigned to if pdatalen is not NULL. */ + ngtcp2_ssize *pdatalen; +} ngtcp2_vmsg_stream; + +typedef struct ngtcp2_vmsg_datagram { + /* data is the pointer to ngtcp2_vec array which contains the data + to send. */ + const ngtcp2_vec *data; + /* datacnt is the number of ngtcp2_vec pointed by data. */ + 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; +} ngtcp2_vmsg_datagram; + +typedef struct ngtcp2_vmsg { + ngtcp2_vmsg_type type; + union { + ngtcp2_vmsg_stream stream; + ngtcp2_vmsg_datagram datagram; + }; +} ngtcp2_vmsg; + +/* + * ngtcp2_conn_sched_ack stores packet number |pkt_num| and its + * reception timestamp |ts| in order to send its ACK. + * + * It returns 0 if it succeeds, or one of the following negative error + * codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory + * NGTCP2_ERR_PROTO + * Same packet number has already been added. + */ +int ngtcp2_conn_sched_ack(ngtcp2_conn *conn, ngtcp2_acktr *acktr, + int64_t pkt_num, int active_ack, ngtcp2_tstamp ts); + +/* + * ngtcp2_conn_find_stream returns a stream whose stream ID is + * |stream_id|. If no such stream is found, it returns NULL. + */ +ngtcp2_strm *ngtcp2_conn_find_stream(ngtcp2_conn *conn, int64_t stream_id); + +/* + * conn_init_stream initializes |strm|. Its stream ID is |stream_id|. + * This function adds |strm| to conn->strms. |strm| must be allocated + * by the caller. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory + * NGTCP2_ERR_CALLBACK_FAILURE + * User-callback function failed. + */ +int ngtcp2_conn_init_stream(ngtcp2_conn *conn, ngtcp2_strm *strm, + int64_t stream_id, void *stream_user_data); + +/* + * ngtcp2_conn_close_stream closes stream |strm|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_INVALID_ARGUMENT + * Stream is not found. + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + */ +int ngtcp2_conn_close_stream(ngtcp2_conn *conn, ngtcp2_strm *strm); + +/* + * ngtcp2_conn_close_stream closes stream |strm| if no further + * transmission and reception are allowed, and all reordered incoming + * data are emitted to the application, and the transmitted data are + * acked. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_INVALID_ARGUMENT + * Stream is not found. + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + */ +int ngtcp2_conn_close_stream_if_shut_rdwr(ngtcp2_conn *conn, ngtcp2_strm *strm); + +/* + * ngtcp2_conn_update_rtt updates RTT measurements. |rtt| is a latest + * RTT which is not adjusted by ack delay. |ack_delay| is unscaled + * 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_set_loss_detection_timer(ngtcp2_conn *conn, ngtcp2_tstamp ts); + +int ngtcp2_conn_on_loss_detection_timer(ngtcp2_conn *conn, ngtcp2_tstamp ts); + +/* + * ngtcp2_conn_detect_lost_pkt detects lost packets. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +int ngtcp2_conn_detect_lost_pkt(ngtcp2_conn *conn, ngtcp2_pktns *pktns, + ngtcp2_conn_stat *cstat, ngtcp2_tstamp ts); + +/* + * ngtcp2_conn_tx_strmq_top returns the ngtcp2_strm which sits on the + * top of queue. tx_strmq must not be empty. + */ +ngtcp2_strm *ngtcp2_conn_tx_strmq_top(ngtcp2_conn *conn); + +/* + * ngtcp2_conn_tx_strmq_pop pops the ngtcp2_strm from the queue. + * tx_strmq must not be empty. + */ +void ngtcp2_conn_tx_strmq_pop(ngtcp2_conn *conn); + +/* + * ngtcp2_conn_tx_strmq_push pushes |strm| into tx_strmq. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +int ngtcp2_conn_tx_strmq_push(ngtcp2_conn *conn, ngtcp2_strm *strm); + +/* + * ngtcp2_conn_internal_expiry returns the minimum expiry time among + * all timers in |conn|. + */ +ngtcp2_tstamp ngtcp2_conn_internal_expiry(ngtcp2_conn *conn); + +ngtcp2_ssize ngtcp2_conn_write_vmsg(ngtcp2_conn *conn, ngtcp2_path *path, + int pkt_info_version, ngtcp2_pkt_info *pi, + uint8_t *dest, size_t destlen, + ngtcp2_vmsg *vmsg, ngtcp2_tstamp ts); + +/* + * ngtcp2_conn_write_single_frame_pkt writes a packet which contains + * |fr| frame only in the buffer pointed by |dest| whose length if + * |destlen|. |type| is a long packet type to send. If |type| is 0, + * Short packet is used. |dcid| is used as a destination connection + * ID. |flags| is zero or more of NGTCP2_WRITE_PKT_FLAG_*. Only + * NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING is recognized. + * + * The packet written by this function will not be retransmitted. + * + * This function returns the number of bytes written in |dest| if it + * succeeds, or one of the following negative error codes: + * + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + */ +ngtcp2_ssize ngtcp2_conn_write_single_frame_pkt( + ngtcp2_conn *conn, ngtcp2_pkt_info *pi, uint8_t *dest, size_t destlen, + uint8_t type, uint8_t flags, const ngtcp2_cid *dcid, ngtcp2_frame *fr, + uint16_t rtb_entry_flags, const ngtcp2_path *path, ngtcp2_tstamp ts); + +/* + * ngtcp2_conn_commit_local_transport_params commits the local + * transport parameters, which is currently set to + * conn->local.settings.transport_params. This function will do some + * amends on transport parameters for adjusting default values. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + * NGTCP2_ERR_INVALID_ARGUMENT + * CID in preferred address equals to the original SCID. + */ +int ngtcp2_conn_commit_local_transport_params(ngtcp2_conn *conn); + +/* + * ngtcp2_conn_lost_pkt_expiry returns the earliest expiry time of + * lost packet. + */ +ngtcp2_tstamp ngtcp2_conn_lost_pkt_expiry(ngtcp2_conn *conn); + +/* + * ngtcp2_conn_remove_lost_pkt removes the expired lost packet. + */ +void ngtcp2_conn_remove_lost_pkt(ngtcp2_conn *conn, ngtcp2_tstamp ts); + +uint64_t ngtcp2_conn_tx_strmq_first_cycle(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_ack_delay_expiry` returns the expiry time point of + * delayed protected ACK. One should call + * `ngtcp2_conn_cancel_expired_ack_delay_timer` and + * `ngtcp2_conn_write_pkt` (or `ngtcp2_conn_writev_stream`) when it + * expires. It returns UINT64_MAX if there is no expiry. + */ +ngtcp2_tstamp ngtcp2_conn_ack_delay_expiry(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_cancel_expired_ack_delay_timer` stops expired ACK + * delay timer. |ts| is the current time. This function must be + * called when `ngtcp2_conn_ack_delay_expiry` <= ts. + */ +void ngtcp2_conn_cancel_expired_ack_delay_timer(ngtcp2_conn *conn, + ngtcp2_tstamp ts); + +/** + * @function + * + * `ngtcp2_conn_loss_detection_expiry` returns the expiry time point + * of loss detection timer. One should call + * `ngtcp2_conn_on_loss_detection_timer` and `ngtcp2_conn_write_pkt` + * (or `ngtcp2_conn_writev_stream`) when it expires. It returns + * UINT64_MAX if loss detection timer is not armed. + */ +ngtcp2_tstamp ngtcp2_conn_loss_detection_expiry(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_get_idle_expiry` returns the time when a connection + * should be closed if it continues to be idle. If idle timeout is + * disabled, this function returns ``UINT64_MAX``. + */ +ngtcp2_tstamp ngtcp2_conn_get_idle_expiry(ngtcp2_conn *conn); + +ngtcp2_duration ngtcp2_conn_compute_pto(ngtcp2_conn *conn, ngtcp2_pktns *pktns); + +/* + * ngtcp2_conn_track_retired_dcid_seq tracks the sequence number |seq| + * of unacknowledged retiring Destination Connection ID. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_CONNECTION_ID_LIMIT + * The number of unacknowledged retirement exceeds the limit. + */ +int ngtcp2_conn_track_retired_dcid_seq(ngtcp2_conn *conn, uint64_t seq); + +/* + * ngtcp2_conn_untrack_retired_dcid_seq deletes the sequence number + * |seq| of unacknowledged retiring Destination Connection ID. It is + * fine if such sequence number is not found. + */ +void ngtcp2_conn_untrack_retired_dcid_seq(ngtcp2_conn *conn, uint64_t seq); + +/* + * ngtcp2_conn_server_negotiate_version negotiates QUIC version. It + * is compatible version negotiation. It returns the negotiated QUIC + * version. This function must not be called by client. + */ +uint32_t +ngtcp2_conn_server_negotiate_version(ngtcp2_conn *conn, + const ngtcp2_version_info *version_info); + +/** + * @function + * + * `ngtcp2_conn_write_connection_close_pkt` writes a packet which + * 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 + * with which the packet should be sent. Each addr field must point + * to the buffer which should be at least ``sizeof(struct + * sockaddr_storage)`` bytes long. The assignment might not be done + * if nothing is written to |dest|. + * + * If |pi| is not ``NULL``, this function stores packet metadata in it + * if it succeeds. The metadata includes ECN markings. + * + * This function must not be called from inside the callback + * functions. + * + * At the moment, successful call to this function makes connection + * close. We may change this behaviour in the future to allow + * graceful shutdown. + * + * This function returns the number of bytes written in |dest| if it + * succeeds, or one of the following negative error codes: + * + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory + * :macro:`NGTCP2_ERR_NOBUF` + * Buffer is too small + * :macro:`NGTCP2_ERR_INVALID_STATE` + * The current state does not allow sending CONNECTION_CLOSE. + * :macro:`NGTCP2_ERR_PKT_NUM_EXHAUSTED` + * Packet number is exhausted, and cannot send any more packet. + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` + * User callback failed + */ +ngtcp2_ssize ngtcp2_conn_write_connection_close_pkt( + ngtcp2_conn *conn, ngtcp2_path *path, ngtcp2_pkt_info *pi, uint8_t *dest, + size_t destlen, uint64_t error_code, const uint8_t *reason, + size_t reasonlen, ngtcp2_tstamp ts); + +/** + * @function + * + * `ngtcp2_conn_write_application_close_pkt` writes a packet which + * 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 + * with which the packet should be sent. Each addr field must point + * to the buffer which should be at least ``sizeof(struct + * sockaddr_storage)`` bytes long. The assignment might not be done + * if nothing is written to |dest|. + * + * If |pi| is not ``NULL``, this function stores packet metadata in it + * 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 + * instead. + * + * This function must not be called from inside the callback + * functions. + * + * At the moment, successful call to this function makes connection + * close. We may change this behaviour in the future to allow + * graceful shutdown. + * + * This function returns the number of bytes written in |dest| if it + * succeeds, or one of the following negative error codes: + * + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory + * :macro:`NGTCP2_ERR_NOBUF` + * Buffer is too small + * :macro:`NGTCP2_ERR_INVALID_STATE` + * The current state does not allow sending CONNECTION_CLOSE. + * :macro:`NGTCP2_ERR_PKT_NUM_EXHAUSTED` + * Packet number is exhausted, and cannot send any more packet. + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` + * User callback failed + */ +ngtcp2_ssize ngtcp2_conn_write_application_close_pkt( + ngtcp2_conn *conn, ngtcp2_path *path, ngtcp2_pkt_info *pi, uint8_t *dest, + size_t destlen, uint64_t app_error_code, const uint8_t *reason, + size_t reasonlen, ngtcp2_tstamp ts); + +int ngtcp2_conn_start_pmtud(ngtcp2_conn *conn); + +void ngtcp2_conn_stop_pmtud(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_set_remote_transport_params` sets transport parameter + * |params| from a remote endpoint to |conn|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_TRANSPORT_PARAM` + * Failed to validate a remote transport parameters. + * :macro:`NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE` + * Version negotiation failure. + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` + * User callback failed + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory. + */ +int ngtcp2_conn_set_remote_transport_params( + ngtcp2_conn *conn, const ngtcp2_transport_params *params); + +/** + * @function + * + * `ngtcp2_conn_set_0rtt_remote_transport_params` sets |params| as + * transport parameters previously received from a server. The + * parameters are used to send early data. QUIC requires that client + * application should remember transport parameters along with a + * session ticket. + * + * At least following fields should be set: + * + * - initial_max_stream_id_bidi + * - initial_max_stream_id_uni + * - initial_max_stream_data_bidi_local + * - initial_max_stream_data_bidi_remote + * - initial_max_stream_data_uni + * - initial_max_data + * - active_connection_id_limit + * - max_datagram_frame_size (if DATAGRAM extension was negotiated) + * + * The following fields are ignored: + * + * - ack_delay_exponent + * - max_ack_delay + * - initial_scid + * - original_dcid + * - preferred_address and preferred_address_present + * - retry_scid and retry_scid_present + * - stateless_reset_token and stateless_reset_token_present + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory. + */ +int ngtcp2_conn_set_0rtt_remote_transport_params( + ngtcp2_conn *conn, const ngtcp2_transport_params *params); + +/* + * ngtcp2_conn_create_ack_frame creates ACK frame, and assigns its + * pointer to |*pfr| if there are any received packets to acknowledge. + * If there are no packets to acknowledge, this function returns 0, + * and |*pfr| is untouched. The caller is advised to set |*pfr| to + * NULL before calling this function, and check it after this function + * returns. + * + * Call ngtcp2_acktr_commit_ack after a created ACK frame is + * successfully serialized into a packet. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +int ngtcp2_conn_create_ack_frame(ngtcp2_conn *conn, ngtcp2_frame **pfr, + ngtcp2_pktns *pktns, uint8_t type, + ngtcp2_tstamp ts, ngtcp2_duration ack_delay, + uint64_t ack_delay_exponent); + +#endif /* NGTCP2_CONN_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_conn_stat.h b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_conn_stat.h new file mode 100644 index 0000000..1a93867 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_conn_stat.h @@ -0,0 +1,132 @@ +/* + * ngtcp2 + * + * Copyright (c) 2023 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_STAT_H +#define NGTCP2_CONN_STAT_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +/** + * @struct + * + * :type:`ngtcp2_conn_stat` holds various connection statistics, and + * computed data for recovery and congestion controller. + */ +typedef struct ngtcp2_conn_stat { + /** + * :member:`latest_rtt` is the latest RTT sample which is not + * adjusted by acknowledgement delay. + */ + ngtcp2_duration latest_rtt; + /** + * :member:`min_rtt` is the minimum RTT seen so far. It is not + * adjusted by acknowledgement delay. + */ + ngtcp2_duration min_rtt; + /** + * :member:`smoothed_rtt` is the smoothed RTT. + */ + ngtcp2_duration smoothed_rtt; + /** + * :member:`rttvar` is a mean deviation of observed RTT. + */ + ngtcp2_duration rttvar; + /** + * :member:`initial_rtt` is the initial RTT which is used when no + * RTT sample is available. + */ + ngtcp2_duration initial_rtt; + /** + * :member:`first_rtt_sample_ts` is the timestamp when the first RTT + * sample is obtained. + */ + ngtcp2_tstamp first_rtt_sample_ts; + /** + * :member:`pto_count` is the count of successive PTO timer + * expiration. + */ + size_t pto_count; + /** + * :member:`loss_detection_timer` is the deadline of the current + * loss detection timer. + */ + ngtcp2_tstamp loss_detection_timer; + /** + * :member:`last_tx_pkt_ts` corresponds to + * time_of_last_ack_eliciting_packet in :rfc:`9002`. + */ + ngtcp2_tstamp last_tx_pkt_ts[NGTCP2_PKTNS_ID_MAX]; + /** + * :member:`loss_time` corresponds to loss_time in :rfc:`9002`. + */ + ngtcp2_tstamp loss_time[NGTCP2_PKTNS_ID_MAX]; + /** + * :member:`cwnd` is the size of congestion window. + */ + uint64_t cwnd; + /** + * :member:`ssthresh` is slow start threshold. + */ + uint64_t ssthresh; + /** + * :member:`congestion_recovery_start_ts` is the timestamp when + * congestion recovery started. + */ + ngtcp2_tstamp congestion_recovery_start_ts; + /** + * :member:`bytes_in_flight` is the number in bytes of all sent + * packets which have not been acknowledged. + */ + uint64_t bytes_in_flight; + /** + * :member:`max_tx_udp_payload_size` is the maximum size of UDP + * datagram payload that this endpoint transmits. It is used by + * congestion controller to compute congestion window. + */ + size_t max_tx_udp_payload_size; + /** + * :member:`delivery_rate_sec` is the current sending rate measured + * in byte per second. + */ + uint64_t delivery_rate_sec; + /** + * :member:`pacing_interval` is the inverse of pacing rate, which is + * the current packet sending rate computed by a congestion + * controller. 0 if a congestion controller does not set pacing + * interval. Even if this value is set to 0, the library paces + * packets. + */ + ngtcp2_duration pacing_interval; + /** + * :member:`send_quantum` is the maximum size of a data aggregate + * scheduled and transmitted together. + */ + size_t send_quantum; +} ngtcp2_conn_stat; + +#endif /* NGTCP2_CONN_STAT_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_conv.c b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_conv.c new file mode 100644 index 0000000..3367217 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_conv.c @@ -0,0 +1,291 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 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_conv.h" + +#include <string.h> +#include <assert.h> + +#include "ngtcp2_str.h" +#include "ngtcp2_pkt.h" +#include "ngtcp2_net.h" +#include "ngtcp2_unreachable.h" + +const uint8_t *ngtcp2_get_uint64(uint64_t *dest, const uint8_t *p) { + uint64_t n; + memcpy(&n, p, sizeof(n)); + *dest = ngtcp2_ntohl64(n); + return p + sizeof(n); +} + +const uint8_t *ngtcp2_get_uint48(uint64_t *dest, const uint8_t *p) { + uint64_t n = 0; + memcpy(((uint8_t *)&n) + 2, p, 6); + *dest = ngtcp2_ntohl64(n); + return p + 6; +} + +const uint8_t *ngtcp2_get_uint32(uint32_t *dest, const uint8_t *p) { + uint32_t n; + memcpy(&n, p, sizeof(n)); + *dest = ngtcp2_ntohl(n); + return p + sizeof(n); +} + +const uint8_t *ngtcp2_get_uint24(uint32_t *dest, const uint8_t *p) { + uint32_t n = 0; + memcpy(((uint8_t *)&n) + 1, p, 3); + *dest = ngtcp2_ntohl(n); + return p + 3; +} + +const uint8_t *ngtcp2_get_uint16(uint16_t *dest, const uint8_t *p) { + uint16_t n; + memcpy(&n, p, sizeof(n)); + *dest = ngtcp2_ntohs(n); + return p + sizeof(n); +} + +const uint8_t *ngtcp2_get_uint16be(uint16_t *dest, const uint8_t *p) { + memcpy(dest, p, sizeof(*dest)); + return p + sizeof(*dest); +} + +static uint64_t get_uvarint(size_t *plen, const uint8_t *p) { + union { + uint8_t n8; + uint16_t n16; + uint32_t n32; + uint64_t n64; + } n; + + *plen = (size_t)(1u << (*p >> 6)); + + switch (*plen) { + case 1: + return *p; + case 2: + memcpy(&n, p, 2); + n.n8 &= 0x3f; + return ngtcp2_ntohs(n.n16); + case 4: + memcpy(&n, p, 4); + n.n8 &= 0x3f; + return ngtcp2_ntohl(n.n32); + case 8: + memcpy(&n, p, 8); + n.n8 &= 0x3f; + return ngtcp2_ntohl64(n.n64); + default: + ngtcp2_unreachable(); + } +} + +const uint8_t *ngtcp2_get_uvarint(uint64_t *dest, const uint8_t *p) { + size_t len; + + *dest = get_uvarint(&len, p); + + return p + len; +} + +const uint8_t *ngtcp2_get_varint(int64_t *dest, const uint8_t *p) { + size_t len; + + *dest = (int64_t)get_uvarint(&len, p); + + return p + len; +} + +int64_t ngtcp2_get_pkt_num(const uint8_t *p, size_t pkt_numlen) { + uint32_t l; + uint16_t s; + + switch (pkt_numlen) { + case 1: + return *p; + case 2: + ngtcp2_get_uint16(&s, p); + return (int64_t)s; + case 3: + ngtcp2_get_uint24(&l, p); + return (int64_t)l; + case 4: + ngtcp2_get_uint32(&l, p); + return (int64_t)l; + default: + ngtcp2_unreachable(); + } +} + +uint8_t *ngtcp2_put_uint64be(uint8_t *p, uint64_t n) { + n = ngtcp2_htonl64(n); + return ngtcp2_cpymem(p, (const uint8_t *)&n, sizeof(n)); +} + +uint8_t *ngtcp2_put_uint48be(uint8_t *p, uint64_t n) { + n = ngtcp2_htonl64(n); + return ngtcp2_cpymem(p, ((const uint8_t *)&n) + 2, 6); +} + +uint8_t *ngtcp2_put_uint32be(uint8_t *p, uint32_t n) { + n = ngtcp2_htonl(n); + return ngtcp2_cpymem(p, (const uint8_t *)&n, sizeof(n)); +} + +uint8_t *ngtcp2_put_uint24be(uint8_t *p, uint32_t n) { + n = ngtcp2_htonl(n); + return ngtcp2_cpymem(p, ((const uint8_t *)&n) + 1, 3); +} + +uint8_t *ngtcp2_put_uint16be(uint8_t *p, uint16_t n) { + n = ngtcp2_htons(n); + return ngtcp2_cpymem(p, (const uint8_t *)&n, sizeof(n)); +} + +uint8_t *ngtcp2_put_uint16(uint8_t *p, uint16_t n) { + return ngtcp2_cpymem(p, (const uint8_t *)&n, sizeof(n)); +} + +uint8_t *ngtcp2_put_uvarint(uint8_t *p, uint64_t n) { + uint8_t *rv; + if (n < 64) { + *p++ = (uint8_t)n; + return p; + } + if (n < 16384) { + rv = ngtcp2_put_uint16be(p, (uint16_t)n); + *p |= 0x40; + return rv; + } + if (n < 1073741824) { + rv = ngtcp2_put_uint32be(p, (uint32_t)n); + *p |= 0x80; + return rv; + } + assert(n < 4611686018427387904ULL); + rv = ngtcp2_put_uint64be(p, n); + *p |= 0xc0; + return rv; +} + +uint8_t *ngtcp2_put_uvarint30(uint8_t *p, uint32_t n) { + uint8_t *rv; + + assert(n < 1073741824); + + rv = ngtcp2_put_uint32be(p, n); + *p |= 0x80; + + return rv; +} + +uint8_t *ngtcp2_put_pkt_num(uint8_t *p, int64_t pkt_num, size_t len) { + switch (len) { + case 1: + *p++ = (uint8_t)pkt_num; + return p; + case 2: + ngtcp2_put_uint16be(p, (uint16_t)pkt_num); + return p + 2; + case 3: + ngtcp2_put_uint24be(p, (uint32_t)pkt_num); + return p + 3; + case 4: + ngtcp2_put_uint32be(p, (uint32_t)pkt_num); + return p + 4; + default: + ngtcp2_unreachable(); + } +} + +size_t ngtcp2_get_uvarintlen(const uint8_t *p) { + return (size_t)(1u << (*p >> 6)); +} + +size_t ngtcp2_put_uvarintlen(uint64_t n) { + if (n < 64) { + return 1; + } + if (n < 16384) { + return 2; + } + if (n < 1073741824) { + return 4; + } + assert(n < 4611686018427387904ULL); + return 8; +} + +int64_t ngtcp2_nth_server_bidi_id(uint64_t n) { + if (n == 0) { + return 0; + } + + if ((NGTCP2_MAX_VARINT >> 2) < n - 1) { + return NGTCP2_MAX_SERVER_STREAM_ID_BIDI; + } + + return (int64_t)(((n - 1) << 2) | 0x01); +} + +int64_t ngtcp2_nth_client_bidi_id(uint64_t n) { + if (n == 0) { + return 0; + } + + if ((NGTCP2_MAX_VARINT >> 2) < n - 1) { + return NGTCP2_MAX_CLIENT_STREAM_ID_BIDI; + } + + return (int64_t)((n - 1) << 2); +} + +int64_t ngtcp2_nth_server_uni_id(uint64_t n) { + if (n == 0) { + return 0; + } + + if ((NGTCP2_MAX_VARINT >> 2) < n - 1) { + return NGTCP2_MAX_SERVER_STREAM_ID_UNI; + } + + return (int64_t)(((n - 1) << 2) | 0x03); +} + +int64_t ngtcp2_nth_client_uni_id(uint64_t n) { + if (n == 0) { + return 0; + } + + if ((NGTCP2_MAX_VARINT >> 2) < n - 1) { + return NGTCP2_MAX_CLIENT_STREAM_ID_UNI; + } + + return (int64_t)(((n - 1) << 2) | 0x02); +} + +uint64_t ngtcp2_ord_stream_id(int64_t stream_id) { + return (uint64_t)(stream_id >> 2) + 1; +} diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_conv.h b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_conv.h new file mode 100644 index 0000000..ef089a9 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_conv.h @@ -0,0 +1,208 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 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_CONV_H +#define NGTCP2_CONV_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +/* + * ngtcp2_get_uint64 reads 8 bytes from |p| as 64 bits unsigned + * integer encoded as network byte order, and stores it in the buffer + * pointed by |dest| in host byte order. It returns |p| + 8. + */ +const uint8_t *ngtcp2_get_uint64(uint64_t *dest, const uint8_t *p); + +/* + * ngtcp2_get_uint48 reads 6 bytes from |p| as 48 bits unsigned + * integer encoded as network byte order, and stores it in the buffer + * pointed by |dest| in host byte order. It returns |p| + 6. + */ +const uint8_t *ngtcp2_get_uint48(uint64_t *dest, const uint8_t *p); + +/* + * ngtcp2_get_uint32 reads 4 bytes from |p| as 32 bits unsigned + * integer encoded as network byte order, and stores it in the buffer + * pointed by |dest| in host byte order. It returns |p| + 4. + */ +const uint8_t *ngtcp2_get_uint32(uint32_t *dest, const uint8_t *p); + +/* + * ngtcp2_get_uint24 reads 3 bytes from |p| as 24 bits unsigned + * integer encoded as network byte order, and stores it in the buffer + * pointed by |dest| in host byte order. It returns |p| + 3. + */ +const uint8_t *ngtcp2_get_uint24(uint32_t *dest, const uint8_t *p); + +/* + * ngtcp2_get_uint16 reads 2 bytes from |p| as 16 bits unsigned + * integer encoded as network byte order, and stores it in the buffer + * pointed by |dest| in host byte order. It returns |p| + 2. + */ +const uint8_t *ngtcp2_get_uint16(uint16_t *dest, const uint8_t *p); + +/* + * ngtcp2_get_uint16be reads 2 bytes from |p| as 16 bits unsigned + * integer encoded as network byte order, and stores it in the buffer + * pointed by |dest| as is. It returns |p| + 2. + */ +const uint8_t *ngtcp2_get_uint16be(uint16_t *dest, const uint8_t *p); + +/* + * ngtcp2_get_uvarint reads variable-length unsigned integer from |p|, + * and stores it in the buffer pointed by |dest| in host byte order. + * It returns |p| plus the number of bytes read from |p|. + */ +const uint8_t *ngtcp2_get_uvarint(uint64_t *dest, const uint8_t *p); + +/* + * ngtcp2_get_varint reads variable-length unsigned integer from |p|, + * and casts it to the signed integer, and stores it in the buffer + * pointed by |dest| in host byte order. No information should be + * lost in this cast, because the variable-length integer is 62 + * bits. It returns |p| plus the number of bytes read from |p|. + */ +const uint8_t *ngtcp2_get_varint(int64_t *dest, const uint8_t *p); + +/* + * ngtcp2_get_pkt_num reads encoded packet number from |p|. The + * packet number is encoed in |pkt_numlen| bytes. + */ +int64_t ngtcp2_get_pkt_num(const uint8_t *p, size_t pkt_numlen); + +/* + * ngtcp2_put_uint64be writes |n| in host byte order in |p| in network + * byte order. It returns the one beyond of the last written + * position. + */ +uint8_t *ngtcp2_put_uint64be(uint8_t *p, uint64_t n); + +/* + * ngtcp2_put_uint48be writes |n| in host byte order in |p| in network + * byte order. It writes only least significant 48 bits. It returns + * the one beyond of the last written position. + */ +uint8_t *ngtcp2_put_uint48be(uint8_t *p, uint64_t n); + +/* + * ngtcp2_put_uint32be writes |n| in host byte order in |p| in network + * byte order. It returns the one beyond of the last written + * position. + */ +uint8_t *ngtcp2_put_uint32be(uint8_t *p, uint32_t n); + +/* + * ngtcp2_put_uint24be writes |n| in host byte order in |p| in network + * byte order. It writes only least significant 24 bits. It returns + * the one beyond of the last written position. + */ +uint8_t *ngtcp2_put_uint24be(uint8_t *p, uint32_t n); + +/* + * ngtcp2_put_uint16be writes |n| in host byte order in |p| in network + * byte order. It returns the one beyond of the last written + * position. + */ +uint8_t *ngtcp2_put_uint16be(uint8_t *p, uint16_t n); + +/* + * ngtcp2_put_uint16 writes |n| as is in |p|. It returns the one + * beyond of the last written position. + */ +uint8_t *ngtcp2_put_uint16(uint8_t *p, uint16_t n); + +/* + * ngtcp2_put_uvarint writes |n| in |p| using variable-length integer + * encoding. It returns the one beyond of the last written position. + */ +uint8_t *ngtcp2_put_uvarint(uint8_t *p, uint64_t n); + +/* + * ngtcp2_put_uvarint30 writes |n| in |p| using variable-length + * integer encoding. |n| must be strictly less than 1073741824. The + * function always encodes |n| in 4 bytes. It returns the one beyond + * of the last written position. + */ +uint8_t *ngtcp2_put_uvarint30(uint8_t *p, uint32_t n); + +/* + * ngtcp2_put_pkt_num encodes |pkt_num| using |len| bytes. It + * returns the one beyond of the last written position. + */ +uint8_t *ngtcp2_put_pkt_num(uint8_t *p, int64_t pkt_num, size_t len); + +/* + * ngtcp2_get_uvarintlen returns the required number of bytes to read + * variable-length integer starting at |p|. + */ +size_t ngtcp2_get_uvarintlen(const uint8_t *p); + +/* + * ngtcp2_put_uvarintlen returns the required number of bytes to + * encode |n|. + */ +size_t ngtcp2_put_uvarintlen(uint64_t n); + +/* + * ngtcp2_nth_server_bidi_id returns |n|-th server bidirectional + * stream ID. If |n| is 0, it returns 0. If the |n|-th stream ID is + * larger than NGTCP2_MAX_SERVER_STREAM_ID_BIDI, this function returns + * NGTCP2_MAX_SERVER_STREAM_ID_BIDI. + */ +int64_t ngtcp2_nth_server_bidi_id(uint64_t n); + +/* + * ngtcp2_nth_client_bidi_id returns |n|-th client bidirectional + * stream ID. If |n| is 0, it returns 0. If the |n|-th stream ID is + * larger than NGTCP2_MAX_CLIENT_STREAM_ID_BIDI, this function returns + * NGTCP2_MAX_CLIENT_STREAM_ID_BIDI. + */ +int64_t ngtcp2_nth_client_bidi_id(uint64_t n); + +/* + * ngtcp2_nth_server_uni_id returns |n|-th server unidirectional + * stream ID. If |n| is 0, it returns 0. If the |n|-th stream ID is + * larger than NGTCP2_MAX_SERVER_STREAM_ID_UNI, this function returns + * NGTCP2_MAX_SERVER_STREAM_ID_UNI. + */ +int64_t ngtcp2_nth_server_uni_id(uint64_t n); + +/* + * ngtcp2_nth_client_uni_id returns |n|-th client unidirectional + * stream ID. If |n| is 0, it returns 0. If the |n|-th stream ID is + * larger than NGTCP2_MAX_CLIENT_STREAM_ID_UNI, this function returns + * NGTCP2_MAX_CLIENT_STREAM_ID_UNI. + */ +int64_t ngtcp2_nth_client_uni_id(uint64_t n); + +/* + * ngtcp2_ord_stream_id returns the ordinal number of |stream_id|. + */ +uint64_t ngtcp2_ord_stream_id(int64_t stream_id); + +#endif /* NGTCP2_CONV_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_conversion.c b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_conversion.c new file mode 100644 index 0000000..eb85687 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_conversion.c @@ -0,0 +1,66 @@ +/* + * ngtcp2 + * + * Copyright (c) 2023 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_conversion.h" + +#include <string.h> +#include <assert.h> + +static void transport_params_copy(int transport_params_version, + ngtcp2_transport_params *dest, + const ngtcp2_transport_params *src) { + assert(transport_params_version != NGTCP2_TRANSPORT_PARAMS_VERSION); + + switch (transport_params_version) { + case NGTCP2_TRANSPORT_PARAMS_V1: + memcpy(dest, src, + offsetof(ngtcp2_transport_params, version_info_present) + + sizeof(src->version_info_present)); + + break; + } +} + +const ngtcp2_transport_params * +ngtcp2_transport_params_convert_to_latest(ngtcp2_transport_params *dest, + int transport_params_version, + const ngtcp2_transport_params *src) { + if (transport_params_version == NGTCP2_TRANSPORT_PARAMS_VERSION) { + return src; + } + + ngtcp2_transport_params_default(dest); + + transport_params_copy(transport_params_version, dest, src); + + return dest; +} + +void ngtcp2_transport_params_convert_to_old( + int transport_params_version, ngtcp2_transport_params *dest, + const ngtcp2_transport_params *src) { + assert(transport_params_version != NGTCP2_TRANSPORT_PARAMS_VERSION); + + transport_params_copy(transport_params_version, dest, src); +} diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_conversion.h b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_conversion.h new file mode 100644 index 0000000..3457a8f --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_conversion.h @@ -0,0 +1,71 @@ +/* + * ngtcp2 + * + * Copyright (c) 2023 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_CONVERSION_H +#define NGTCP2_CONVERSION_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +/* + * ngtcp2_transport_params_convert_to_latest converts |src| of version + * |transport_params_version| to the latest version + * NGTCP2_TRANSPORT_PARAMS_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 |transport_params_version| == NGTCP2_TRANSPORT_PARAMS_VERSION, + * no conversion is made, and |src| is returned. Otherwise, first + * |dest| is initialized via ngtcp2_transport_params_default, and then + * all valid fields in |src| are copied into |dest|. Finally, |dest| + * is returned. + */ +const ngtcp2_transport_params * +ngtcp2_transport_params_convert_to_latest(ngtcp2_transport_params *dest, + int transport_params_version, + const ngtcp2_transport_params *src); + +/* + * ngtcp2_transport_params_convert_to_old converts |src| of the latest + * version to |dest| of version |transport_params_version|. + * + * |transport_params_version| must not be the latest version + * NGTCP2_TRANSPORT_PARAMS_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 + * |transport_params_version| from |src| to |dest|. + */ +void ngtcp2_transport_params_convert_to_old(int transport_params_version, + ngtcp2_transport_params *dest, + const ngtcp2_transport_params *src); + +#endif /* NGTCP2_CONVERSION_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_crypto.c b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_crypto.c new file mode 100644 index 0000000..2c00af5 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_crypto.c @@ -0,0 +1,885 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 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_crypto.h" + +#include <string.h> +#include <assert.h> + +#include "ngtcp2_str.h" +#include "ngtcp2_conv.h" +#include "ngtcp2_conn.h" +#include "ngtcp2_net.h" +#include "ngtcp2_conversion.h" + +int ngtcp2_crypto_km_new(ngtcp2_crypto_km **pckm, const uint8_t *secret, + size_t secretlen, + const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *iv, size_t ivlen, + const ngtcp2_mem *mem) { + int rv = ngtcp2_crypto_km_nocopy_new(pckm, secretlen, ivlen, mem); + if (rv != 0) { + return rv; + } + + if (secretlen) { + memcpy((*pckm)->secret.base, secret, secretlen); + } + if (aead_ctx) { + (*pckm)->aead_ctx = *aead_ctx; + } + memcpy((*pckm)->iv.base, iv, ivlen); + + return 0; +} + +int ngtcp2_crypto_km_nocopy_new(ngtcp2_crypto_km **pckm, size_t secretlen, + size_t ivlen, const ngtcp2_mem *mem) { + size_t len; + uint8_t *p; + + len = sizeof(ngtcp2_crypto_km) + secretlen + ivlen; + + *pckm = ngtcp2_mem_malloc(mem, len); + if (*pckm == NULL) { + return NGTCP2_ERR_NOMEM; + } + + 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; + + return 0; +} + +void ngtcp2_crypto_km_del(ngtcp2_crypto_km *ckm, const ngtcp2_mem *mem) { + if (ckm == NULL) { + return; + } + + ngtcp2_mem_free(mem, ckm); +} + +void ngtcp2_crypto_create_nonce(uint8_t *dest, const uint8_t *iv, size_t ivlen, + int64_t pkt_num) { + size_t i; + uint64_t n; + + assert(ivlen >= 8); + + memcpy(dest, iv, ivlen); + n = ngtcp2_htonl64((uint64_t)pkt_num); + + for (i = 0; i < 8; ++i) { + dest[ivlen - 8 + i] ^= ((uint8_t *)&n)[i]; + } +} + +/* + * varint_paramlen returns the length of a single transport parameter + * which has variable integer in its parameter. + */ +static size_t varint_paramlen(ngtcp2_transport_param_id id, uint64_t param) { + size_t valuelen = ngtcp2_put_uvarintlen(param); + return ngtcp2_put_uvarintlen(id) + ngtcp2_put_uvarintlen(valuelen) + valuelen; +} + +/* + * write_varint_param writes parameter |id| of the given |value| in + * varint encoding. It returns p + the number of bytes written. + */ +static uint8_t *write_varint_param(uint8_t *p, ngtcp2_transport_param_id id, + uint64_t value) { + p = ngtcp2_put_uvarint(p, id); + p = ngtcp2_put_uvarint(p, ngtcp2_put_uvarintlen(value)); + return ngtcp2_put_uvarint(p, value); +} + +/* + * cid_paramlen returns the length of a single transport parameter + * which has |cid| as value. + */ +static size_t cid_paramlen(ngtcp2_transport_param_id id, + const ngtcp2_cid *cid) { + return ngtcp2_put_uvarintlen(id) + ngtcp2_put_uvarintlen(cid->datalen) + + cid->datalen; +} + +/* + * write_cid_param writes parameter |id| of the given |cid|. It + * returns p + the number of bytes written. + */ +static uint8_t *write_cid_param(uint8_t *p, ngtcp2_transport_param_id id, + const ngtcp2_cid *cid) { + assert(cid->datalen == 0 || cid->datalen >= NGTCP2_MIN_CIDLEN); + assert(cid->datalen <= NGTCP2_MAX_CIDLEN); + + p = ngtcp2_put_uvarint(p, id); + p = ngtcp2_put_uvarint(p, cid->datalen); + if (cid->datalen) { + p = ngtcp2_cpymem(p, cid->data, cid->datalen); + } + return p; +} + +static const uint8_t empty_address[16]; + +ngtcp2_ssize ngtcp2_transport_params_encode_versioned( + uint8_t *dest, size_t destlen, int transport_params_version, + const ngtcp2_transport_params *params) { + uint8_t *p; + size_t len = 0; + /* For some reason, gcc 7.3.0 requires this initialization. */ + size_t preferred_addrlen = 0; + size_t version_infolen = 0; + const ngtcp2_sockaddr_in *sa_in; + const ngtcp2_sockaddr_in6 *sa_in6; + ngtcp2_transport_params paramsbuf; + + params = ngtcp2_transport_params_convert_to_latest( + ¶msbuf, transport_params_version, params); + + if (params->original_dcid_present) { + len += + cid_paramlen(NGTCP2_TRANSPORT_PARAM_ORIGINAL_DESTINATION_CONNECTION_ID, + ¶ms->original_dcid); + } + + 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; + } + + if (params->preferred_addr_present) { + assert(params->preferred_addr.cid.datalen >= NGTCP2_MIN_CIDLEN); + assert(params->preferred_addr.cid.datalen <= NGTCP2_MAX_CIDLEN); + preferred_addrlen = 4 /* ipv4Address */ + 2 /* ipv4Port */ + + 16 /* ipv6Address */ + 2 /* ipv6Port */ + + 1 + params->preferred_addr.cid.datalen /* CID */ + + NGTCP2_STATELESS_RESET_TOKENLEN; + len += ngtcp2_put_uvarintlen(NGTCP2_TRANSPORT_PARAM_PREFERRED_ADDRESS) + + ngtcp2_put_uvarintlen(preferred_addrlen) + preferred_addrlen; + } + if (params->retry_scid_present) { + len += cid_paramlen(NGTCP2_TRANSPORT_PARAM_RETRY_SOURCE_CONNECTION_ID, + ¶ms->retry_scid); + } + + if (params->initial_scid_present) { + len += cid_paramlen(NGTCP2_TRANSPORT_PARAM_INITIAL_SOURCE_CONNECTION_ID, + ¶ms->initial_scid); + } + + if (params->initial_max_stream_data_bidi_local) { + len += varint_paramlen( + NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL, + params->initial_max_stream_data_bidi_local); + } + if (params->initial_max_stream_data_bidi_remote) { + len += varint_paramlen( + NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE, + params->initial_max_stream_data_bidi_remote); + } + if (params->initial_max_stream_data_uni) { + len += varint_paramlen(NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAM_DATA_UNI, + params->initial_max_stream_data_uni); + } + if (params->initial_max_data) { + len += varint_paramlen(NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_DATA, + params->initial_max_data); + } + if (params->initial_max_streams_bidi) { + len += varint_paramlen(NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAMS_BIDI, + params->initial_max_streams_bidi); + } + if (params->initial_max_streams_uni) { + len += varint_paramlen(NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAMS_UNI, + params->initial_max_streams_uni); + } + if (params->max_udp_payload_size != + NGTCP2_DEFAULT_MAX_RECV_UDP_PAYLOAD_SIZE) { + len += varint_paramlen(NGTCP2_TRANSPORT_PARAM_MAX_UDP_PAYLOAD_SIZE, + params->max_udp_payload_size); + } + if (params->ack_delay_exponent != NGTCP2_DEFAULT_ACK_DELAY_EXPONENT) { + len += varint_paramlen(NGTCP2_TRANSPORT_PARAM_ACK_DELAY_EXPONENT, + params->ack_delay_exponent); + } + if (params->disable_active_migration) { + len += + ngtcp2_put_uvarintlen(NGTCP2_TRANSPORT_PARAM_DISABLE_ACTIVE_MIGRATION) + + ngtcp2_put_uvarintlen(0); + } + if (params->max_ack_delay != NGTCP2_DEFAULT_MAX_ACK_DELAY) { + len += varint_paramlen(NGTCP2_TRANSPORT_PARAM_MAX_ACK_DELAY, + params->max_ack_delay / NGTCP2_MILLISECONDS); + } + if (params->max_idle_timeout) { + len += varint_paramlen(NGTCP2_TRANSPORT_PARAM_MAX_IDLE_TIMEOUT, + params->max_idle_timeout / NGTCP2_MILLISECONDS); + } + if (params->active_connection_id_limit && + params->active_connection_id_limit != + NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT) { + len += varint_paramlen(NGTCP2_TRANSPORT_PARAM_ACTIVE_CONNECTION_ID_LIMIT, + params->active_connection_id_limit); + } + if (params->max_datagram_frame_size) { + len += varint_paramlen(NGTCP2_TRANSPORT_PARAM_MAX_DATAGRAM_FRAME_SIZE, + params->max_datagram_frame_size); + } + if (params->grease_quic_bit) { + len += ngtcp2_put_uvarintlen(NGTCP2_TRANSPORT_PARAM_GREASE_QUIC_BIT) + + ngtcp2_put_uvarintlen(0); + } + if (params->version_info_present) { + version_infolen = + sizeof(uint32_t) + params->version_info.available_versionslen; + len += ngtcp2_put_uvarintlen(NGTCP2_TRANSPORT_PARAM_VERSION_INFORMATION) + + ngtcp2_put_uvarintlen(version_infolen) + version_infolen; + } + + if (dest == NULL && destlen == 0) { + return (ngtcp2_ssize)len; + } + + if (destlen < len) { + return NGTCP2_ERR_NOBUF; + } + + p = dest; + + if (params->original_dcid_present) { + p = write_cid_param( + p, NGTCP2_TRANSPORT_PARAM_ORIGINAL_DESTINATION_CONNECTION_ID, + ¶ms->original_dcid); + } + + if (params->stateless_reset_token_present) { + p = ngtcp2_put_uvarint(p, NGTCP2_TRANSPORT_PARAM_STATELESS_RESET_TOKEN); + p = ngtcp2_put_uvarint(p, sizeof(params->stateless_reset_token)); + p = ngtcp2_cpymem(p, params->stateless_reset_token, + sizeof(params->stateless_reset_token)); + } + + if (params->preferred_addr_present) { + p = ngtcp2_put_uvarint(p, NGTCP2_TRANSPORT_PARAM_PREFERRED_ADDRESS); + p = ngtcp2_put_uvarint(p, preferred_addrlen); + + if (params->preferred_addr.ipv4_present) { + sa_in = ¶ms->preferred_addr.ipv4; + p = ngtcp2_cpymem(p, &sa_in->sin_addr, sizeof(sa_in->sin_addr)); + p = ngtcp2_put_uint16(p, sa_in->sin_port); + } else { + p = ngtcp2_cpymem(p, empty_address, sizeof(sa_in->sin_addr)); + p = ngtcp2_put_uint16(p, 0); + } + + if (params->preferred_addr.ipv6_present) { + sa_in6 = ¶ms->preferred_addr.ipv6; + p = ngtcp2_cpymem(p, &sa_in6->sin6_addr, sizeof(sa_in6->sin6_addr)); + p = ngtcp2_put_uint16(p, sa_in6->sin6_port); + } else { + p = ngtcp2_cpymem(p, empty_address, sizeof(sa_in6->sin6_addr)); + p = ngtcp2_put_uint16(p, 0); + } + + *p++ = (uint8_t)params->preferred_addr.cid.datalen; + if (params->preferred_addr.cid.datalen) { + p = ngtcp2_cpymem(p, params->preferred_addr.cid.data, + params->preferred_addr.cid.datalen); + } + p = ngtcp2_cpymem(p, params->preferred_addr.stateless_reset_token, + sizeof(params->preferred_addr.stateless_reset_token)); + } + + if (params->retry_scid_present) { + p = write_cid_param(p, NGTCP2_TRANSPORT_PARAM_RETRY_SOURCE_CONNECTION_ID, + ¶ms->retry_scid); + } + + if (params->initial_scid_present) { + p = write_cid_param(p, NGTCP2_TRANSPORT_PARAM_INITIAL_SOURCE_CONNECTION_ID, + ¶ms->initial_scid); + } + + if (params->initial_max_stream_data_bidi_local) { + p = write_varint_param( + p, NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL, + params->initial_max_stream_data_bidi_local); + } + + if (params->initial_max_stream_data_bidi_remote) { + p = write_varint_param( + p, NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE, + params->initial_max_stream_data_bidi_remote); + } + + if (params->initial_max_stream_data_uni) { + p = write_varint_param(p, + NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAM_DATA_UNI, + params->initial_max_stream_data_uni); + } + + if (params->initial_max_data) { + p = write_varint_param(p, NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_DATA, + params->initial_max_data); + } + + if (params->initial_max_streams_bidi) { + p = write_varint_param(p, NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAMS_BIDI, + params->initial_max_streams_bidi); + } + + if (params->initial_max_streams_uni) { + p = write_varint_param(p, NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAMS_UNI, + params->initial_max_streams_uni); + } + + if (params->max_udp_payload_size != + NGTCP2_DEFAULT_MAX_RECV_UDP_PAYLOAD_SIZE) { + p = write_varint_param(p, NGTCP2_TRANSPORT_PARAM_MAX_UDP_PAYLOAD_SIZE, + params->max_udp_payload_size); + } + + if (params->ack_delay_exponent != NGTCP2_DEFAULT_ACK_DELAY_EXPONENT) { + p = write_varint_param(p, NGTCP2_TRANSPORT_PARAM_ACK_DELAY_EXPONENT, + params->ack_delay_exponent); + } + + if (params->disable_active_migration) { + p = ngtcp2_put_uvarint(p, NGTCP2_TRANSPORT_PARAM_DISABLE_ACTIVE_MIGRATION); + p = ngtcp2_put_uvarint(p, 0); + } + + if (params->max_ack_delay != NGTCP2_DEFAULT_MAX_ACK_DELAY) { + p = write_varint_param(p, NGTCP2_TRANSPORT_PARAM_MAX_ACK_DELAY, + params->max_ack_delay / NGTCP2_MILLISECONDS); + } + + if (params->max_idle_timeout) { + p = write_varint_param(p, NGTCP2_TRANSPORT_PARAM_MAX_IDLE_TIMEOUT, + params->max_idle_timeout / NGTCP2_MILLISECONDS); + } + + if (params->active_connection_id_limit && + params->active_connection_id_limit != + NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT) { + p = write_varint_param(p, NGTCP2_TRANSPORT_PARAM_ACTIVE_CONNECTION_ID_LIMIT, + params->active_connection_id_limit); + } + + if (params->max_datagram_frame_size) { + p = write_varint_param(p, NGTCP2_TRANSPORT_PARAM_MAX_DATAGRAM_FRAME_SIZE, + params->max_datagram_frame_size); + } + + if (params->grease_quic_bit) { + p = ngtcp2_put_uvarint(p, NGTCP2_TRANSPORT_PARAM_GREASE_QUIC_BIT); + p = ngtcp2_put_uvarint(p, 0); + } + + if (params->version_info_present) { + p = ngtcp2_put_uvarint(p, NGTCP2_TRANSPORT_PARAM_VERSION_INFORMATION); + p = ngtcp2_put_uvarint(p, version_infolen); + p = ngtcp2_put_uint32be(p, params->version_info.chosen_version); + if (params->version_info.available_versionslen) { + p = ngtcp2_cpymem(p, params->version_info.available_versions, + params->version_info.available_versionslen); + } + } + + assert((size_t)(p - dest) == len); + + return (ngtcp2_ssize)len; +} + +/* + * decode_varint decodes a single varint from the buffer pointed by + * |*pp| of length |end - *pp|. If it decodes an integer + * successfully, it stores the integer in |*pdest|, increment |*pp| by + * the number of bytes read from |*pp|, and returns 0. Otherwise it + * returns -1. + */ +static int decode_varint(uint64_t *pdest, const uint8_t **pp, + const uint8_t *end) { + const uint8_t *p = *pp; + size_t len; + + if (p == end) { + return -1; + } + + len = ngtcp2_get_uvarintlen(p); + if ((uint64_t)(end - p) < len) { + return -1; + } + + *pp = ngtcp2_get_uvarint(pdest, p); + + return 0; +} + +/* + * decode_varint_param decodes length prefixed value from the buffer + * pointed by |*pp| of length |end - *pp|. The length and value are + * encoded in varint form. If it decodes a value successfully, it + * stores the value in |*pdest|, increment |*pp| by the number of + * bytes read from |*pp|, and returns 0. Otherwise it returns -1. + */ +static int decode_varint_param(uint64_t *pdest, const uint8_t **pp, + const uint8_t *end) { + const uint8_t *p = *pp; + uint64_t valuelen; + + if (decode_varint(&valuelen, &p, end) != 0) { + return -1; + } + + if (p == end) { + return -1; + } + + if ((uint64_t)(end - p) < valuelen) { + return -1; + } + + if (ngtcp2_get_uvarintlen(p) != valuelen) { + return -1; + } + + *pp = ngtcp2_get_uvarint(pdest, p); + + return 0; +} + +/* + * decode_cid_param decodes length prefixed ngtcp2_cid from the buffer + * pointed by |*pp| of length |end - *pp|. The length is encoded in + * varint form. If it decodes a value successfully, it stores the + * value in |*pdest|, increment |*pp| by the number of read from + * |*pp|, and returns the number of bytes read. Otherwise it returns + * the one of the negative error code: + * + * NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM + * Could not decode Connection ID. + */ +static int decode_cid_param(ngtcp2_cid *pdest, const uint8_t **pp, + const uint8_t *end) { + const uint8_t *p = *pp; + uint64_t valuelen; + + if (decode_varint(&valuelen, &p, end) != 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + + if ((valuelen != 0 && valuelen < NGTCP2_MIN_CIDLEN) || + valuelen > NGTCP2_MAX_CIDLEN || (size_t)(end - p) < valuelen) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + + ngtcp2_cid_init(pdest, p, (size_t)valuelen); + + p += valuelen; + + *pp = p; + + return 0; +} + +int ngtcp2_transport_params_decode_versioned(int transport_params_version, + ngtcp2_transport_params *dest, + const uint8_t *data, + size_t datalen) { + const uint8_t *p, *end, *lend; + size_t len; + uint64_t param_type; + uint64_t valuelen; + int rv; + ngtcp2_sockaddr_in *sa_in; + ngtcp2_sockaddr_in6 *sa_in6; + uint32_t version; + ngtcp2_transport_params *params, paramsbuf; + + if (transport_params_version == NGTCP2_TRANSPORT_PARAMS_VERSION) { + params = dest; + } else { + params = ¶msbuf; + } + + /* Set default values */ + memset(params, 0, sizeof(*params)); + params->original_dcid_present = 0; + params->initial_scid_present = 0; + params->initial_max_streams_bidi = 0; + params->initial_max_streams_uni = 0; + params->initial_max_stream_data_bidi_local = 0; + params->initial_max_stream_data_bidi_remote = 0; + params->initial_max_stream_data_uni = 0; + params->max_udp_payload_size = NGTCP2_DEFAULT_MAX_RECV_UDP_PAYLOAD_SIZE; + params->ack_delay_exponent = NGTCP2_DEFAULT_ACK_DELAY_EXPONENT; + params->stateless_reset_token_present = 0; + params->preferred_addr_present = 0; + params->disable_active_migration = 0; + params->max_ack_delay = NGTCP2_DEFAULT_MAX_ACK_DELAY; + params->max_idle_timeout = 0; + params->active_connection_id_limit = + NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT; + params->retry_scid_present = 0; + params->max_datagram_frame_size = 0; + memset(¶ms->retry_scid, 0, sizeof(params->retry_scid)); + memset(¶ms->initial_scid, 0, sizeof(params->initial_scid)); + memset(¶ms->original_dcid, 0, sizeof(params->original_dcid)); + params->version_info_present = 0; + + p = data; + end = data + datalen; + + for (; (size_t)(end - p) >= 2;) { + if (decode_varint(¶m_type, &p, end) != 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + + switch (param_type) { + case NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL: + if (decode_varint_param(¶ms->initial_max_stream_data_bidi_local, &p, + end) != 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + break; + case NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE: + if (decode_varint_param(¶ms->initial_max_stream_data_bidi_remote, &p, + end) != 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + break; + case NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAM_DATA_UNI: + if (decode_varint_param(¶ms->initial_max_stream_data_uni, &p, end) != + 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + break; + case NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_DATA: + if (decode_varint_param(¶ms->initial_max_data, &p, end) != 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + break; + case NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAMS_BIDI: + if (decode_varint_param(¶ms->initial_max_streams_bidi, &p, end) != + 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + if (params->initial_max_streams_bidi > NGTCP2_MAX_STREAMS) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + break; + case NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAMS_UNI: + if (decode_varint_param(¶ms->initial_max_streams_uni, &p, end) != 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + if (params->initial_max_streams_uni > NGTCP2_MAX_STREAMS) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + break; + case NGTCP2_TRANSPORT_PARAM_MAX_IDLE_TIMEOUT: + if (decode_varint_param(¶ms->max_idle_timeout, &p, end) != 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + params->max_idle_timeout *= NGTCP2_MILLISECONDS; + break; + case NGTCP2_TRANSPORT_PARAM_MAX_UDP_PAYLOAD_SIZE: + if (decode_varint_param(¶ms->max_udp_payload_size, &p, end) != 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + break; + case NGTCP2_TRANSPORT_PARAM_STATELESS_RESET_TOKEN: + if (decode_varint(&valuelen, &p, end) != 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + if ((size_t)valuelen != sizeof(params->stateless_reset_token)) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + if ((size_t)(end - p) < sizeof(params->stateless_reset_token)) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + + p = ngtcp2_get_bytes(params->stateless_reset_token, p, + sizeof(params->stateless_reset_token)); + params->stateless_reset_token_present = 1; + + break; + case NGTCP2_TRANSPORT_PARAM_ACK_DELAY_EXPONENT: + if (decode_varint_param(¶ms->ack_delay_exponent, &p, end) != 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + if (params->ack_delay_exponent > 20) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + break; + case NGTCP2_TRANSPORT_PARAM_PREFERRED_ADDRESS: + if (decode_varint(&valuelen, &p, end) != 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + if ((size_t)(end - p) < valuelen) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + len = 4 /* ipv4Address */ + 2 /* ipv4Port */ + 16 /* ipv6Address */ + + 2 /* ipv6Port */ + + 1 /* cid length */ + NGTCP2_STATELESS_RESET_TOKENLEN; + if (valuelen < len) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + + sa_in = ¶ms->preferred_addr.ipv4; + + p = ngtcp2_get_bytes(&sa_in->sin_addr, p, sizeof(sa_in->sin_addr)); + p = ngtcp2_get_uint16be(&sa_in->sin_port, p); + + if (sa_in->sin_port || memcmp(empty_address, &sa_in->sin_addr, + sizeof(sa_in->sin_addr)) != 0) { + sa_in->sin_family = NGTCP2_AF_INET; + params->preferred_addr.ipv4_present = 1; + } + + sa_in6 = ¶ms->preferred_addr.ipv6; + + p = ngtcp2_get_bytes(&sa_in6->sin6_addr, p, sizeof(sa_in6->sin6_addr)); + p = ngtcp2_get_uint16be(&sa_in6->sin6_port, p); + + if (sa_in6->sin6_port || memcmp(empty_address, &sa_in6->sin6_addr, + sizeof(sa_in6->sin6_addr)) != 0) { + sa_in6->sin6_family = NGTCP2_AF_INET6; + params->preferred_addr.ipv6_present = 1; + } + + /* cid */ + params->preferred_addr.cid.datalen = *p++; + len += params->preferred_addr.cid.datalen; + if (valuelen != len || + params->preferred_addr.cid.datalen > NGTCP2_MAX_CIDLEN || + params->preferred_addr.cid.datalen < NGTCP2_MIN_CIDLEN) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + if (params->preferred_addr.cid.datalen) { + p = ngtcp2_get_bytes(params->preferred_addr.cid.data, p, + params->preferred_addr.cid.datalen); + } + + /* stateless reset token */ + p = ngtcp2_get_bytes( + params->preferred_addr.stateless_reset_token, p, + sizeof(params->preferred_addr.stateless_reset_token)); + params->preferred_addr_present = 1; + break; + case NGTCP2_TRANSPORT_PARAM_DISABLE_ACTIVE_MIGRATION: + if (decode_varint(&valuelen, &p, end) != 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + if (valuelen != 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + params->disable_active_migration = 1; + break; + case NGTCP2_TRANSPORT_PARAM_ORIGINAL_DESTINATION_CONNECTION_ID: + rv = decode_cid_param(¶ms->original_dcid, &p, end); + if (rv != 0) { + return rv; + } + params->original_dcid_present = 1; + break; + case NGTCP2_TRANSPORT_PARAM_RETRY_SOURCE_CONNECTION_ID: + rv = decode_cid_param(¶ms->retry_scid, &p, end); + if (rv != 0) { + return rv; + } + params->retry_scid_present = 1; + break; + case NGTCP2_TRANSPORT_PARAM_INITIAL_SOURCE_CONNECTION_ID: + rv = decode_cid_param(¶ms->initial_scid, &p, end); + if (rv != 0) { + return rv; + } + params->initial_scid_present = 1; + break; + case NGTCP2_TRANSPORT_PARAM_MAX_ACK_DELAY: + if (decode_varint_param(¶ms->max_ack_delay, &p, end) != 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + if (params->max_ack_delay >= 16384) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + params->max_ack_delay *= NGTCP2_MILLISECONDS; + break; + case NGTCP2_TRANSPORT_PARAM_ACTIVE_CONNECTION_ID_LIMIT: + if (decode_varint_param(¶ms->active_connection_id_limit, &p, end) != + 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + break; + case NGTCP2_TRANSPORT_PARAM_MAX_DATAGRAM_FRAME_SIZE: + if (decode_varint_param(¶ms->max_datagram_frame_size, &p, end) != 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + break; + case NGTCP2_TRANSPORT_PARAM_GREASE_QUIC_BIT: + if (decode_varint(&valuelen, &p, end) != 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + if (valuelen != 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + params->grease_quic_bit = 1; + break; + case NGTCP2_TRANSPORT_PARAM_VERSION_INFORMATION: + if (decode_varint(&valuelen, &p, end) != 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + if ((size_t)(end - p) < valuelen) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + if (valuelen < sizeof(uint32_t) || (valuelen & 0x3)) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + p = ngtcp2_get_uint32(¶ms->version_info.chosen_version, p); + if (params->version_info.chosen_version == 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + if (valuelen > sizeof(uint32_t)) { + params->version_info.available_versions = (uint8_t *)p; + params->version_info.available_versionslen = + (size_t)valuelen - sizeof(uint32_t); + + for (lend = p + (valuelen - sizeof(uint32_t)); p != lend;) { + p = ngtcp2_get_uint32(&version, p); + if (version == 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + } + } + params->version_info_present = 1; + break; + default: + /* Ignore unknown parameter */ + if (decode_varint(&valuelen, &p, end) != 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + if ((size_t)(end - p) < valuelen) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + p += valuelen; + break; + } + } + + if (end - p != 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + + if (transport_params_version != NGTCP2_TRANSPORT_PARAMS_VERSION) { + ngtcp2_transport_params_convert_to_old(transport_params_version, dest, + params); + } + + return 0; +} + +static int transport_params_copy_new(ngtcp2_transport_params **pdest, + const ngtcp2_transport_params *src, + const ngtcp2_mem *mem) { + size_t len = sizeof(**pdest); + ngtcp2_transport_params *dest; + uint8_t *p; + + if (src->version_info_present) { + len += src->version_info.available_versionslen; + } + + dest = ngtcp2_mem_malloc(mem, len); + if (dest == NULL) { + return NGTCP2_ERR_NOMEM; + } + + *dest = *src; + + if (src->version_info_present && src->version_info.available_versionslen) { + p = (uint8_t *)dest + sizeof(*dest); + memcpy(p, src->version_info.available_versions, + src->version_info.available_versionslen); + dest->version_info.available_versions = p; + } + + *pdest = dest; + + return 0; +} + +int ngtcp2_transport_params_decode_new(ngtcp2_transport_params **pparams, + const uint8_t *data, size_t datalen, + const ngtcp2_mem *mem) { + int rv; + ngtcp2_transport_params params; + + rv = ngtcp2_transport_params_decode(¶ms, data, datalen); + if (rv < 0) { + return rv; + } + + if (mem == NULL) { + mem = ngtcp2_mem_default(); + } + + return transport_params_copy_new(pparams, ¶ms, mem); +} + +void ngtcp2_transport_params_del(ngtcp2_transport_params *params, + const ngtcp2_mem *mem) { + if (params == NULL) { + return; + } + + if (mem == NULL) { + mem = ngtcp2_mem_default(); + } + + ngtcp2_mem_free(mem, params); +} + +int ngtcp2_transport_params_copy_new(ngtcp2_transport_params **pdest, + const ngtcp2_transport_params *src, + const ngtcp2_mem *mem) { + if (src == NULL) { + *pdest = NULL; + return 0; + } + + return transport_params_copy_new(pdest, src, mem); +} diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_crypto.h b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_crypto.h new file mode 100644 index 0000000..b78429b --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_crypto.h @@ -0,0 +1,147 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 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_CRYPTO_H +#define NGTCP2_CRYPTO_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_mem.h" + +/* NGTCP2_INITIAL_AEAD_OVERHEAD is an overhead of AEAD used by Initial + packets. Because QUIC uses AEAD_AES_128_GCM, the overhead is 16 + bytes. */ +#define NGTCP2_INITIAL_AEAD_OVERHEAD 16 + +/* NGTCP2_MAX_AEAD_OVERHEAD is expected maximum AEAD overhead. */ +#define NGTCP2_MAX_AEAD_OVERHEAD 16 + +/* ngtcp2_transport_param_id is the registry of QUIC transport + parameter ID. */ +typedef uint64_t ngtcp2_transport_param_id; + +#define NGTCP2_TRANSPORT_PARAM_ORIGINAL_DESTINATION_CONNECTION_ID 0x00 +#define NGTCP2_TRANSPORT_PARAM_MAX_IDLE_TIMEOUT 0x01 +#define NGTCP2_TRANSPORT_PARAM_STATELESS_RESET_TOKEN 0x02 +#define NGTCP2_TRANSPORT_PARAM_MAX_UDP_PAYLOAD_SIZE 0x03 +#define NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_DATA 0x04 +#define NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL 0x05 +#define NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE 0x06 +#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_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 +/* https://datatracker.ietf.org/doc/html/rfc9368 */ +#define NGTCP2_TRANSPORT_PARAM_VERSION_INFORMATION 0x11 + +/* NGTCP2_CRYPTO_KM_FLAG_NONE indicates that no flag is set. */ +#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 + +typedef struct ngtcp2_crypto_km { + ngtcp2_vec secret; + ngtcp2_crypto_aead_ctx aead_ctx; + ngtcp2_vec iv; + /* pkt_num is a packet number of a packet which uses this keying + material. For encryption key, it is the lowest packet number of + a packet. For decryption key, it is the lowest packet number of + a packet which can be decrypted with this keying material. */ + int64_t pkt_num; + /* use_count is the number of encryption applied with this key. + This field is only used for tx key. */ + uint64_t use_count; + /* flags is the bitwise OR of zero or more of + NGTCP2_CRYPTO_KM_FLAG_*. */ + uint8_t flags; +} ngtcp2_crypto_km; + +/* + * ngtcp2_crypto_km_new creates new ngtcp2_crypto_km object and + * assigns its pointer to |*pckm|. The |secret| of length + * |secretlen|, the |key| of length |keylen| and the |iv| of length + * |ivlen| are copied to |*pckm|. If |secretlen| == 0, the function + * assumes no secret is given which is acceptable. The sole reason to + * store secret is update keys. Only 1RTT key can be updated. + */ +int ngtcp2_crypto_km_new(ngtcp2_crypto_km **pckm, const uint8_t *secret, + size_t secretlen, + const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *iv, size_t ivlen, + const ngtcp2_mem *mem); + +/* + * ngtcp2_crypto_km_nocopy_new is similar to ngtcp2_crypto_km_new, but + * it does not copy secret, key and IV. + */ +int ngtcp2_crypto_km_nocopy_new(ngtcp2_crypto_km **pckm, size_t secretlen, + size_t ivlen, const ngtcp2_mem *mem); + +void ngtcp2_crypto_km_del(ngtcp2_crypto_km *ckm, const ngtcp2_mem *mem); + +typedef struct ngtcp2_crypto_cc { + ngtcp2_crypto_aead aead; + ngtcp2_crypto_cipher hp; + ngtcp2_crypto_km *ckm; + ngtcp2_crypto_cipher_ctx hp_ctx; + ngtcp2_encrypt encrypt; + ngtcp2_decrypt decrypt; + ngtcp2_hp_mask hp_mask; +} ngtcp2_crypto_cc; + +void ngtcp2_crypto_create_nonce(uint8_t *dest, const uint8_t *iv, size_t ivlen, + int64_t pkt_num); + +/* + * ngtcp2_transport_params_copy_new makes a copy of |src|, and assigns + * it to |*pdest|. If |src| is NULL, NULL is assigned to |*pdest|. + * + * Caller is responsible to call ngtcp2_transport_params_del to free + * the memory assigned to |*pdest|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +int ngtcp2_transport_params_copy_new(ngtcp2_transport_params **pdest, + const ngtcp2_transport_params *src, + const ngtcp2_mem *mem); + +#endif /* NGTCP2_CRYPTO_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_err.c b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_err.c new file mode 100644 index 0000000..5e4794c --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_err.c @@ -0,0 +1,157 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 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_err.h" + +const char *ngtcp2_strerror(int liberr) { + switch (liberr) { + case 0: + return "NO_ERROR"; + case NGTCP2_ERR_INVALID_ARGUMENT: + return "ERR_INVALID_ARGUMENT"; + case NGTCP2_ERR_NOBUF: + return "ERR_NOBUF"; + case NGTCP2_ERR_PROTO: + return "ERR_PROTO"; + case NGTCP2_ERR_INVALID_STATE: + return "ERR_INVALID_STATE"; + case NGTCP2_ERR_ACK_FRAME: + return "ERR_ACK_FRAME"; + case NGTCP2_ERR_STREAM_ID_BLOCKED: + return "ERR_STREAM_ID_BLOCKED"; + case NGTCP2_ERR_STREAM_IN_USE: + return "ERR_STREAM_IN_USE"; + case NGTCP2_ERR_STREAM_DATA_BLOCKED: + return "ERR_STREAM_DATA_BLOCKED"; + case NGTCP2_ERR_FLOW_CONTROL: + return "ERR_FLOW_CONTROL"; + case NGTCP2_ERR_CONNECTION_ID_LIMIT: + return "ERR_CONNECTION_ID_LIMIT"; + case NGTCP2_ERR_STREAM_LIMIT: + return "ERR_STREAM_LIMIT"; + case NGTCP2_ERR_FINAL_SIZE: + return "ERR_FINAL_SIZE"; + case NGTCP2_ERR_CRYPTO: + return "ERR_CRYPTO"; + case NGTCP2_ERR_PKT_NUM_EXHAUSTED: + return "ERR_PKT_NUM_EXHAUSTED"; + case NGTCP2_ERR_NOMEM: + return "ERR_NOMEM"; + case NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM: + return "ERR_REQUIRED_TRANSPORT_PARAM"; + case NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM: + return "ERR_MALFORMED_TRANSPORT_PARAM"; + case NGTCP2_ERR_FRAME_ENCODING: + return "ERR_FRAME_ENCODING"; + case NGTCP2_ERR_DECRYPT: + return "ERR_DECRYPT"; + case NGTCP2_ERR_STREAM_SHUT_WR: + return "ERR_STREAM_SHUT_WR"; + case NGTCP2_ERR_STREAM_NOT_FOUND: + return "ERR_STREAM_NOT_FOUND"; + case NGTCP2_ERR_STREAM_STATE: + return "ERR_STREAM_STATE"; + case NGTCP2_ERR_RECV_VERSION_NEGOTIATION: + return "ERR_RECV_VERSION_NEGOTIATION"; + case NGTCP2_ERR_CLOSING: + return "ERR_CLOSING"; + case NGTCP2_ERR_DRAINING: + return "ERR_DRAINING"; + case NGTCP2_ERR_TRANSPORT_PARAM: + return "ERR_TRANSPORT_PARAM"; + case NGTCP2_ERR_DISCARD_PKT: + return "ERR_DISCARD_PKT"; + case NGTCP2_ERR_CONN_ID_BLOCKED: + return "ERR_CONN_ID_BLOCKED"; + case NGTCP2_ERR_CALLBACK_FAILURE: + return "ERR_CALLBACK_FAILURE"; + case NGTCP2_ERR_INTERNAL: + return "ERR_INTERNAL"; + case NGTCP2_ERR_CRYPTO_BUFFER_EXCEEDED: + return "ERR_CRYPTO_BUFFER_EXCEEDED"; + case NGTCP2_ERR_WRITE_MORE: + return "ERR_WRITE_MORE"; + case NGTCP2_ERR_RETRY: + return "ERR_RETRY"; + case NGTCP2_ERR_DROP_CONN: + return "ERR_DROP_CONN"; + case NGTCP2_ERR_AEAD_LIMIT_REACHED: + return "ERR_AEAD_LIMIT_REACHED"; + case NGTCP2_ERR_NO_VIABLE_PATH: + return "ERR_NO_VIABLE_PATH"; + case NGTCP2_ERR_VERSION_NEGOTIATION: + return "ERR_VERSION_NEGOTIATION"; + case NGTCP2_ERR_HANDSHAKE_TIMEOUT: + return "ERR_HANDSHAKE_TIMEOUT"; + case NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE: + return "ERR_VERSION_NEGOTIATION_FAILURE"; + case NGTCP2_ERR_IDLE_CLOSE: + return "ERR_IDLE_CLOSE"; + default: + return "(unknown)"; + } +} + +int ngtcp2_err_is_fatal(int liberr) { return liberr < NGTCP2_ERR_FATAL; } + +uint64_t ngtcp2_err_infer_quic_transport_error_code(int liberr) { + switch (liberr) { + case 0: + return NGTCP2_NO_ERROR; + case NGTCP2_ERR_ACK_FRAME: + case NGTCP2_ERR_FRAME_ENCODING: + return NGTCP2_FRAME_ENCODING_ERROR; + case NGTCP2_ERR_FLOW_CONTROL: + return NGTCP2_FLOW_CONTROL_ERROR; + case NGTCP2_ERR_CONNECTION_ID_LIMIT: + return NGTCP2_CONNECTION_ID_LIMIT_ERROR; + case NGTCP2_ERR_STREAM_LIMIT: + return NGTCP2_STREAM_LIMIT_ERROR; + case NGTCP2_ERR_FINAL_SIZE: + return NGTCP2_FINAL_SIZE_ERROR; + case NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM: + case NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM: + case NGTCP2_ERR_TRANSPORT_PARAM: + return NGTCP2_TRANSPORT_PARAMETER_ERROR; + case NGTCP2_ERR_INVALID_ARGUMENT: + case NGTCP2_ERR_NOMEM: + case NGTCP2_ERR_CALLBACK_FAILURE: + case NGTCP2_ERR_HANDSHAKE_TIMEOUT: + case NGTCP2_ERR_PKT_NUM_EXHAUSTED: + case NGTCP2_ERR_INTERNAL: + return NGTCP2_INTERNAL_ERROR; + case NGTCP2_ERR_STREAM_STATE: + return NGTCP2_STREAM_STATE_ERROR; + case NGTCP2_ERR_CRYPTO_BUFFER_EXCEEDED: + return NGTCP2_CRYPTO_BUFFER_EXCEEDED; + case NGTCP2_ERR_AEAD_LIMIT_REACHED: + return NGTCP2_AEAD_LIMIT_REACHED; + case NGTCP2_ERR_NO_VIABLE_PATH: + return NGTCP2_NO_VIABLE_PATH; + case NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE: + return NGTCP2_VERSION_NEGOTIATION_ERROR; + default: + return NGTCP2_PROTOCOL_VIOLATION; + } +} diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_err.h b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_err.h new file mode 100644 index 0000000..9229f54 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_err.h @@ -0,0 +1,34 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 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_ERR_H +#define NGTCP2_ERR_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#endif /* NGTCP2_ERR_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_frame_chain.c b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_frame_chain.c new file mode 100644 index 0000000..41c2a6a --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_frame_chain.c @@ -0,0 +1,220 @@ +/* + * ngtcp2 + * + * Copyright (c) 2023 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_frame_chain.h" + +#include <string.h> +#include <assert.h> + +ngtcp2_objalloc_def(frame_chain, ngtcp2_frame_chain, oplent); + +int ngtcp2_frame_chain_new(ngtcp2_frame_chain **pfrc, const ngtcp2_mem *mem) { + *pfrc = ngtcp2_mem_malloc(mem, sizeof(ngtcp2_frame_chain)); + if (*pfrc == NULL) { + return NGTCP2_ERR_NOMEM; + } + + ngtcp2_frame_chain_init(*pfrc); + + return 0; +} + +int ngtcp2_frame_chain_objalloc_new(ngtcp2_frame_chain **pfrc, + ngtcp2_objalloc *objalloc) { + *pfrc = ngtcp2_objalloc_frame_chain_get(objalloc); + if (*pfrc == NULL) { + return NGTCP2_ERR_NOMEM; + } + + ngtcp2_frame_chain_init(*pfrc); + + return 0; +} + +int ngtcp2_frame_chain_extralen_new(ngtcp2_frame_chain **pfrc, size_t extralen, + const ngtcp2_mem *mem) { + *pfrc = ngtcp2_mem_malloc(mem, sizeof(ngtcp2_frame_chain) + extralen); + if (*pfrc == NULL) { + return NGTCP2_ERR_NOMEM; + } + + ngtcp2_frame_chain_init(*pfrc); + + return 0; +} + +int ngtcp2_frame_chain_stream_datacnt_objalloc_new(ngtcp2_frame_chain **pfrc, + size_t datacnt, + ngtcp2_objalloc *objalloc, + const ngtcp2_mem *mem) { + size_t need, avail = sizeof(ngtcp2_frame) - sizeof(ngtcp2_stream); + + if (datacnt > 1) { + need = sizeof(ngtcp2_vec) * (datacnt - 1); + + if (need > avail) { + return ngtcp2_frame_chain_extralen_new(pfrc, need - avail, mem); + } + } + + return ngtcp2_frame_chain_objalloc_new(pfrc, objalloc); +} + +int ngtcp2_frame_chain_new_token_objalloc_new(ngtcp2_frame_chain **pfrc, + const uint8_t *token, + size_t tokenlen, + ngtcp2_objalloc *objalloc, + const ngtcp2_mem *mem) { + size_t avail = sizeof(ngtcp2_frame) - sizeof(ngtcp2_new_token); + int rv; + uint8_t *p; + ngtcp2_frame *fr; + + if (tokenlen > avail) { + rv = ngtcp2_frame_chain_extralen_new(pfrc, tokenlen - avail, mem); + } else { + rv = ngtcp2_frame_chain_objalloc_new(pfrc, objalloc); + } + if (rv != 0) { + return rv; + } + + fr = &(*pfrc)->fr; + fr->type = NGTCP2_FRAME_NEW_TOKEN; + + p = (uint8_t *)fr + sizeof(ngtcp2_new_token); + memcpy(p, token, tokenlen); + + fr->new_token.token = p; + fr->new_token.tokenlen = tokenlen; + + 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) { + ngtcp2_frame_chain_binder *binder; + + if (frc == NULL) { + return; + } + + switch (frc->fr.type) { + case NGTCP2_FRAME_CRYPTO: + case NGTCP2_FRAME_STREAM: + if (frc->fr.stream.datacnt && + sizeof(ngtcp2_vec) * (frc->fr.stream.datacnt - 1) > + sizeof(ngtcp2_frame) - sizeof(ngtcp2_stream)) { + ngtcp2_frame_chain_del(frc, mem); + + return; + } + + break; + case NGTCP2_FRAME_NEW_TOKEN: + if (frc->fr.new_token.tokenlen > + sizeof(ngtcp2_frame) - sizeof(ngtcp2_new_token)) { + ngtcp2_frame_chain_del(frc, mem); + + return; + } + + break; + } + + binder = frc->binder; + if (binder && --binder->refcount == 0) { + ngtcp2_mem_free(mem, binder); + } + + frc->binder = NULL; + + ngtcp2_objalloc_frame_chain_release(objalloc, frc); +} + +void ngtcp2_frame_chain_init(ngtcp2_frame_chain *frc) { + frc->next = NULL; + frc->binder = NULL; +} + +void ngtcp2_frame_chain_list_objalloc_del(ngtcp2_frame_chain *frc, + ngtcp2_objalloc *objalloc, + const ngtcp2_mem *mem) { + ngtcp2_frame_chain *next; + + for (; frc; frc = next) { + next = frc->next; + + ngtcp2_frame_chain_objalloc_del(frc, objalloc, mem); + } +} + +int ngtcp2_frame_chain_binder_new(ngtcp2_frame_chain_binder **pbinder, + const ngtcp2_mem *mem) { + *pbinder = ngtcp2_mem_calloc(mem, 1, sizeof(ngtcp2_frame_chain_binder)); + if (*pbinder == NULL) { + return NGTCP2_ERR_NOMEM; + } + + return 0; +} + +int ngtcp2_bind_frame_chains(ngtcp2_frame_chain *a, ngtcp2_frame_chain *b, + const ngtcp2_mem *mem) { + ngtcp2_frame_chain_binder *binder; + int rv; + + assert(b->binder == NULL); + + if (a->binder == NULL) { + rv = ngtcp2_frame_chain_binder_new(&binder, mem); + if (rv != 0) { + return rv; + } + + a->binder = binder; + ++a->binder->refcount; + } + + b->binder = a->binder; + ++b->binder->refcount; + + return 0; +} diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_frame_chain.h b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_frame_chain.h new file mode 100644 index 0000000..656fa5b --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_frame_chain.h @@ -0,0 +1,171 @@ +/* + * ngtcp2 + * + * Copyright (c) 2023 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_FRAME_CHAIN_H +#define NGTCP2_FRAME_CHAIN_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_pkt.h" +#include "ngtcp2_objalloc.h" + +/* NGTCP2_FRAME_CHAIN_BINDER_FLAG_NONE indicates that no flag is + set. */ +#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 + +/* + * ngtcp2_frame_chain_binder binds 2 or more of ngtcp2_frame_chain to + * share the acknowledgement state. In general, all + * ngtcp2_frame_chains bound to the same binder must have the same + * information. + */ +typedef struct ngtcp2_frame_chain_binder { + size_t refcount; + /* flags is bitwise OR of zero or more of + NGTCP2_FRAME_CHAIN_BINDER_FLAG_*. */ + uint32_t flags; +} ngtcp2_frame_chain_binder; + +int ngtcp2_frame_chain_binder_new(ngtcp2_frame_chain_binder **pbinder, + const ngtcp2_mem *mem); + +typedef struct ngtcp2_frame_chain ngtcp2_frame_chain; + +/* + * ngtcp2_frame_chain chains frames in a single packet. + */ +struct ngtcp2_frame_chain { + union { + struct { + ngtcp2_frame_chain *next; + ngtcp2_frame_chain_binder *binder; + ngtcp2_frame fr; + }; + + ngtcp2_opl_entry oplent; + }; +}; + +ngtcp2_objalloc_decl(frame_chain, ngtcp2_frame_chain, oplent); + +/* + * ngtcp2_bind_frame_chains binds two frame chains |a| and |b| using + * new or existing ngtcp2_frame_chain_binder. |a| might have non-NULL + * a->binder. |b| must not have non-NULL b->binder. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory + */ +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_new allocates ngtcp2_frame_chain object and + * assigns its pointer to |*pfrc|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +int ngtcp2_frame_chain_new(ngtcp2_frame_chain **pfrc, const ngtcp2_mem *mem); + +/* + * ngtcp2_frame_chain_objalloc_new behaves like + * ngtcp2_frame_chain_new, but it uses |objalloc| to allocate the object. + */ +int ngtcp2_frame_chain_objalloc_new(ngtcp2_frame_chain **pfrc, + ngtcp2_objalloc *objalloc); + +/* + * ngtcp2_frame_chain_extralen_new works like ngtcp2_frame_chain_new, + * but it allocates extra memory |extralen| in order to extend + * ngtcp2_frame. + */ +int ngtcp2_frame_chain_extralen_new(ngtcp2_frame_chain **pfrc, size_t extralen, + const ngtcp2_mem *mem); + +/* + * ngtcp2_frame_chain_stream_datacnt_objalloc_new works like + * ngtcp2_frame_chain_new, but it allocates enough data to store + * additional |datacnt| - 1 ngtcp2_vec object after ngtcp2_stream + * object. If no additional space is required, + * ngtcp2_frame_chain_objalloc_new is called internally. + */ +int ngtcp2_frame_chain_stream_datacnt_objalloc_new(ngtcp2_frame_chain **pfrc, + size_t datacnt, + ngtcp2_objalloc *objalloc, + const ngtcp2_mem *mem); + +int ngtcp2_frame_chain_new_token_objalloc_new(ngtcp2_frame_chain **pfrc, + const uint8_t *token, + size_t tokenlen, + ngtcp2_objalloc *objalloc, + 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|. + */ +void ngtcp2_frame_chain_objalloc_del(ngtcp2_frame_chain *frc, + ngtcp2_objalloc *objalloc, + const ngtcp2_mem *mem); + +/* + * ngtcp2_frame_chain_init initializes |frc|. + */ +void ngtcp2_frame_chain_init(ngtcp2_frame_chain *frc); + +/* + * ngtcp2_frame_chain_list_objalloc_del adds all ngtcp2_frame_chain + * linked from |frc| to |objalloc| for reuse. Depending on the frame type + * and its size, ngtcp2_frame_chain might be deleted instead. + */ +void ngtcp2_frame_chain_list_objalloc_del(ngtcp2_frame_chain *frc, + ngtcp2_objalloc *objalloc, + const ngtcp2_mem *mem); + +#endif /* NGTCP2_FRAME_CHAIN_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_gaptr.c b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_gaptr.c new file mode 100644 index 0000000..87c2389 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_gaptr.c @@ -0,0 +1,167 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 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_gaptr.h" + +#include <string.h> +#include <assert.h> + +void ngtcp2_gaptr_init(ngtcp2_gaptr *gaptr, const ngtcp2_mem *mem) { + ngtcp2_ksl_init(&gaptr->gap, ngtcp2_ksl_range_compar, sizeof(ngtcp2_range), + mem); + + gaptr->mem = mem; +} + +static int gaptr_gap_init(ngtcp2_gaptr *gaptr) { + ngtcp2_range range = {0, UINT64_MAX}; + int rv; + + rv = ngtcp2_ksl_insert(&gaptr->gap, NULL, &range, NULL); + if (rv != 0) { + return rv; + } + + return 0; +} + +void ngtcp2_gaptr_free(ngtcp2_gaptr *gaptr) { + if (gaptr == NULL) { + return; + } + + ngtcp2_ksl_free(&gaptr->gap); +} + +int ngtcp2_gaptr_push(ngtcp2_gaptr *gaptr, uint64_t offset, uint64_t datalen) { + int rv; + ngtcp2_range k, m, l, r, q = {offset, offset + datalen}; + ngtcp2_ksl_it it; + + if (ngtcp2_ksl_len(&gaptr->gap) == 0) { + rv = gaptr_gap_init(gaptr); + if (rv != 0) { + return rv; + } + } + + it = ngtcp2_ksl_lower_bound_compar(&gaptr->gap, &q, + ngtcp2_ksl_range_exclusive_compar); + + for (; !ngtcp2_ksl_it_end(&it);) { + k = *(ngtcp2_range *)ngtcp2_ksl_it_key(&it); + m = ngtcp2_range_intersect(&q, &k); + if (!ngtcp2_range_len(&m)) { + break; + } + + if (ngtcp2_range_eq(&k, &m)) { + ngtcp2_ksl_remove_hint(&gaptr->gap, &it, &it, &k); + continue; + } + ngtcp2_range_cut(&l, &r, &k, &m); + if (ngtcp2_range_len(&l)) { + ngtcp2_ksl_update_key(&gaptr->gap, &k, &l); + + if (ngtcp2_range_len(&r)) { + rv = ngtcp2_ksl_insert(&gaptr->gap, &it, &r, NULL); + if (rv != 0) { + return rv; + } + } + } else if (ngtcp2_range_len(&r)) { + ngtcp2_ksl_update_key(&gaptr->gap, &k, &r); + } + ngtcp2_ksl_it_next(&it); + } + return 0; +} + +uint64_t ngtcp2_gaptr_first_gap_offset(ngtcp2_gaptr *gaptr) { + ngtcp2_ksl_it it; + ngtcp2_range r; + + if (ngtcp2_ksl_len(&gaptr->gap) == 0) { + return 0; + } + + it = ngtcp2_ksl_begin(&gaptr->gap); + r = *(ngtcp2_range *)ngtcp2_ksl_it_key(&it); + + return r.begin; +} + +ngtcp2_range ngtcp2_gaptr_get_first_gap_after(ngtcp2_gaptr *gaptr, + uint64_t offset) { + ngtcp2_range q = {offset, offset + 1}; + ngtcp2_ksl_it it; + + if (ngtcp2_ksl_len(&gaptr->gap) == 0) { + ngtcp2_range r = {0, UINT64_MAX}; + return r; + } + + it = ngtcp2_ksl_lower_bound_compar(&gaptr->gap, &q, + ngtcp2_ksl_range_exclusive_compar); + + assert(!ngtcp2_ksl_it_end(&it)); + + return *(ngtcp2_range *)ngtcp2_ksl_it_key(&it); +} + +int ngtcp2_gaptr_is_pushed(ngtcp2_gaptr *gaptr, uint64_t offset, + uint64_t datalen) { + ngtcp2_range q = {offset, offset + datalen}; + ngtcp2_ksl_it it; + ngtcp2_range k; + ngtcp2_range m; + + if (ngtcp2_ksl_len(&gaptr->gap) == 0) { + return 0; + } + + it = ngtcp2_ksl_lower_bound_compar(&gaptr->gap, &q, + ngtcp2_ksl_range_exclusive_compar); + k = *(ngtcp2_range *)ngtcp2_ksl_it_key(&it); + m = ngtcp2_range_intersect(&q, &k); + + return ngtcp2_range_len(&m) == 0; +} + +void ngtcp2_gaptr_drop_first_gap(ngtcp2_gaptr *gaptr) { + ngtcp2_ksl_it it; + ngtcp2_range r; + + if (ngtcp2_ksl_len(&gaptr->gap) == 0) { + return; + } + + it = ngtcp2_ksl_begin(&gaptr->gap); + + assert(!ngtcp2_ksl_it_end(&it)); + + r = *(ngtcp2_range *)ngtcp2_ksl_it_key(&it); + + ngtcp2_ksl_remove_hint(&gaptr->gap, NULL, &it, &r); +} diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_gaptr.h b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_gaptr.h new file mode 100644 index 0000000..0f100a8 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_gaptr.h @@ -0,0 +1,98 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 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_GAPTR_H +#define NGTCP2_GAPTR_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_mem.h" +#include "ngtcp2_ksl.h" +#include "ngtcp2_range.h" + +/* + * ngtcp2_gaptr maintains the gap in the range [0, UINT64_MAX). + */ +typedef struct ngtcp2_gaptr { + /* gap maintains the range of offset which is not received + yet. Initially, its range is [0, UINT64_MAX). */ + ngtcp2_ksl gap; + /* mem is custom memory allocator */ + const ngtcp2_mem *mem; +} ngtcp2_gaptr; + +/* + * ngtcp2_gaptr_init initializes |gaptr|. + */ +void ngtcp2_gaptr_init(ngtcp2_gaptr *gaptr, const ngtcp2_mem *mem); + +/* + * ngtcp2_gaptr_free frees resources allocated for |gaptr|. + */ +void ngtcp2_gaptr_free(ngtcp2_gaptr *gaptr); + +/* + * ngtcp2_gaptr_push adds new data of length |datalen| at the stream + * offset |offset|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory + */ +int ngtcp2_gaptr_push(ngtcp2_gaptr *gaptr, uint64_t offset, uint64_t datalen); + +/* + * ngtcp2_gaptr_first_gap_offset returns the offset to the first gap. + * If there is no gap, it returns UINT64_MAX. + */ +uint64_t ngtcp2_gaptr_first_gap_offset(ngtcp2_gaptr *gaptr); + +/* + * ngtcp2_gaptr_get_first_gap_after returns the first gap which + * overlaps or comes after |offset|. + */ +ngtcp2_range ngtcp2_gaptr_get_first_gap_after(ngtcp2_gaptr *gaptr, + uint64_t offset); + +/* + * ngtcp2_gaptr_is_pushed returns nonzero if range [offset, offset + + * datalen) is completely pushed into this object. + */ +int ngtcp2_gaptr_is_pushed(ngtcp2_gaptr *gaptr, uint64_t offset, + uint64_t datalen); + +/* + * ngtcp2_gaptr_drop_first_gap deletes the first gap entirely as if + * the range is pushed. This function assumes that at least one gap + * exists. + */ +void ngtcp2_gaptr_drop_first_gap(ngtcp2_gaptr *gaptr); + +#endif /* NGTCP2_GAPTR_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_idtr.c b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_idtr.c new file mode 100644 index 0000000..d988022 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_idtr.c @@ -0,0 +1,79 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 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_idtr.h" + +#include <assert.h> + +void ngtcp2_idtr_init(ngtcp2_idtr *idtr, int server, const ngtcp2_mem *mem) { + ngtcp2_gaptr_init(&idtr->gap, mem); + + idtr->server = server; +} + +void ngtcp2_idtr_free(ngtcp2_idtr *idtr) { + if (idtr == NULL) { + return; + } + + ngtcp2_gaptr_free(&idtr->gap); +} + +/* + * id_from_stream_id translates |stream_id| to id space used by + * ngtcp2_idtr. + */ +static uint64_t id_from_stream_id(int64_t stream_id) { + return (uint64_t)(stream_id >> 2); +} + +int ngtcp2_idtr_open(ngtcp2_idtr *idtr, int64_t stream_id) { + uint64_t q; + + assert((idtr->server && (stream_id % 2)) || + (!idtr->server && (stream_id % 2)) == 0); + + q = id_from_stream_id(stream_id); + + if (ngtcp2_gaptr_is_pushed(&idtr->gap, q, 1)) { + return NGTCP2_ERR_STREAM_IN_USE; + } + + return ngtcp2_gaptr_push(&idtr->gap, q, 1); +} + +int ngtcp2_idtr_is_open(ngtcp2_idtr *idtr, int64_t stream_id) { + uint64_t q; + + assert((idtr->server && (stream_id % 2)) || + (!idtr->server && (stream_id % 2)) == 0); + + q = id_from_stream_id(stream_id); + + return ngtcp2_gaptr_is_pushed(&idtr->gap, q, 1); +} + +uint64_t ngtcp2_idtr_first_gap(ngtcp2_idtr *idtr) { + return ngtcp2_gaptr_first_gap_offset(&idtr->gap); +} diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_idtr.h b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_idtr.h new file mode 100644 index 0000000..edb8c68 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_idtr.h @@ -0,0 +1,89 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 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_IDTR_H +#define NGTCP2_IDTR_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_mem.h" +#include "ngtcp2_gaptr.h" + +/* + * ngtcp2_idtr tracks the usage of stream ID. + */ +typedef struct ngtcp2_idtr { + /* gap maintains the range of ID which is not used yet. Initially, + its range is [0, UINT64_MAX). */ + ngtcp2_gaptr gap; + /* server is nonzero if this object records server initiated stream + ID. */ + int server; +} ngtcp2_idtr; + +/* + * ngtcp2_idtr_init initializes |idtr|. + * + * If this object records server initiated ID (even number), set + * |server| to nonzero. + */ +void ngtcp2_idtr_init(ngtcp2_idtr *idtr, int server, const ngtcp2_mem *mem); + +/* + * ngtcp2_idtr_free frees resources allocated for |idtr|. + */ +void ngtcp2_idtr_free(ngtcp2_idtr *idtr); + +/* + * ngtcp2_idtr_open claims that |stream_id| is in used. + * + * It returns 0 if it succeeds, or one of the following negative error + * codes: + * + * NGTCP2_ERR_STREAM_IN_USE + * ID has already been used. + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +int ngtcp2_idtr_open(ngtcp2_idtr *idtr, int64_t stream_id); + +/* + * ngtcp2_idtr_open tells whether ID |stream_id| is in used or not. + * + * It returns nonzero if |stream_id| is used. + */ +int ngtcp2_idtr_is_open(ngtcp2_idtr *idtr, int64_t stream_id); + +/* + * ngtcp2_idtr_first_gap returns the first id of first gap. If there + * is no gap, it returns UINT64_MAX. The returned id is an id space + * used in this object internally, and not stream ID. + */ +uint64_t ngtcp2_idtr_first_gap(ngtcp2_idtr *idtr); + +#endif /* NGTCP2_IDTR_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_ksl.c b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_ksl.c new file mode 100644 index 0000000..0ccc048 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_ksl.c @@ -0,0 +1,823 @@ +/* + * ngtcp2 + * + * Copyright (c) 2018 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_ksl.h" + +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <stdio.h> + +#include "ngtcp2_macro.h" +#include "ngtcp2_mem.h" +#include "ngtcp2_range.h" + +static ngtcp2_ksl_blk null_blk = {{{NULL, NULL, 0, 0, {0}}}}; + +ngtcp2_objalloc_def(ksl_blk, ngtcp2_ksl_blk, oplent); + +static size_t ksl_nodelen(size_t keylen) { + return (sizeof(ngtcp2_ksl_node) + keylen - sizeof(uint64_t) + 0xfu) & + ~(uintptr_t)0xfu; +} + +static size_t ksl_blklen(size_t nodelen) { + return sizeof(ngtcp2_ksl_blk) + nodelen * NGTCP2_KSL_MAX_NBLK - + sizeof(uint64_t); +} + +/* + * ksl_node_set_key sets |key| to |node|. + */ +static void ksl_node_set_key(ngtcp2_ksl *ksl, ngtcp2_ksl_node *node, + const void *key) { + memcpy(node->key, key, ksl->keylen); +} + +void ngtcp2_ksl_init(ngtcp2_ksl *ksl, ngtcp2_ksl_compar compar, size_t keylen, + const ngtcp2_mem *mem) { + size_t nodelen = ksl_nodelen(keylen); + + ngtcp2_objalloc_init(&ksl->blkalloc, + ((ksl_blklen(nodelen) + 0xfu) & ~(uintptr_t)0xfu) * 8, + mem); + + ksl->head = NULL; + ksl->front = ksl->back = NULL; + ksl->compar = compar; + ksl->keylen = keylen; + ksl->nodelen = nodelen; + ksl->n = 0; +} + +static ngtcp2_ksl_blk *ksl_blk_objalloc_new(ngtcp2_ksl *ksl) { + return ngtcp2_objalloc_ksl_blk_len_get(&ksl->blkalloc, + ksl_blklen(ksl->nodelen)); +} + +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); + if (!head) { + return NGTCP2_ERR_NOMEM; + } + + head->next = head->prev = NULL; + head->n = 0; + head->leaf = 1; + + ksl->head = head; + ksl->front = ksl->back = head; + + return 0; +} + +#ifdef NOMEMPOOL +/* + * ksl_free_blk frees |blk| recursively. + */ +static void ksl_free_blk(ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk) { + size_t i; + + if (!blk->leaf) { + for (i = 0; i < blk->n; ++i) { + ksl_free_blk(ksl, ngtcp2_ksl_nth_node(ksl, blk, i)->blk); + } + } + + ksl_blk_objalloc_del(ksl, blk); +} +#endif /* NOMEMPOOL */ + +void ngtcp2_ksl_free(ngtcp2_ksl *ksl) { + if (!ksl || !ksl->head) { + return; + } + +#ifdef NOMEMPOOL + ksl_free_blk(ksl, ksl->head); +#endif /* NOMEMPOOL */ + + ngtcp2_objalloc_free(&ksl->blkalloc); +} + +/* + * ksl_split_blk splits |blk| into 2 ngtcp2_ksl_blk objects. The new + * ngtcp2_ksl_blk is always the "right" block. + * + * It returns the pointer to the ngtcp2_ksl_blk created which is the + * located at the right of |blk|, or NULL which indicates out of + * memory error. + */ +static ngtcp2_ksl_blk *ksl_split_blk(ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk) { + ngtcp2_ksl_blk *rblk; + + rblk = ksl_blk_objalloc_new(ksl); + if (rblk == NULL) { + return NULL; + } + + rblk->next = blk->next; + blk->next = rblk; + if (rblk->next) { + rblk->next->prev = rblk; + } else if (ksl->back == blk) { + ksl->back = rblk; + } + rblk->prev = blk; + rblk->leaf = blk->leaf; + + rblk->n = blk->n / 2; + + memcpy(rblk->nodes, blk->nodes + ksl->nodelen * (blk->n - rblk->n), + ksl->nodelen * rblk->n); + + blk->n -= rblk->n; + + assert(blk->n >= NGTCP2_KSL_MIN_NBLK); + assert(rblk->n >= NGTCP2_KSL_MIN_NBLK); + + return rblk; +} + +/* + * ksl_split_node splits a node included in |blk| at the position |i| + * into 2 adjacent nodes. The new node is always inserted at the + * position |i+1|. + * + * It returns 0 if it succeeds, or one of the following negative error + * codes: + * + * NGTCP2_ERR_NOMEM + * 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; + + 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))); + + node = ngtcp2_ksl_nth_node(ksl, blk, i + 1); + node->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); + + return 0; +} + +/* + * ksl_split_head splits a head (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: + * + * 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; + + rblk = ksl_split_blk(ksl, ksl->head); + if (rblk == NULL) { + return NGTCP2_ERR_NOMEM; + } + + lblk = ksl->head; + + nhead = ksl_blk_objalloc_new(ksl); + if (nhead == NULL) { + ksl_blk_objalloc_del(ksl, rblk); + return NGTCP2_ERR_NOMEM; + } + nhead->next = nhead->prev = NULL; + nhead->n = 2; + nhead->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; + + 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->head = nhead; + + return 0; +} + +/* + * insert_node inserts a node whose key is |key| with the associated + * |data| at the index of |i|. This function assumes that the number + * of nodes contained by |blk| is strictly less than + * NGTCP2_KSL_MAX_NBLK. + */ +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)); + + node = ngtcp2_ksl_nth_node(ksl, blk, i); + ksl_node_set_key(ksl, node, key); + node->data = data; + + ++blk->n; +} + +static size_t ksl_bsearch(ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, + const ngtcp2_ksl_key *key, ngtcp2_ksl_compar compar) { + size_t i; + ngtcp2_ksl_node *node; + + 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)) + ; + + return i; +} + +int ngtcp2_ksl_insert(ngtcp2_ksl *ksl, ngtcp2_ksl_it *it, + const ngtcp2_ksl_key *key, void *data) { + ngtcp2_ksl_blk *blk; + ngtcp2_ksl_node *node; + size_t i; + int rv; + + if (!ksl->head) { + rv = ksl_head_init(ksl); + if (rv != 0) { + return rv; + } + } + + blk = ksl->head; + + if (blk->n == NGTCP2_KSL_MAX_NBLK) { + rv = ksl_split_head(ksl); + if (rv != 0) { + return rv; + } + blk = ksl->head; + } + + for (;;) { + i = ksl_bsearch(ksl, blk, key, ksl->compar); + + if (blk->leaf) { + if (i < blk->n && + !ksl->compar(key, ngtcp2_ksl_nth_node(ksl, blk, i)->key)) { + if (it) { + *it = ngtcp2_ksl_end(ksl); + } + return NGTCP2_ERR_INVALID_ARGUMENT; + } + ksl_insert_node(ksl, blk, i, key, data); + ++ksl->n; + if (it) { + ngtcp2_ksl_it_init(it, ksl, blk, i); + } + return 0; + } + + 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); + 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); + } + ksl_node_set_key(ksl, node, key); + blk = node->blk; + } + ksl_insert_node(ksl, blk, blk->n, key, data); + ++ksl->n; + if (it) { + ngtcp2_ksl_it_init(it, ksl, blk, blk->n - 1); + } + return 0; + } + + node = ngtcp2_ksl_nth_node(ksl, blk, i); + + if (node->blk->n == NGTCP2_KSL_MAX_NBLK) { + rv = ksl_split_node(ksl, blk, i); + if (rv != 0) { + 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); + } + } + } + + blk = node->blk; + } +} + +/* + * ksl_remove_node removes the node included in |blk| at the index of + * |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))); + + --blk->n; +} + +/* + * ksl_merge_node merges 2 nodes which are the nodes at the index of + * |i| and |i + 1|. + * + * If |blk| is the direct descendant of head (root) block and the head + * block contains just 2 nodes, the merged block becomes head block, + * which decreases the height of |ksl| by 1. + * + * This function returns the pointer to the merged block. + */ +static ngtcp2_ksl_blk *ksl_merge_node(ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, + size_t i) { + ngtcp2_ksl_blk *lblk, *rblk; + + assert(i + 1 < blk->n); + + lblk = ngtcp2_ksl_nth_node(ksl, blk, i)->blk; + rblk = ngtcp2_ksl_nth_node(ksl, blk, i + 1)->blk; + + assert(lblk->n + rblk->n < NGTCP2_KSL_MAX_NBLK); + + memcpy(lblk->nodes + ksl->nodelen * lblk->n, rblk->nodes, + ksl->nodelen * rblk->n); + + lblk->n += rblk->n; + lblk->next = rblk->next; + if (lblk->next) { + lblk->next->prev = lblk; + } else if (ksl->back == rblk) { + ksl->back = lblk; + } + + ksl_blk_objalloc_del(ksl, rblk); + + if (ksl->head == blk && blk->n == 2) { + ksl_blk_objalloc_del(ksl, ksl->head); + ksl->head = lblk; + } else { + ksl_remove_node(ksl, blk, i + 1); + ksl_node_set_key(ksl, ngtcp2_ksl_nth_node(ksl, blk, i), + ngtcp2_ksl_nth_node(ksl, lblk, lblk->n - 1)->key); + } + + return lblk; +} + +/* + * ksl_shift_left moves the first nodes in blk->nodes[i]->blk->nodes + * to blk->nodes[i - 1]->blk->nodes in a manner that they have the + * same amount of nodes as much as possible. + */ +static void ksl_shift_left(ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, size_t i) { + ngtcp2_ksl_node *lnode, *rnode; + size_t n; + + assert(i > 0); + + lnode = ngtcp2_ksl_nth_node(ksl, blk, i - 1); + rnode = ngtcp2_ksl_nth_node(ksl, blk, i); + + assert(lnode->blk->n < NGTCP2_KSL_MAX_NBLK); + assert(rnode->blk->n > NGTCP2_KSL_MIN_NBLK); + + n = (lnode->blk->n + rnode->blk->n + 1) / 2 - lnode->blk->n; + + assert(n > 0); + assert(lnode->blk->n <= NGTCP2_KSL_MAX_NBLK - n); + assert(rnode->blk->n >= NGTCP2_KSL_MIN_NBLK + n); + + memcpy(lnode->blk->nodes + ksl->nodelen * lnode->blk->n, rnode->blk->nodes, + ksl->nodelen * n); + + lnode->blk->n += (uint32_t)n; + rnode->blk->n -= (uint32_t)n; + + ksl_node_set_key( + ksl, lnode, ngtcp2_ksl_nth_node(ksl, lnode->blk, lnode->blk->n - 1)->key); + + memmove(rnode->blk->nodes, rnode->blk->nodes + ksl->nodelen * n, + ksl->nodelen * rnode->blk->n); +} + +/* + * ksl_shift_right moves the last nodes in blk->nodes[i]->blk->nodes + * to blk->nodes[i + 1]->blk->nodes in a manner that they have the + * same amount of nodes as much as possible.. + */ +static void ksl_shift_right(ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, size_t i) { + ngtcp2_ksl_node *lnode, *rnode; + size_t n; + + assert(i < blk->n - 1); + + lnode = ngtcp2_ksl_nth_node(ksl, blk, i); + rnode = ngtcp2_ksl_nth_node(ksl, blk, i + 1); + + assert(lnode->blk->n > NGTCP2_KSL_MIN_NBLK); + assert(rnode->blk->n < NGTCP2_KSL_MAX_NBLK); + + n = (lnode->blk->n + rnode->blk->n + 1) / 2 - rnode->blk->n; + + assert(n > 0); + assert(lnode->blk->n >= NGTCP2_KSL_MIN_NBLK + n); + assert(rnode->blk->n <= NGTCP2_KSL_MAX_NBLK - n); + + memmove(rnode->blk->nodes + ksl->nodelen * n, rnode->blk->nodes, + ksl->nodelen * rnode->blk->n); + + rnode->blk->n += (uint32_t)n; + lnode->blk->n -= (uint32_t)n; + + memcpy(rnode->blk->nodes, lnode->blk->nodes + ksl->nodelen * lnode->blk->n, + ksl->nodelen * n); + + ksl_node_set_key( + ksl, lnode, ngtcp2_ksl_nth_node(ksl, lnode->blk, lnode->blk->n - 1)->key); +} + +/* + * key_equal returns nonzero if |lhs| and |rhs| are equal using the + * function |compar|. + */ +static int key_equal(ngtcp2_ksl_compar compar, const ngtcp2_ksl_key *lhs, + const ngtcp2_ksl_key *rhs) { + return !compar(lhs, rhs) && !compar(rhs, lhs); +} + +int ngtcp2_ksl_remove_hint(ngtcp2_ksl *ksl, ngtcp2_ksl_it *it, + const ngtcp2_ksl_it *hint, + const ngtcp2_ksl_key *key) { + ngtcp2_ksl_blk *blk = hint->blk; + + assert(ksl->head); + + if (blk->n <= NGTCP2_KSL_MIN_NBLK) { + return ngtcp2_ksl_remove(ksl, it, key); + } + + ksl_remove_node(ksl, blk, hint->i); + + --ksl->n; + + if (it) { + if (hint->i == blk->n && blk->next) { + ngtcp2_ksl_it_init(it, ksl, blk->next, 0); + } else { + ngtcp2_ksl_it_init(it, ksl, blk, hint->i); + } + } + + return 0; +} + +int ngtcp2_ksl_remove(ngtcp2_ksl *ksl, ngtcp2_ksl_it *it, + const ngtcp2_ksl_key *key) { + ngtcp2_ksl_blk *blk = ksl->head; + ngtcp2_ksl_node *node; + size_t i; + + if (!ksl->head) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + 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 = ksl_merge_node(ksl, ksl->head, 0); + } + + for (;;) { + i = ksl_bsearch(ksl, blk, key, ksl->compar); + + if (i == blk->n) { + if (it) { + *it = ngtcp2_ksl_end(ksl); + } + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + if (blk->leaf) { + if (ksl->compar(key, ngtcp2_ksl_nth_node(ksl, blk, i)->key)) { + if (it) { + *it = ngtcp2_ksl_end(ksl); + } + return NGTCP2_ERR_INVALID_ARGUMENT; + } + ksl_remove_node(ksl, blk, i); + --ksl->n; + if (it) { + if (blk->n == i && blk->next) { + ngtcp2_ksl_it_init(it, ksl, blk->next, 0); + } else { + ngtcp2_ksl_it_init(it, ksl, blk, i); + } + } + return 0; + } + + node = ngtcp2_ksl_nth_node(ksl, blk, i); + + if (node->blk->n > NGTCP2_KSL_MIN_NBLK) { + blk = node->blk; + continue; + } + + 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) { + 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) { + ksl_shift_right(ksl, blk, i - 1); + blk = node->blk; + continue; + } + + if (i + 1 < blk->n) { + blk = ksl_merge_node(ksl, blk, i); + continue; + } + + assert(i > 0); + + blk = ksl_merge_node(ksl, blk, i - 1); + } +} + +ngtcp2_ksl_it ngtcp2_ksl_lower_bound(ngtcp2_ksl *ksl, + const ngtcp2_ksl_key *key) { + ngtcp2_ksl_blk *blk = ksl->head; + ngtcp2_ksl_it it; + size_t i; + + if (!blk) { + ngtcp2_ksl_it_init(&it, ksl, &null_blk, 0); + return it; + } + + for (;;) { + i = ksl_bsearch(ksl, blk, key, ksl->compar); + + if (blk->leaf) { + if (i == blk->n && blk->next) { + blk = blk->next; + i = 0; + } + ngtcp2_ksl_it_init(&it, ksl, blk, i); + return it; + } + + 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) + ; + if (blk->next) { + blk = blk->next; + i = 0; + } else { + i = blk->n; + } + ngtcp2_ksl_it_init(&it, ksl, blk, i); + return it; + } + blk = ngtcp2_ksl_nth_node(ksl, blk, i)->blk; + } +} + +ngtcp2_ksl_it ngtcp2_ksl_lower_bound_compar(ngtcp2_ksl *ksl, + const ngtcp2_ksl_key *key, + ngtcp2_ksl_compar compar) { + ngtcp2_ksl_blk *blk = ksl->head; + ngtcp2_ksl_it it; + size_t i; + + if (!blk) { + ngtcp2_ksl_it_init(&it, ksl, &null_blk, 0); + return it; + } + + for (;;) { + i = ksl_bsearch(ksl, blk, key, compar); + + if (blk->leaf) { + if (i == blk->n && blk->next) { + blk = blk->next; + i = 0; + } + ngtcp2_ksl_it_init(&it, ksl, blk, i); + return it; + } + + 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) + ; + if (blk->next) { + blk = blk->next; + i = 0; + } else { + i = blk->n; + } + ngtcp2_ksl_it_init(&it, ksl, blk, i); + return it; + } + blk = ngtcp2_ksl_nth_node(ksl, blk, 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_node *node; + size_t i; + + assert(ksl->head); + + for (;;) { + i = ksl_bsearch(ksl, blk, old_key, ksl->compar); + + assert(i < blk->n); + node = ngtcp2_ksl_nth_node(ksl, 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); + 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); + } + + blk = node->blk; + } +} + +size_t ngtcp2_ksl_len(ngtcp2_ksl *ksl) { return ksl->n; } + +void ngtcp2_ksl_clear(ngtcp2_ksl *ksl) { + if (!ksl->head) { + return; + } + +#ifdef NOMEMPOOL + ksl_free_blk(ksl, ksl->head); +#endif /* NOMEMPOOL */ + + ksl->front = ksl->back = ksl->head = NULL; + ksl->n = 0; + + ngtcp2_objalloc_clear(&ksl->blkalloc); +} + +#ifndef WIN32 +static void ksl_print(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, "\n"); + return; + } + + for (i = 0; i < blk->n; ++i) { + ksl_print(ksl, ngtcp2_ksl_nth_node(ksl, blk, i)->blk, level + 1); + } +} + +void ngtcp2_ksl_print(ngtcp2_ksl *ksl) { + if (!ksl->head) { + return; + } + + ksl_print(ksl, ksl->head, 0); +} +#endif /* !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); + } else { + ngtcp2_ksl_it_init(&it, ksl, &null_blk, 0); + } + + return it; +} + +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); + } else { + ngtcp2_ksl_it_init(&it, ksl, &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; + it->blk = blk; + it->i = i; +} + +void ngtcp2_ksl_it_prev(ngtcp2_ksl_it *it) { + assert(!ngtcp2_ksl_it_begin(it)); + + if (it->i == 0) { + it->blk = it->blk->prev; + it->i = it->blk->n - 1; + } else { + --it->i; + } +} + +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; +} + +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(a->begin, b->begin) < ngtcp2_min(a->end, b->end)); +} diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_ksl.h b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_ksl.h new file mode 100644 index 0000000..7e08f15 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_ksl.h @@ -0,0 +1,347 @@ +/* + * ngtcp2 + * + * Copyright (c) 2018 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_KSL_H +#define NGTCP2_KSL_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <stdlib.h> + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_objalloc.h" + +/* + * Skip List using single key instead of range. + */ + +#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) +/* NGTCP2_KSL_MIN_NBLK is the minimum number of nodes which a single + block other than root must contains. */ +#define NGTCP2_KSL_MIN_NBLK (NGTCP2_KSL_DEGR - 1) + +/* + * ngtcp2_ksl_key represents key in ngtcp2_ksl. + */ +typedef void ngtcp2_ksl_key; + +typedef struct ngtcp2_ksl_node ngtcp2_ksl_node; + +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. + */ +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]; + }; +}; + +/* + * ngtcp2_ksl_blk contains ngtcp2_ksl_node objects. + */ +struct ngtcp2_ksl_blk { + union { + struct { + /* next points to the next block if leaf field is nonzero. */ + ngtcp2_ksl_blk *next; + /* prev points to the previous block if leaf field is nonzero. */ + ngtcp2_ksl_blk *prev; + /* n is the number of nodes this object contains in nodes. */ + uint32_t n; + /* 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]; + }; + }; + + ngtcp2_opl_entry oplent; + }; +}; + +ngtcp2_objalloc_decl(ksl_blk, ngtcp2_ksl_blk, oplent); + +/* + * ngtcp2_ksl_compar is a function type which returns nonzero if key + * |lhs| should be placed before |rhs|. It returns 0 otherwise. + */ +typedef int (*ngtcp2_ksl_compar)(const ngtcp2_ksl_key *lhs, + const ngtcp2_ksl_key *rhs); + +typedef struct ngtcp2_ksl ngtcp2_ksl; + +typedef struct ngtcp2_ksl_it ngtcp2_ksl_it; + +/* + * ngtcp2_ksl_it is a forward iterator to iterate nodes. + */ +struct ngtcp2_ksl_it { + const ngtcp2_ksl *ksl; + ngtcp2_ksl_blk *blk; + size_t i; +}; + +/* + * ngtcp2_ksl is a deterministic paged skip list. + */ +struct ngtcp2_ksl { + ngtcp2_objalloc blkalloc; + /* head points to the root block. */ + ngtcp2_ksl_blk *head; + /* front points to the first leaf block. */ + ngtcp2_ksl_blk *front; + /* back points to the last leaf block. */ + ngtcp2_ksl_blk *back; + ngtcp2_ksl_compar compar; + 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; +}; + +/* + * ngtcp2_ksl_init initializes |ksl|. |compar| specifies compare + * function. |keylen| is the length of key. + */ +void ngtcp2_ksl_init(ngtcp2_ksl *ksl, ngtcp2_ksl_compar compar, size_t keylen, + const ngtcp2_mem *mem); + +/* + * ngtcp2_ksl_free frees resources allocated for |ksl|. If |ksl| is + * NULL, this function does nothing. It does not free the memory + * region pointed by |ksl| itself. + */ +void ngtcp2_ksl_free(ngtcp2_ksl *ksl); + +/* + * ngtcp2_ksl_insert inserts |key| with its associated |data|. On + * successful insertion, the iterator points to the inserted node is + * stored in |*it|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + * NGTCP2_ERR_INVALID_ARGUMENT + * |key| already exists. + */ +int ngtcp2_ksl_insert(ngtcp2_ksl *ksl, ngtcp2_ksl_it *it, + const ngtcp2_ksl_key *key, void *data); + +/* + * ngtcp2_ksl_remove removes the |key| from |ksl|. + * + * This function assigns the iterator to |*it|, which points to the + * node which is located at the right next of the removed node if |it| + * is not NULL. If |key| is not found, no deletion takes place and + * the return value of ngtcp2_ksl_end(ksl) is assigned to |*it|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_INVALID_ARGUMENT + * |key| does not exist. + */ +int ngtcp2_ksl_remove(ngtcp2_ksl *ksl, ngtcp2_ksl_it *it, + const ngtcp2_ksl_key *key); + +/* + * ngtcp2_ksl_remove_hint removes the |key| from |ksl|. |hint| must + * point to the same node denoted by |key|. |hint| is used to remove + * a node efficiently in some cases. Other than that, it behaves + * exactly like ngtcp2_ksl_remove. |it| and |hint| can point to the + * same object. + */ +int ngtcp2_ksl_remove_hint(ngtcp2_ksl *ksl, ngtcp2_ksl_it *it, + const ngtcp2_ksl_it *hint, + const ngtcp2_ksl_key *key); + +/* + * ngtcp2_ksl_lower_bound returns the iterator which points to the + * first node which has the key which is equal to |key| or the last + * node which satisfies !compar(&node->key, key). If there is no such + * node, it returns the iterator which satisfies ngtcp2_ksl_it_end(it) + * != 0. + */ +ngtcp2_ksl_it ngtcp2_ksl_lower_bound(ngtcp2_ksl *ksl, + const ngtcp2_ksl_key *key); + +/* + * ngtcp2_ksl_lower_bound_compar works like ngtcp2_ksl_lower_bound, + * but it takes custom function |compar| to do lower bound search. + */ +ngtcp2_ksl_it ngtcp2_ksl_lower_bound_compar(ngtcp2_ksl *ksl, + const ngtcp2_ksl_key *key, + ngtcp2_ksl_compar compar); + +/* + * ngtcp2_ksl_update_key replaces the key of nodes which has |old_key| + * with |new_key|. |new_key| must be strictly greater than the + * previous node and strictly smaller than the next node. + */ +void ngtcp2_ksl_update_key(ngtcp2_ksl *ksl, const ngtcp2_ksl_key *old_key, + const ngtcp2_ksl_key *new_key); + +/* + * ngtcp2_ksl_begin returns the iterator which points to the first + * node. If there is no node in |ksl|, it returns the iterator which + * satisfies ngtcp2_ksl_it_end(it) != 0. + */ +ngtcp2_ksl_it ngtcp2_ksl_begin(const ngtcp2_ksl *ksl); + +/* + * ngtcp2_ksl_end returns the iterator which points to the node + * following the last node. The returned object satisfies + * ngtcp2_ksl_it_end(). If there is no node in |ksl|, it returns the + * iterator which satisfies ngtcp2_ksl_it_begin(it) != 0. + */ +ngtcp2_ksl_it ngtcp2_ksl_end(const ngtcp2_ksl *ksl); + +/* + * ngtcp2_ksl_len returns the number of elements stored in |ksl|. + */ +size_t ngtcp2_ksl_len(ngtcp2_ksl *ksl); + +/* + * ngtcp2_ksl_clear removes all elements stored in |ksl|. + */ +void ngtcp2_ksl_clear(ngtcp2_ksl *ksl); + +/* + * ngtcp2_ksl_nth_node returns the |n|th node under |blk|. + */ +#define ngtcp2_ksl_nth_node(KSL, BLK, N) \ + ((ngtcp2_ksl_node *)(void *)((BLK)->nodes + (KSL)->nodelen * (N))) + +#ifndef WIN32 +/* + * ngtcp2_ksl_print prints its internal state in stderr. It assumes + * that the key is of type int64_t. This function should be used for + * the debugging purpose only. + */ +void ngtcp2_ksl_print(ngtcp2_ksl *ksl); +#endif /* !WIN32 */ + +/* + * 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); + +/* + * 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 + +/* + * 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) + +/* + * ngtcp2_ksl_it_prev moves backward the iterator by one. It is + * undefined if this function is called when ngtcp2_ksl_it_begin(it) + * returns nonzero. + */ +void ngtcp2_ksl_it_prev(ngtcp2_ksl_it *it); + +/* + * ngtcp2_ksl_it_end returns nonzero if |it| points to the beyond the + * last node. + */ +#define ngtcp2_ksl_it_end(IT) \ + ((IT)->blk->n == (IT)->i && (IT)->blk->next == NULL) + +/* + * ngtcp2_ksl_it_begin returns nonzero if |it| points to the first + * node. |it| might satisfy both ngtcp2_ksl_it_begin(&it) and + * ngtcp2_ksl_it_end(&it) if the skip list has no node. + */ +int ngtcp2_ksl_it_begin(const ngtcp2_ksl_it *it); + +/* + * ngtcp2_ksl_key returns the key of 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_key(IT) \ + ((ngtcp2_ksl_key *)ngtcp2_ksl_nth_node((IT)->ksl, (IT)->blk, (IT)->i)->key) + +/* + * ngtcp2_ksl_range_compar is an implementation of ngtcp2_ksl_compar. + * lhs->ptr and rhs->ptr must point to ngtcp2_range object and the + * function returns nonzero if (const ngtcp2_range *)(lhs->ptr)->begin + * < (const ngtcp2_range *)(rhs->ptr)->begin. + */ +int ngtcp2_ksl_range_compar(const ngtcp2_ksl_key *lhs, + const ngtcp2_ksl_key *rhs); + +/* + * ngtcp2_ksl_range_exclusive_compar is an implementation of + * ngtcp2_ksl_compar. lhs->ptr and rhs->ptr must point to + * ngtcp2_range object and the function returns nonzero if (const + * ngtcp2_range *)(lhs->ptr)->begin < (const ngtcp2_range + * *)(rhs->ptr)->begin and the 2 ranges do not intersect. + */ +int ngtcp2_ksl_range_exclusive_compar(const ngtcp2_ksl_key *lhs, + const ngtcp2_ksl_key *rhs); + +#endif /* NGTCP2_KSL_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_log.c b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_log.c new file mode 100644 index 0000000..93922a2 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_log.c @@ -0,0 +1,836 @@ +/* + * ngtcp2 + * + * Copyright (c) 2018 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_log.h" + +#include <stdio.h> +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif +#include <assert.h> +#include <string.h> + +#include "ngtcp2_str.h" +#include "ngtcp2_vec.h" +#include "ngtcp2_macro.h" +#include "ngtcp2_conv.h" +#include "ngtcp2_unreachable.h" +#include "ngtcp2_net.h" + +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); + } else { + log->scid[0] = '\0'; + } + log->log_printf = log_printf; + log->events = 0xff; + log->ts = log->last_ts = ts; + log->user_data = user_data; +} + +/* + * # Log header + * + * <LEVEL><TIMESTAMP> <SCID> <EVENT> + * + * <LEVEL>: + * Log level. I=Info, W=Warning, E=Error + * + * <TIMESTAMP>: + * Timestamp relative to ngtcp2_log.ts field in milliseconds + * resolution. + * + * <SCID>: + * Source Connection ID in hex string. + * + * <EVENT>: + * Event. See ngtcp2_log_event. + * + * # Frame event + * + * <DIR> <PKN> <PKTNAME> <FRAMENAME>(<FRAMETYPE>) + * + * <DIR>: + * Flow direction. tx=transmission, rx=reception + * + * <PKN>: + * Packet number. + * + * <PKTNAME>: + * Packet name. (e.g., Initial, Handshake, 1RTT) + * + * <FRAMENAME>: + * Frame name. (e.g., STREAM, ACK, PING) + * + * <FRAMETYPE>: + * Frame type in hex string. + */ + +#define NGTCP2_LOG_BUFLEN 4096 + +/* 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" + +static const char *strerrorcode(uint64_t error_code) { + switch (error_code) { + case NGTCP2_NO_ERROR: + return "NO_ERROR"; + case NGTCP2_INTERNAL_ERROR: + return "INTERNAL_ERROR"; + case NGTCP2_CONNECTION_REFUSED: + return "CONNECTION_REFUSED"; + case NGTCP2_FLOW_CONTROL_ERROR: + return "FLOW_CONTROL_ERROR"; + case NGTCP2_STREAM_LIMIT_ERROR: + return "STREAM_LIMIT_ERROR"; + case NGTCP2_STREAM_STATE_ERROR: + return "STREAM_STATE_ERROR"; + case NGTCP2_FINAL_SIZE_ERROR: + return "FINAL_SIZE_ERROR"; + case NGTCP2_FRAME_ENCODING_ERROR: + return "FRAME_ENCODING_ERROR"; + case NGTCP2_TRANSPORT_PARAMETER_ERROR: + return "TRANSPORT_PARAMETER_ERROR"; + case NGTCP2_CONNECTION_ID_LIMIT_ERROR: + return "CONNECTION_ID_LIMIT_ERROR"; + case NGTCP2_PROTOCOL_VIOLATION: + return "PROTOCOL_VIOLATION"; + case NGTCP2_INVALID_TOKEN: + return "INVALID_TOKEN"; + case NGTCP2_APPLICATION_ERROR: + return "APPLICATION_ERROR"; + case NGTCP2_CRYPTO_BUFFER_EXCEEDED: + return "CRYPTO_BUFFER_EXCEEDED"; + case NGTCP2_KEY_UPDATE_ERROR: + return "KEY_UPDATE_ERROR"; + case NGTCP2_VERSION_NEGOTIATION_ERROR: + return "VERSION_NEGOTIATION_ERROR"; + default: + if (0x100u <= error_code && error_code <= 0x1ffu) { + return "CRYPTO_ERROR"; + } + return "(unknown)"; + } +} + +static const char *strapperrorcode(uint64_t app_error_code) { + (void)app_error_code; + return "(unknown)"; +} + +static const char *strpkttype_long(uint8_t type) { + switch (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_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"; + } +} + +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, fr->offset, ngtcp2_vec_len(fr->data, fr->datacnt), + (fr->stream_id & 0x2) != 0); +} + +static void log_fr_ack(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_ack *fr, const char *dir) { + 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, + 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); + + 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); + } + + 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); + } +} + +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); +} + +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, + strapperrorcode(fr->app_error_code), fr->app_error_code, fr->final_size); +} + +static void log_fr_connection_close(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_connection_close *fr, + const char *dir) { + char reason[256]; + size_t reasonlen = ngtcp2_min(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, + 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)); +} + +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); +} + +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); +} + +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); +} + +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); +} + +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); +} + +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); +} + +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); +} + +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), + fr->retire_prior_to, + (const char *)ngtcp2_encode_hex(buf, fr->stateless_reset_token, + sizeof(fr->stateless_reset_token))); +} + +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); +} + +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]; + + 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))); +} + +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]; + + 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))); +} + +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)); +} + +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; + + if (fr->tokenlen > 64) { + p = ngtcp2_encode_hex(buf, fr->token, 64); + p[128] = '*'; + p[129] = '\0'; + } else { + p = ngtcp2_encode_hex(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); +} + +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); +} + +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); +} + +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)); +} + +static void log_fr(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_frame *fr, const char *dir) { + switch (fr->type) { + case NGTCP2_FRAME_STREAM: + log_fr_stream(log, hd, &fr->stream, dir); + break; + case NGTCP2_FRAME_ACK: + case NGTCP2_FRAME_ACK_ECN: + log_fr_ack(log, hd, &fr->ack, dir); + break; + case NGTCP2_FRAME_PADDING: + log_fr_padding(log, hd, &fr->padding, dir); + break; + case NGTCP2_FRAME_RESET_STREAM: + log_fr_reset_stream(log, hd, &fr->reset_stream, dir); + break; + case NGTCP2_FRAME_CONNECTION_CLOSE: + case NGTCP2_FRAME_CONNECTION_CLOSE_APP: + log_fr_connection_close(log, hd, &fr->connection_close, dir); + break; + case NGTCP2_FRAME_MAX_DATA: + log_fr_max_data(log, hd, &fr->max_data, dir); + break; + case NGTCP2_FRAME_MAX_STREAM_DATA: + log_fr_max_stream_data(log, hd, &fr->max_stream_data, dir); + break; + case NGTCP2_FRAME_MAX_STREAMS_BIDI: + case NGTCP2_FRAME_MAX_STREAMS_UNI: + log_fr_max_streams(log, hd, &fr->max_streams, dir); + break; + case NGTCP2_FRAME_PING: + log_fr_ping(log, hd, &fr->ping, dir); + break; + case NGTCP2_FRAME_DATA_BLOCKED: + log_fr_data_blocked(log, hd, &fr->data_blocked, dir); + break; + case NGTCP2_FRAME_STREAM_DATA_BLOCKED: + log_fr_stream_data_blocked(log, hd, &fr->stream_data_blocked, dir); + break; + case NGTCP2_FRAME_STREAMS_BLOCKED_BIDI: + case NGTCP2_FRAME_STREAMS_BLOCKED_UNI: + log_fr_streams_blocked(log, hd, &fr->streams_blocked, dir); + break; + case NGTCP2_FRAME_NEW_CONNECTION_ID: + log_fr_new_connection_id(log, hd, &fr->new_connection_id, dir); + break; + case NGTCP2_FRAME_STOP_SENDING: + log_fr_stop_sending(log, hd, &fr->stop_sending, dir); + break; + case NGTCP2_FRAME_PATH_CHALLENGE: + log_fr_path_challenge(log, hd, &fr->path_challenge, dir); + break; + case NGTCP2_FRAME_PATH_RESPONSE: + log_fr_path_response(log, hd, &fr->path_response, dir); + break; + case NGTCP2_FRAME_CRYPTO: + log_fr_crypto(log, hd, &fr->stream, dir); + break; + case NGTCP2_FRAME_NEW_TOKEN: + log_fr_new_token(log, hd, &fr->new_token, dir); + break; + case NGTCP2_FRAME_RETIRE_CONNECTION_ID: + log_fr_retire_connection_id(log, hd, &fr->retire_connection_id, dir); + break; + case NGTCP2_FRAME_HANDSHAKE_DONE: + log_fr_handshake_done(log, hd, &fr->handshake_done, dir); + break; + case NGTCP2_FRAME_DATAGRAM: + case NGTCP2_FRAME_DATAGRAM_LEN: + log_fr_datagram(log, hd, &fr->datagram, dir); + break; + default: + ngtcp2_unreachable(); + } +} + +void ngtcp2_log_rx_fr(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_frame *fr) { + if (!log->log_printf || !(log->events & NGTCP2_LOG_EVENT_FRM)) { + return; + } + + log_fr(log, hd, fr, "rx"); +} + +void ngtcp2_log_tx_fr(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_frame *fr) { + if (!log->log_printf || !(log->events & NGTCP2_LOG_EVENT_FRM)) { + return; + } + + log_fr(log, hd, fr, "tx"); +} + +void ngtcp2_log_rx_vn(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const uint32_t *sv, size_t nsv) { + size_t i; + + if (!log->log_printf || !(log->events & NGTCP2_LOG_EVENT_PKT)) { + return; + } + + 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]); + } +} + +void ngtcp2_log_rx_sr(ngtcp2_log *log, const ngtcp2_pkt_stateless_reset *sr) { + uint8_t buf[sizeof(sr->stateless_reset_token) * 2 + 1]; + ngtcp2_pkt_hd shd; + ngtcp2_pkt_hd *hd = &shd; + + if (!log->log_printf || !(log->events & NGTCP2_LOG_EVENT_PKT)) { + return; + } + + memset(&shd, 0, sizeof(shd)); + + shd.type = NGTCP2_PKT_STATELESS_RESET; + + log->log_printf( + log->user_data, (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)), + 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]; + size_t i; + const ngtcp2_sockaddr_in *sa_in; + const ngtcp2_sockaddr_in6 *sa_in6; + const uint8_t *p; + uint32_t version; + + if (!log->log_printf || !(log->events & NGTCP2_LOG_EVENT_CRY)) { + return; + } + + 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))); + } + + 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)); + } + + 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)); + } + + 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( + 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)); + } + + 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)); + } + + 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)); + } + + 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); + + 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); + + assert(!(params->version_info.available_versionslen & 0x3)); + + for (i = 0, p = params->version_info.available_versions; + i < params->version_info.available_versionslen; + i += sizeof(uint32_t)) { + p = ngtcp2_get_uint32(&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); + } + } +} + +void ngtcp2_log_pkt_lost(ngtcp2_log *log, int64_t pkt_num, uint8_t type, + uint8_t flags, ngtcp2_tstamp sent_ts) { + if (!log->log_printf || !(log->events & NGTCP2_LOG_EVENT_LDC)) { + 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); +} + +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]; + + if (!log->log_printf || !(log->events & NGTCP2_LOG_EVENT_PKT)) { + return; + } + + if (hd->type == NGTCP2_PKT_1RTT) { + ngtcp2_log_info( + 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), + strpkttype(hd), (hd->flags & NGTCP2_PKT_FLAG_KEY_PHASE) != 0); + } else { + ngtcp2_log_info( + 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), + hd->version, strpkttype(hd), hd->len); + } +} + +void ngtcp2_log_rx_pkt_hd(ngtcp2_log *log, const ngtcp2_pkt_hd *hd) { + log_pkt_hd(log, hd, "rx"); +} + +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)); +} diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_log.h b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_log.h new file mode 100644 index 0000000..1280ce0 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_log.h @@ -0,0 +1,132 @@ +/* + * ngtcp2 + * + * Copyright (c) 2018 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_LOG_H +#define NGTCP2_LOG_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_pkt.h" + +typedef struct ngtcp2_log { + /* log_printf is a sink to write log. NULL means no logging + output. */ + ngtcp2_printf log_printf; + /* events is an event filter. Only events set in this field are + emitted. */ + uint8_t events; + /* ts is the time point used to write time delta in the log. */ + ngtcp2_tstamp ts; + /* last_ts is the most recent time point that this object is + told. */ + ngtcp2_tstamp last_ts; + /* user_data is user-defined opaque data which is passed to + log_pritnf. */ + void *user_data; + /* scid is SCID encoded as NULL-terminated hex string. */ + uint8_t scid[NGTCP2_MAX_CIDLEN * 2 + 1]; +} ngtcp2_log; + +/** + * @enum + * + * :type:`ngtcp2_log_event` defines an event of ngtcp2 library + * internal logger. + */ +typedef enum ngtcp2_log_event { + /** + * :enum:`NGTCP2_LOG_EVENT_NONE` represents no event. + */ + NGTCP2_LOG_EVENT_NONE, + /** + * :enum:`NGTCP2_LOG_EVENT_CON` is a connection (catch-all) event + */ + NGTCP2_LOG_EVENT_CON = 0x1, + /** + * :enum:`NGTCP2_LOG_EVENT_PKT` is a packet event. + */ + NGTCP2_LOG_EVENT_PKT = 0x2, + /** + * :enum:`NGTCP2_LOG_EVENT_FRM` is a QUIC frame event. + */ + NGTCP2_LOG_EVENT_FRM = 0x4, + /** + * :enum:`NGTCP2_LOG_EVENT_LDC` is a loss detection and congestion + * control event. + */ + NGTCP2_LOG_EVENT_LDC = 0x8, + /** + * :enum:`NGTCP2_LOG_EVENT_CRY` is a crypto event. + */ + NGTCP2_LOG_EVENT_CRY = 0x10, + /** + * :enum:`NGTCP2_LOG_EVENT_PTV` is a path validation event. + */ + NGTCP2_LOG_EVENT_PTV = 0x20, + /** + * :enum:`NGTCP2_LOG_EVENT_CCA` is a congestion controller algorithm + * event. + */ + NGTCP2_LOG_EVENT_CCA = 0x40, +} ngtcp2_log_event; + +void ngtcp2_log_init(ngtcp2_log *log, const ngtcp2_cid *scid, + ngtcp2_printf log_printf, ngtcp2_tstamp ts, + void *user_data); + +void ngtcp2_log_rx_fr(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_frame *fr); +void ngtcp2_log_tx_fr(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_frame *fr); + +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_remote_tp(ngtcp2_log *log, + const ngtcp2_transport_params *params); + +void ngtcp2_log_pkt_lost(ngtcp2_log *log, int64_t pkt_num, uint8_t type, + uint8_t flags, ngtcp2_tstamp sent_ts); + +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); + +/** + * @function + * + * `ngtcp2_log_info` writes info level log. + */ +void ngtcp2_log_info(ngtcp2_log *log, ngtcp2_log_event ev, const char *fmt, + ...); + +#endif /* NGTCP2_LOG_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_macro.h b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_macro.h new file mode 100644 index 0000000..28d3461 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_macro.h @@ -0,0 +1,58 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 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_MACRO_H +#define NGTCP2_MACRO_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <stddef.h> + +#include <ngtcp2/ngtcp2.h> + +#define ngtcp2_min(A, B) ((A) < (B) ? (A) : (B)) +#define ngtcp2_max(A, B) ((A) > (B) ? (A) : (B)) + +#define ngtcp2_struct_of(ptr, type, member) \ + ((type *)(void *)((char *)(ptr)-offsetof(type, member))) + +/* ngtcp2_list_insert inserts |T| before |*PD|. The contract is that + this is singly linked list, and the next element is pointed by next + field of the previous element. |PD| must be a pointer to the + pointer to the next field of the previous element of |*PD|: if C is + the previous element of |PD|, PD = &C->next. */ +#define ngtcp2_list_insert(T, PD) \ + do { \ + (T)->next = *(PD); \ + *(PD) = (T); \ + } while (0) + +/* + * ngtcp2_arraylen returns the number of elements in array |A|. + */ +#define ngtcp2_arraylen(A) (sizeof(A) / sizeof(A[0])) + +#endif /* NGTCP2_MACRO_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_map.c b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_map.c new file mode 100644 index 0000000..33e9fcc --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_map.c @@ -0,0 +1,338 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * Copyright (c) 2012 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_map.h" + +#include <string.h> +#include <assert.h> +#include <stdio.h> + +#include "ngtcp2_conv.h" + +#define NGTCP2_INITIAL_TABLE_LENBITS 4 + +void ngtcp2_map_init(ngtcp2_map *map, const ngtcp2_mem *mem) { + map->mem = mem; + map->tablelen = 0; + map->tablelenbits = 0; + map->table = NULL; + map->size = 0; +} + +void ngtcp2_map_free(ngtcp2_map *map) { + if (!map) { + return; + } + + ngtcp2_mem_free(map->mem, map->table); +} + +void ngtcp2_map_each_free(ngtcp2_map *map, int (*func)(void *data, void *ptr), + void *ptr) { + uint32_t i; + ngtcp2_map_bucket *bkt; + + for (i = 0; i < map->tablelen; ++i) { + bkt = &map->table[i]; + + if (bkt->data == NULL) { + continue; + } + + func(bkt->data, ptr); + } +} + +int ngtcp2_map_each(ngtcp2_map *map, int (*func)(void *data, void *ptr), + void *ptr) { + int rv; + uint32_t i; + ngtcp2_map_bucket *bkt; + + if (map->size == 0) { + return 0; + } + + for (i = 0; i < map->tablelen; ++i) { + bkt = &map->table[i]; + + if (bkt->data == NULL) { + continue; + } + + rv = func(bkt->data, ptr); + if (rv != 0) { + return rv; + } + } + + return 0; +} + +static uint32_t hash(ngtcp2_map_key_type key) { + return (uint32_t)((key * 11400714819323198485llu) >> 32); +} + +static size_t h2idx(uint32_t hash, uint32_t bits) { + return hash >> (32 - bits); +} + +static size_t distance(uint32_t tablelen, uint32_t tablelenbits, + ngtcp2_map_bucket *bkt, size_t idx) { + return (idx - h2idx(bkt->hash, tablelenbits)) & (tablelen - 1); +} + +static void map_bucket_swap(ngtcp2_map_bucket *bkt, uint32_t *phash, + ngtcp2_map_key_type *pkey, void **pdata) { + uint32_t h = bkt->hash; + ngtcp2_map_key_type key = bkt->key; + void *data = bkt->data; + + bkt->hash = *phash; + bkt->key = *pkey; + bkt->data = *pdata; + + *phash = h; + *pkey = key; + *pdata = data; +} + +static void map_bucket_set_data(ngtcp2_map_bucket *bkt, uint32_t hash, + ngtcp2_map_key_type key, void *data) { + bkt->hash = hash; + bkt->key = key; + bkt->data = data; +} + +#ifndef WIN32 +void ngtcp2_map_print_distance(ngtcp2_map *map) { + uint32_t i; + size_t idx; + ngtcp2_map_bucket *bkt; + + for (i = 0; i < map->tablelen; ++i) { + bkt = &map->table[i]; + + if (bkt->data == NULL) { + fprintf(stderr, "@%u <EMPTY>\n", i); + continue; + } + + idx = h2idx(bkt->hash, map->tablelenbits); + fprintf(stderr, "@%u hash=%08x key=%" PRIu64 " base=%zu distance=%zu\n", i, + bkt->hash, bkt->key, idx, + distance(map->tablelen, map->tablelenbits, bkt, idx)); + } +} +#endif /* !WIN32 */ + +static int insert(ngtcp2_map_bucket *table, uint32_t tablelen, + uint32_t tablelenbits, uint32_t hash, ngtcp2_map_key_type key, + void *data) { + size_t idx = h2idx(hash, tablelenbits); + size_t d = 0, dd; + ngtcp2_map_bucket *bkt; + + for (;;) { + bkt = &table[idx]; + + if (bkt->data == NULL) { + map_bucket_set_data(bkt, hash, key, data); + return 0; + } + + dd = distance(tablelen, tablelenbits, bkt, idx); + if (d > dd) { + map_bucket_swap(bkt, &hash, &key, &data); + d = dd; + } 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. */ + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + ++d; + idx = (idx + 1) & (tablelen - 1); + } +} + +/* new_tablelen must be power of 2 and new_tablelen == (1 << + new_tablelenbits) must hold. */ +static int map_resize(ngtcp2_map *map, uint32_t new_tablelen, + uint32_t new_tablelenbits) { + uint32_t i; + ngtcp2_map_bucket *new_table; + ngtcp2_map_bucket *bkt; + int rv; + (void)rv; + + new_table = + ngtcp2_mem_calloc(map->mem, new_tablelen, sizeof(ngtcp2_map_bucket)); + if (new_table == NULL) { + return NGTCP2_ERR_NOMEM; + } + + for (i = 0; i < map->tablelen; ++i) { + bkt = &map->table[i]; + if (bkt->data == NULL) { + continue; + } + rv = insert(new_table, new_tablelen, new_tablelenbits, bkt->hash, bkt->key, + bkt->data); + + assert(0 == rv); + } + + ngtcp2_mem_free(map->mem, map->table); + map->tablelen = new_tablelen; + map->tablelenbits = new_tablelenbits; + map->table = new_table; + + return 0; +} + +int ngtcp2_map_insert(ngtcp2_map *map, ngtcp2_map_key_type key, void *data) { + int rv; + + assert(data); + + /* Load factor is 0.75 */ + if ((map->size + 1) * 4 > map->tablelen * 3) { + if (map->tablelen) { + rv = map_resize(map, map->tablelen * 2, map->tablelenbits + 1); + if (rv != 0) { + return rv; + } + } else { + rv = map_resize(map, 1 << NGTCP2_INITIAL_TABLE_LENBITS, + NGTCP2_INITIAL_TABLE_LENBITS); + if (rv != 0) { + return rv; + } + } + } + + rv = insert(map->table, map->tablelen, map->tablelenbits, hash(key), key, + data); + if (rv != 0) { + return rv; + } + ++map->size; + return 0; +} + +void *ngtcp2_map_find(ngtcp2_map *map, ngtcp2_map_key_type key) { + uint32_t h; + size_t idx; + ngtcp2_map_bucket *bkt; + size_t d = 0; + + if (map->size == 0) { + return NULL; + } + + h = hash(key); + idx = h2idx(h, map->tablelenbits); + + for (;;) { + bkt = &map->table[idx]; + + if (bkt->data == NULL || + d > distance(map->tablelen, map->tablelenbits, bkt, idx)) { + return NULL; + } + + if (bkt->key == key) { + return bkt->data; + } + + ++d; + idx = (idx + 1) & (map->tablelen - 1); + } +} + +int ngtcp2_map_remove(ngtcp2_map *map, ngtcp2_map_key_type key) { + uint32_t h; + size_t idx, didx; + ngtcp2_map_bucket *bkt; + size_t d = 0; + + if (map->size == 0) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + h = hash(key); + idx = h2idx(h, map->tablelenbits); + + for (;;) { + bkt = &map->table[idx]; + + if (bkt->data == NULL || + d > distance(map->tablelen, map->tablelenbits, bkt, idx)) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + if (bkt->key == key) { + map_bucket_set_data(bkt, 0, 0, NULL); + + didx = idx; + idx = (idx + 1) & (map->tablelen - 1); + + for (;;) { + bkt = &map->table[idx]; + if (bkt->data == NULL || + distance(map->tablelen, map->tablelenbits, bkt, idx) == 0) { + break; + } + + map->table[didx] = *bkt; + map_bucket_set_data(bkt, 0, 0, NULL); + didx = idx; + + idx = (idx + 1) & (map->tablelen - 1); + } + + --map->size; + + return 0; + } + + ++d; + idx = (idx + 1) & (map->tablelen - 1); + } +} + +void ngtcp2_map_clear(ngtcp2_map *map) { + if (map->tablelen == 0) { + return; + } + + memset(map->table, 0, sizeof(*map->table) * map->tablelen); + map->size = 0; +} + +size_t ngtcp2_map_size(ngtcp2_map *map) { return map->size; } diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_map.h b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_map.h new file mode 100644 index 0000000..d05b165 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_map.h @@ -0,0 +1,138 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * Copyright (c) 2012 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_MAP_H +#define NGTCP2_MAP_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_mem.h" + +/* Implementation of unordered map */ + +typedef uint64_t ngtcp2_map_key_type; + +typedef struct ngtcp2_map_bucket { + uint32_t hash; + ngtcp2_map_key_type key; + void *data; +} ngtcp2_map_bucket; + +typedef struct ngtcp2_map { + ngtcp2_map_bucket *table; + const ngtcp2_mem *mem; + size_t size; + uint32_t tablelen; + uint32_t tablelenbits; +} ngtcp2_map; + +/* + * Initializes the map |map|. + */ +void ngtcp2_map_init(ngtcp2_map *map, const ngtcp2_mem *mem); + +/* + * Deallocates any resources allocated for |map|. The stored entries + * are not freed by this function. Use ngtcp2_map_each_free() to free + * each entries. + */ +void ngtcp2_map_free(ngtcp2_map *map); + +/* + * Deallocates each entries using |func| function and any resources + * allocated for |map|. The |func| function is responsible for freeing + * given the |data| object. The |ptr| will be passed to the |func| as + * send argument. The return value of the |func| will be ignored. + */ +void ngtcp2_map_each_free(ngtcp2_map *map, int (*func)(void *data, void *ptr), + void *ptr); + +/* + * Inserts the new |data| with the |key| to the map |map|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_INVALID_ARGUMENT + * The item associated by |key| already exists. + * NGTCP2_ERR_NOMEM + * Out of memory + */ +int ngtcp2_map_insert(ngtcp2_map *map, ngtcp2_map_key_type key, void *data); + +/* + * Returns the data associated by the key |key|. If there is no such + * data, this function returns NULL. + */ +void *ngtcp2_map_find(ngtcp2_map *map, ngtcp2_map_key_type key); + +/* + * Removes the data associated by the key |key| from the |map|. The + * removed data is not freed by this function. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_INVALID_ARGUMENT + * The data associated by |key| does not exist. + */ +int ngtcp2_map_remove(ngtcp2_map *map, ngtcp2_map_key_type key); + +/* + * Removes all entries from |map|. + */ +void ngtcp2_map_clear(ngtcp2_map *map); + +/* + * Returns the number of items stored in the map |map|. + */ +size_t ngtcp2_map_size(ngtcp2_map *map); + +/* + * Applies the function |func| to each data in the |map| with the + * optional user supplied pointer |ptr|. + * + * If the |func| returns 0, this function calls the |func| with the + * next data. If the |func| returns nonzero, it will not call the + * |func| for further entries and return the return value of the + * |func| immediately. Thus, this function returns 0 if all the + * invocations of the |func| return 0, or nonzero value which the last + * invocation of |func| returns. + * + * Don't use this function to free each data. Use + * ngtcp2_map_each_free() instead. + */ +int ngtcp2_map_each(ngtcp2_map *map, int (*func)(void *data, void *ptr), + void *ptr); + +#ifndef WIN32 +void ngtcp2_map_print_distance(ngtcp2_map *map); +#endif /* !WIN32 */ + +#endif /* NGTCP2_MAP_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_mem.c b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_mem.c new file mode 100644 index 0000000..bcce0b5 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_mem.c @@ -0,0 +1,113 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * Copyright (c) 2014 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_mem.h" + +#include <stdio.h> + +static void *default_malloc(size_t size, void *user_data) { + (void)user_data; + + return malloc(size); +} + +static void default_free(void *ptr, void *user_data) { + (void)user_data; + + free(ptr); +} + +static void *default_calloc(size_t nmemb, size_t size, void *user_data) { + (void)user_data; + + return calloc(nmemb, size); +} + +static void *default_realloc(void *ptr, size_t size, void *user_data) { + (void)user_data; + + return realloc(ptr, size); +} + +static const ngtcp2_mem mem_default = {NULL, default_malloc, default_free, + default_calloc, default_realloc}; + +const ngtcp2_mem *ngtcp2_mem_default(void) { return &mem_default; } + +#ifndef MEMDEBUG +void *ngtcp2_mem_malloc(const ngtcp2_mem *mem, size_t size) { + return mem->malloc(size, mem->user_data); +} + +void ngtcp2_mem_free(const ngtcp2_mem *mem, void *ptr) { + mem->free(ptr, mem->user_data); +} + +void *ngtcp2_mem_calloc(const ngtcp2_mem *mem, size_t nmemb, size_t size) { + return mem->calloc(nmemb, size, mem->user_data); +} + +void *ngtcp2_mem_realloc(const ngtcp2_mem *mem, void *ptr, size_t size) { + return mem->realloc(ptr, size, mem->user_data); +} +#else /* MEMDEBUG */ +void *ngtcp2_mem_malloc_debug(const ngtcp2_mem *mem, size_t size, + const char *func, const char *file, size_t line) { + void *nptr = mem->malloc(size, mem->user_data); + + fprintf(stderr, "malloc %p size=%zu in %s at %s:%zu\n", nptr, size, func, + file, line); + + return nptr; +} + +void ngtcp2_mem_free_debug(const ngtcp2_mem *mem, void *ptr, const char *func, + const char *file, size_t line) { + fprintf(stderr, "free ptr=%p in %s at %s:%zu\n", ptr, func, file, line); + + mem->free(ptr, mem->user_data); +} + +void *ngtcp2_mem_calloc_debug(const ngtcp2_mem *mem, size_t nmemb, size_t size, + const char *func, const char *file, size_t line) { + void *nptr = mem->calloc(nmemb, size, mem->user_data); + + fprintf(stderr, "calloc %p nmemb=%zu size=%zu in %s at %s:%zu\n", nptr, nmemb, + size, func, file, line); + + return nptr; +} + +void *ngtcp2_mem_realloc_debug(const ngtcp2_mem *mem, void *ptr, size_t size, + const char *func, const char *file, + size_t line) { + void *nptr = mem->realloc(ptr, size, mem->user_data); + + fprintf(stderr, "realloc %p ptr=%p size=%zu in %s at %s:%zu\n", nptr, ptr, + size, func, file, line); + + return nptr; +} +#endif /* MEMDEBUG */ diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_mem.h b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_mem.h new file mode 100644 index 0000000..c99b6c5 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_mem.h @@ -0,0 +1,72 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * Copyright (c) 2014 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_MEM_H +#define NGTCP2_MEM_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +/* Convenient wrapper functions to call allocator function in + |mem|. */ +#ifndef MEMDEBUG +void *ngtcp2_mem_malloc(const ngtcp2_mem *mem, size_t size); + +void ngtcp2_mem_free(const ngtcp2_mem *mem, void *ptr); + +void *ngtcp2_mem_calloc(const ngtcp2_mem *mem, size_t nmemb, size_t size); + +void *ngtcp2_mem_realloc(const ngtcp2_mem *mem, void *ptr, size_t size); +#else /* MEMDEBUG */ +void *ngtcp2_mem_malloc_debug(const ngtcp2_mem *mem, size_t size, + const char *func, const char *file, size_t line); + +# define ngtcp2_mem_malloc(MEM, SIZE) \ + ngtcp2_mem_malloc_debug((MEM), (SIZE), __func__, __FILE__, __LINE__) + +void ngtcp2_mem_free_debug(const ngtcp2_mem *mem, void *ptr, const char *func, + const char *file, size_t line); + +# define ngtcp2_mem_free(MEM, PTR) \ + ngtcp2_mem_free_debug((MEM), (PTR), __func__, __FILE__, __LINE__) + +void *ngtcp2_mem_calloc_debug(const ngtcp2_mem *mem, size_t nmemb, size_t size, + const char *func, const char *file, size_t line); + +# define ngtcp2_mem_calloc(MEM, NMEMB, SIZE) \ + ngtcp2_mem_calloc_debug((MEM), (NMEMB), (SIZE), __func__, __FILE__, \ + __LINE__) + +void *ngtcp2_mem_realloc_debug(const ngtcp2_mem *mem, void *ptr, size_t size, + const char *func, const char *file, size_t line); + +# define ngtcp2_mem_realloc(MEM, PTR, SIZE) \ + ngtcp2_mem_realloc_debug((MEM), (PTR), (SIZE), __func__, __FILE__, __LINE__) +#endif /* MEMDEBUG */ + +#endif /* NGTCP2_MEM_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_net.h b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_net.h new file mode 100644 index 0000000..4a2c404 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_net.h @@ -0,0 +1,143 @@ +/* + * ngtcp2 + * + * Copyright (c) 2022 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_NET_H +#define NGTCP2_NET_H + +/* This header file is explicitly allowed to be shared with + ngtcp2_crypto library. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#ifdef HAVE_ARPA_INET_H +# include <arpa/inet.h> +#endif /* HAVE_ARPA_INET_H */ + +#ifdef HAVE_NETINET_IN_H +# include <netinet/in.h> +#endif /* HAVE_NETINET_IN_H */ + +#ifdef HAVE_BYTESWAP_H +# include <byteswap.h> +#endif /* HAVE_BYTESWAP_H */ + +#ifdef HAVE_ENDIAN_H +# include <endian.h> +#endif /* HAVE_ENDIAN_H */ + +#ifdef HAVE_SYS_ENDIAN_H +# include <sys/endian.h> +#endif /* HAVE_SYS_ENDIAN_H */ + +#if defined(__APPLE__) +# include <libkern/OSByteOrder.h> +#endif // __APPLE__ + +#include <ngtcp2/ngtcp2.h> + +#if defined(HAVE_BE64TOH) || \ + (defined(HAVE_DECL_BE64TOH) && HAVE_DECL_BE64TOH > 0) +# define ngtcp2_ntohl64(N) be64toh(N) +# define ngtcp2_htonl64(N) htobe64(N) +#else /* !HAVE_BE64TOH */ +# if defined(WORDS_BIGENDIAN) +# define ngtcp2_ntohl64(N) (N) +# define ngtcp2_htonl64(N) (N) +# else /* !WORDS_BIGENDIAN */ +# if defined(HAVE_BSWAP_64) || \ + (defined(HAVE_DECL_BSWAP_64) && HAVE_DECL_BSWAP_64 > 0) +# define ngtcp2_bswap64 bswap_64 +# elif defined(WIN32) +# define ngtcp2_bswap64 _byteswap_uint64 +# elif defined(__APPLE__) +# define ngtcp2_bswap64 OSSwapInt64 +# else /* !HAVE_BSWAP_64 && !WIN32 && !__APPLE__ */ +# define ngtcp2_bswap64(N) \ + ((uint64_t)(ngtcp2_ntohl((uint32_t)(N))) << 32 | \ + ngtcp2_ntohl((uint32_t)((N) >> 32))) +# endif /* !HAVE_BSWAP_64 && !WIN32 && !__APPLE__ */ +# define ngtcp2_ntohl64(N) ngtcp2_bswap64(N) +# define ngtcp2_htonl64(N) ngtcp2_bswap64(N) +# endif /* !WORDS_BIGENDIAN */ +#endif /* !HAVE_BE64TOH */ + +#if defined(WIN32) +/* Windows requires ws2_32 library for ntonl family functions. We + define inline functions for those function so that we don't have + dependency on that lib. */ + +# ifdef _MSC_VER +# define STIN static __inline +# else +# define STIN static inline +# endif + +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; + return res; +} + +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; + return res; +} + +STIN uint32_t ngtcp2_ntohl(uint32_t netlong) { + uint32_t res; + unsigned char *p = (unsigned char *)&netlong; + res = (uint32_t)(*p++ << 24); + res += (uint32_t)(*p++ << 16); + res += (uint32_t)(*p++ << 8); + res += *p; + return res; +} + +STIN uint16_t ngtcp2_ntohs(uint16_t netshort) { + uint16_t res; + unsigned char *p = (unsigned char *)&netshort; + res = (uint16_t)(*p++ << 8); + res += *p; + return res; +} + +#else /* !WIN32 */ + +# define ngtcp2_htonl htonl +# define ngtcp2_htons htons +# define ngtcp2_ntohl ntohl +# define ngtcp2_ntohs ntohs + +#endif /* !WIN32 */ + +#endif /* NGTCP2_NET_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_objalloc.c b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_objalloc.c new file mode 100644 index 0000000..8b06cdd --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_objalloc.c @@ -0,0 +1,40 @@ +/* + * ngtcp2 + * + * Copyright (c) 2022 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_objalloc.h" + +void ngtcp2_objalloc_init(ngtcp2_objalloc *objalloc, size_t blklen, + const ngtcp2_mem *mem) { + ngtcp2_balloc_init(&objalloc->balloc, blklen, mem); + ngtcp2_opl_init(&objalloc->opl); +} + +void ngtcp2_objalloc_free(ngtcp2_objalloc *objalloc) { + ngtcp2_balloc_free(&objalloc->balloc); +} + +void ngtcp2_objalloc_clear(ngtcp2_objalloc *objalloc) { + ngtcp2_opl_clear(&objalloc->opl); + ngtcp2_balloc_clear(&objalloc->balloc); +} diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_objalloc.h b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_objalloc.h new file mode 100644 index 0000000..ea73e78 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_objalloc.h @@ -0,0 +1,147 @@ +/* + * ngtcp2 + * + * Copyright (c) 2022 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_OBJALLOC_H +#define NGTCP2_OBJALLOC_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_balloc.h" +#include "ngtcp2_opl.h" +#include "ngtcp2_macro.h" +#include "ngtcp2_mem.h" + +/* + * ngtcp2_objalloc combines ngtcp2_balloc and ngtcp2_opl, and provides + * an object pool with the custom allocator to reduce the allocation + * and deallocation overheads for small objects. + */ +typedef struct ngtcp2_objalloc { + ngtcp2_balloc balloc; + ngtcp2_opl opl; +} ngtcp2_objalloc; + +/* + * ngtcp2_objalloc_init initializes |objalloc|. |blklen| is directly + * passed to ngtcp2_balloc_init. + */ +void ngtcp2_objalloc_init(ngtcp2_objalloc *objalloc, size_t blklen, + const ngtcp2_mem *mem); + +/* + * ngtcp2_objalloc_free releases all allocated resources. + */ +void ngtcp2_objalloc_free(ngtcp2_objalloc *objalloc); + +/* + * ngtcp2_objalloc_clear releases all allocated resources and + * initializes its state. + */ +void ngtcp2_objalloc_clear(ngtcp2_objalloc *objalloc); + +#ifndef NOMEMPOOL +# define ngtcp2_objalloc_decl(NAME, TYPE, OPLENTFIELD) \ + 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); \ + } \ + \ + TYPE *ngtcp2_objalloc_##NAME##_get(ngtcp2_objalloc *objalloc); \ + \ + TYPE *ngtcp2_objalloc_##NAME##_len_get(ngtcp2_objalloc *objalloc, \ + size_t len); \ + \ + inline static void ngtcp2_objalloc_##NAME##_release( \ + ngtcp2_objalloc *objalloc, TYPE *obj) { \ + ngtcp2_opl_push(&objalloc->opl, &obj->OPLENTFIELD); \ + } + +# define ngtcp2_objalloc_def(NAME, TYPE, OPLENTFIELD) \ + TYPE *ngtcp2_objalloc_##NAME##_get(ngtcp2_objalloc *objalloc) { \ + ngtcp2_opl_entry *oplent = ngtcp2_opl_pop(&objalloc->opl); \ + TYPE *obj; \ + int rv; \ + \ + if (!oplent) { \ + rv = \ + ngtcp2_balloc_get(&objalloc->balloc, (void **)&obj, sizeof(TYPE)); \ + if (rv != 0) { \ + return NULL; \ + } \ + \ + return obj; \ + } \ + \ + return ngtcp2_struct_of(oplent, TYPE, OPLENTFIELD); \ + } \ + \ + TYPE *ngtcp2_objalloc_##NAME##_len_get(ngtcp2_objalloc *objalloc, \ + size_t len) { \ + ngtcp2_opl_entry *oplent = ngtcp2_opl_pop(&objalloc->opl); \ + TYPE *obj; \ + int rv; \ + \ + if (!oplent) { \ + rv = ngtcp2_balloc_get(&objalloc->balloc, (void **)&obj, len); \ + if (rv != 0) { \ + return NULL; \ + } \ + \ + return obj; \ + } \ + \ + return ngtcp2_struct_of(oplent, TYPE, OPLENTFIELD); \ + } +#else /* NOMEMPOOL */ +# define ngtcp2_objalloc_decl(NAME, TYPE, OPLENTFIELD) \ + 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); \ + } \ + \ + inline static TYPE *ngtcp2_objalloc_##NAME##_get( \ + ngtcp2_objalloc *objalloc) { \ + return ngtcp2_mem_malloc(objalloc->balloc.mem, sizeof(TYPE)); \ + } \ + \ + inline static TYPE *ngtcp2_objalloc_##NAME##_len_get( \ + ngtcp2_objalloc *objalloc, size_t len) { \ + return ngtcp2_mem_malloc(objalloc->balloc.mem, len); \ + } \ + \ + inline static void ngtcp2_objalloc_##NAME##_release( \ + ngtcp2_objalloc *objalloc, TYPE *obj) { \ + ngtcp2_mem_free(objalloc->balloc.mem, obj); \ + } + +# define ngtcp2_objalloc_def(NAME, TYPE, OPLENTFIELD) +#endif /* NOMEMPOOL */ + +#endif /* NGTCP2_OBJALLOC_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_opl.c b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_opl.c new file mode 100644 index 0000000..a29361c --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_opl.c @@ -0,0 +1,46 @@ +/* + * ngtcp2 + * + * Copyright (c) 2022 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_opl.h" + +void ngtcp2_opl_init(ngtcp2_opl *opl) { opl->head = NULL; } + +void ngtcp2_opl_push(ngtcp2_opl *opl, ngtcp2_opl_entry *ent) { + ent->next = opl->head; + opl->head = ent; +} + +ngtcp2_opl_entry *ngtcp2_opl_pop(ngtcp2_opl *opl) { + ngtcp2_opl_entry *ent = opl->head; + + if (!ent) { + return NULL; + } + + opl->head = ent->next; + + return ent; +} + +void ngtcp2_opl_clear(ngtcp2_opl *opl) { opl->head = NULL; } diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_opl.h b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_opl.h new file mode 100644 index 0000000..714aa36 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_opl.h @@ -0,0 +1,65 @@ +/* + * ngtcp2 + * + * Copyright (c) 2022 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_OPL_H +#define NGTCP2_OPL_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +typedef struct ngtcp2_opl_entry ngtcp2_opl_entry; + +struct ngtcp2_opl_entry { + ngtcp2_opl_entry *next; +}; + +/* + * ngtcp2_opl is an object memory pool. + */ +typedef struct ngtcp2_opl { + ngtcp2_opl_entry *head; +} ngtcp2_opl; + +/* + * ngtcp2_opl_init initializes |opl|. + */ +void ngtcp2_opl_init(ngtcp2_opl *opl); + +/* + * ngtcp2_opl_push inserts |ent| to |opl| head. + */ +void ngtcp2_opl_push(ngtcp2_opl *opl, ngtcp2_opl_entry *ent); + +/* + * ngtcp2_opl_pop removes the first ngtcp2_opl_entry from |opl| and + * returns it. If |opl| does not have any entry, it returns NULL. + */ +ngtcp2_opl_entry *ngtcp2_opl_pop(ngtcp2_opl *opl); + +void ngtcp2_opl_clear(ngtcp2_opl *opl); + +#endif /* NGTCP2_OPL_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_path.c b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_path.c new file mode 100644 index 0000000..8323873 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_path.c @@ -0,0 +1,77 @@ +/* + * 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. + */ +#include "ngtcp2_path.h" + +#include <string.h> + +#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); + dest->user_data = src->user_data; +} + +int ngtcp2_path_eq(const ngtcp2_path *a, const ngtcp2_path *b) { + return ngtcp2_addr_eq(&a->local, &b->local) && + ngtcp2_addr_eq(&a->remote, &b->remote); +} + +void ngtcp2_path_storage_init(ngtcp2_path_storage *ps, + const ngtcp2_sockaddr *local_addr, + ngtcp2_socklen local_addrlen, + const ngtcp2_sockaddr *remote_addr, + ngtcp2_socklen remote_addrlen, void *user_data) { + ngtcp2_addr_init(&ps->path.local, (const ngtcp2_sockaddr *)&ps->local_addrbuf, + 0); + ngtcp2_addr_init(&ps->path.remote, + (const ngtcp2_sockaddr *)&ps->remote_addrbuf, 0); + + ngtcp2_addr_copy_byte(&ps->path.local, local_addr, local_addrlen); + ngtcp2_addr_copy_byte(&ps->path.remote, remote_addr, remote_addrlen); + + ps->path.user_data = user_data; +} + +void ngtcp2_path_storage_init2(ngtcp2_path_storage *ps, + const ngtcp2_path *path) { + ngtcp2_path_storage_init(ps, path->local.addr, path->local.addrlen, + path->remote.addr, path->remote.addrlen, + path->user_data); +} + +void ngtcp2_path_storage_zero(ngtcp2_path_storage *ps) { + ngtcp2_addr_init(&ps->path.local, (const ngtcp2_sockaddr *)&ps->local_addrbuf, + 0); + ngtcp2_addr_init(&ps->path.remote, + (const ngtcp2_sockaddr *)&ps->remote_addrbuf, 0); + ps->path.user_data = NULL; +} diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_path.h b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_path.h new file mode 100644 index 0000000..0c360e9 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_path.h @@ -0,0 +1,49 @@ +/* + * 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. + */ +#ifndef NGTCP2_PATH_H +#define NGTCP2_PATH_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +/* + * 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. + */ +void ngtcp2_path_storage_init2(ngtcp2_path_storage *ps, + const ngtcp2_path *path); + +#endif /* NGTCP2_PATH_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_pkt.c b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_pkt.c new file mode 100644 index 0000000..1687ff2 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_pkt.c @@ -0,0 +1,2532 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 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_pkt.h" + +#include <assert.h> +#include <string.h> + +#include "ngtcp2_conv.h" +#include "ngtcp2_str.h" +#include "ngtcp2_macro.h" +#include "ngtcp2_cid.h" +#include "ngtcp2_mem.h" +#include "ngtcp2_vec.h" +#include "ngtcp2_unreachable.h" + +int ngtcp2_pkt_chain_new(ngtcp2_pkt_chain **ppc, const ngtcp2_path *path, + const ngtcp2_pkt_info *pi, const uint8_t *pkt, + size_t pktlen, size_t dgramlen, ngtcp2_tstamp ts, + const ngtcp2_mem *mem) { + *ppc = ngtcp2_mem_malloc(mem, sizeof(ngtcp2_pkt_chain) + pktlen); + if (*ppc == NULL) { + return NGTCP2_ERR_NOMEM; + } + + ngtcp2_path_storage_init2(&(*ppc)->path, path); + (*ppc)->pi = *pi; + (*ppc)->next = NULL; + (*ppc)->pkt = (uint8_t *)(*ppc) + sizeof(ngtcp2_pkt_chain); + (*ppc)->pktlen = pktlen; + (*ppc)->dgramlen = dgramlen; + (*ppc)->ts = ts; + + memcpy((*ppc)->pkt, pkt, pktlen); + + return 0; +} + +void ngtcp2_pkt_chain_del(ngtcp2_pkt_chain *pc, const ngtcp2_mem *mem) { + ngtcp2_mem_free(mem, pc); +} + +int ngtcp2_pkt_decode_version_cid(ngtcp2_version_cid *dest, const uint8_t *data, + size_t datalen, size_t short_dcidlen) { + size_t len; + uint32_t version; + size_t dcidlen, scidlen; + int supported_version; + + assert(datalen); + + if (data[0] & NGTCP2_HEADER_FORM_BIT) { + /* 1 byte (Header Form, Fixed Bit, Long Packet Type, Type-Specific bits) + * 4 bytes Version + * 1 byte DCID Length + * 1 byte SCID Length + */ + len = 1 + 4 + 1 + 1; + if (datalen < len) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + dcidlen = data[5]; + len += dcidlen; + if (datalen < len) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + scidlen = data[5 + 1 + dcidlen]; + len += scidlen; + if (datalen < len) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + ngtcp2_get_uint32(&version, &data[1]); + + supported_version = ngtcp2_is_supported_version(version); + + if (supported_version && + (dcidlen > NGTCP2_MAX_CIDLEN || scidlen > NGTCP2_MAX_CIDLEN)) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + if (version && !supported_version && + datalen < NGTCP2_MAX_UDP_PAYLOAD_SIZE) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + dest->version = version; + dest->dcid = &data[6]; + dest->dcidlen = dcidlen; + dest->scid = &data[6 + dcidlen + 1]; + dest->scidlen = scidlen; + + if (!version) { + /* VN */ + return 0; + } + + if (!supported_version) { + return NGTCP2_ERR_VERSION_NEGOTIATION; + } + return 0; + } + + assert(short_dcidlen <= NGTCP2_MAX_CIDLEN); + + len = 1 + short_dcidlen; + if (datalen < len) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + dest->version = 0; + dest->dcid = &data[1]; + dest->dcidlen = short_dcidlen; + dest->scid = NULL; + dest->scidlen = 0; + + return 0; +} + +void ngtcp2_pkt_hd_init(ngtcp2_pkt_hd *hd, uint8_t flags, uint8_t type, + const ngtcp2_cid *dcid, const ngtcp2_cid *scid, + int64_t pkt_num, size_t pkt_numlen, uint32_t version, + size_t len) { + hd->flags = flags; + hd->type = type; + if (dcid) { + hd->dcid = *dcid; + } else { + ngtcp2_cid_zero(&hd->dcid); + } + if (scid) { + hd->scid = *scid; + } else { + ngtcp2_cid_zero(&hd->scid); + } + hd->pkt_num = pkt_num; + hd->token = NULL; + hd->tokenlen = 0; + hd->pkt_numlen = pkt_numlen; + hd->version = version; + hd->len = len; +} + +ngtcp2_ssize ngtcp2_pkt_decode_hd_long(ngtcp2_pkt_hd *dest, const uint8_t *pkt, + size_t pktlen) { + uint8_t type; + uint32_t version; + size_t dcil, scil; + const uint8_t *p; + size_t len = 0; + size_t n; + size_t ntokenlen = 0; + const uint8_t *token = NULL; + size_t tokenlen = 0; + uint64_t vi; + uint8_t flags = NGTCP2_PKT_FLAG_LONG_FORM; + + if (pktlen < 5) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + if (!(pkt[0] & NGTCP2_HEADER_FORM_BIT)) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + ngtcp2_get_uint32(&version, &pkt[1]); + + if (version == 0) { + type = NGTCP2_PKT_VERSION_NEGOTIATION; + /* Version Negotiation is not a long header packet. */ + flags = NGTCP2_PKT_FLAG_NONE; + /* This must be Version Negotiation packet which lacks packet + number and payload length fields. */ + len = 5 + 2; + } else { + if (!(pkt[0] & NGTCP2_FIXED_BIT_MASK)) { + flags |= NGTCP2_PKT_FLAG_FIXED_BIT_CLEAR; + } + + type = ngtcp2_pkt_get_type_long(version, pkt[0]); + switch (type) { + case 0: + return NGTCP2_ERR_INVALID_ARGUMENT; + case NGTCP2_PKT_INITIAL: + len = 1 /* Token Length */ + NGTCP2_MIN_LONG_HEADERLEN - + 1; /* Cut packet number field */ + break; + case NGTCP2_PKT_RETRY: + /* Retry packet does not have packet number and length fields */ + len = 5 + 2; + break; + case NGTCP2_PKT_HANDSHAKE: + case NGTCP2_PKT_0RTT: + len = NGTCP2_MIN_LONG_HEADERLEN - 1; /* Cut packet number field */ + break; + default: + /* Unreachable */ + ngtcp2_unreachable(); + } + } + + if (pktlen < len) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + p = &pkt[5]; + dcil = *p; + if (dcil > NGTCP2_MAX_CIDLEN) { + /* QUIC v1 implementation never expect to receive CID length more + than NGTCP2_MAX_CIDLEN. */ + return NGTCP2_ERR_INVALID_ARGUMENT; + } + len += dcil; + + if (pktlen < len) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + p += 1 + dcil; + scil = *p; + if (scil > NGTCP2_MAX_CIDLEN) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + len += scil; + + if (pktlen < len) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + p += 1 + scil; + + if (type == NGTCP2_PKT_INITIAL) { + /* Token Length */ + ntokenlen = ngtcp2_get_uvarintlen(p); + len += ntokenlen - 1; + + if (pktlen < len) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + p = ngtcp2_get_uvarint(&vi, p); + if (pktlen - len < vi) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + tokenlen = (size_t)vi; + len += tokenlen; + + if (tokenlen) { + token = p; + } + + p += tokenlen; + } + + switch (type) { + case NGTCP2_PKT_RETRY: + break; + default: + if (!(flags & NGTCP2_PKT_FLAG_LONG_FORM)) { + assert(type == NGTCP2_PKT_VERSION_NEGOTIATION); + /* Version Negotiation is not a long header packet. */ + break; + } + + /* Length */ + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + + if (pktlen < len) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + } + + dest->flags = flags; + dest->type = type; + dest->version = version; + dest->pkt_num = 0; + dest->pkt_numlen = 0; + + p = &pkt[6]; + ngtcp2_cid_init(&dest->dcid, p, dcil); + p += dcil + 1; + ngtcp2_cid_init(&dest->scid, p, scil); + p += scil; + + dest->token = token; + dest->tokenlen = tokenlen; + p += ntokenlen + tokenlen; + + switch (type) { + case NGTCP2_PKT_RETRY: + dest->len = 0; + break; + default: + if (!(flags & NGTCP2_PKT_FLAG_LONG_FORM)) { + assert(type == NGTCP2_PKT_VERSION_NEGOTIATION); + /* Version Negotiation is not a long header packet. */ + dest->len = 0; + break; + } + + p = ngtcp2_get_uvarint(&vi, p); + if (vi > SIZE_MAX) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + dest->len = (size_t)vi; + } + + assert((size_t)(p - pkt) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_decode_hd_short(ngtcp2_pkt_hd *dest, const uint8_t *pkt, + size_t pktlen, size_t dcidlen) { + size_t len = 1 + dcidlen; + const uint8_t *p = pkt; + uint8_t flags = NGTCP2_PKT_FLAG_NONE; + + assert(dcidlen <= NGTCP2_MAX_CIDLEN); + + if (pktlen < len) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + if (pkt[0] & NGTCP2_HEADER_FORM_BIT) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + if (!(pkt[0] & NGTCP2_FIXED_BIT_MASK)) { + flags |= NGTCP2_PKT_FLAG_FIXED_BIT_CLEAR; + } + + p = &pkt[1]; + + dest->type = NGTCP2_PKT_1RTT; + + ngtcp2_cid_init(&dest->dcid, p, dcidlen); + p += dcidlen; + + /* Set 0 to SCID so that we don't accidentally reference it and gets + garbage. */ + ngtcp2_cid_zero(&dest->scid); + + dest->flags = flags; + dest->version = 0; + dest->len = 0; + dest->pkt_num = 0; + dest->pkt_numlen = 0; + dest->token = NULL; + dest->tokenlen = 0; + + assert((size_t)(p - pkt) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_encode_hd_long(uint8_t *out, size_t outlen, + const ngtcp2_pkt_hd *hd) { + uint8_t *p; + size_t len = NGTCP2_MIN_LONG_HEADERLEN + hd->dcid.datalen + hd->scid.datalen - + 2; /* NGTCP2_MIN_LONG_HEADERLEN includes 1 byte for + len and 1 byte for packet number. */ + + if (hd->type != NGTCP2_PKT_RETRY) { + len += NGTCP2_PKT_LENGTHLEN /* Length */ + hd->pkt_numlen; + } + + if (hd->type == NGTCP2_PKT_INITIAL) { + len += ngtcp2_put_uvarintlen(hd->tokenlen) + hd->tokenlen; + } + + if (outlen < len) { + return NGTCP2_ERR_NOBUF; + } + + p = out; + + *p = (uint8_t)(NGTCP2_HEADER_FORM_BIT | + (ngtcp2_pkt_versioned_type(hd->version, hd->type) << 4) | + (uint8_t)(hd->pkt_numlen - 1)); + if (!(hd->flags & NGTCP2_PKT_FLAG_FIXED_BIT_CLEAR)) { + *p |= NGTCP2_FIXED_BIT_MASK; + } + + ++p; + + p = ngtcp2_put_uint32be(p, hd->version); + *p++ = (uint8_t)hd->dcid.datalen; + if (hd->dcid.datalen) { + p = ngtcp2_cpymem(p, hd->dcid.data, hd->dcid.datalen); + } + *p++ = (uint8_t)hd->scid.datalen; + if (hd->scid.datalen) { + p = ngtcp2_cpymem(p, hd->scid.data, hd->scid.datalen); + } + + if (hd->type == NGTCP2_PKT_INITIAL) { + p = ngtcp2_put_uvarint(p, hd->tokenlen); + if (hd->tokenlen) { + p = ngtcp2_cpymem(p, hd->token, hd->tokenlen); + } + } + + if (hd->type != NGTCP2_PKT_RETRY) { + p = ngtcp2_put_uvarint30(p, (uint32_t)hd->len); + p = ngtcp2_put_pkt_num(p, hd->pkt_num, hd->pkt_numlen); + } + + assert((size_t)(p - out) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_encode_hd_short(uint8_t *out, size_t outlen, + const ngtcp2_pkt_hd *hd) { + uint8_t *p; + size_t len = 1 + hd->dcid.datalen + hd->pkt_numlen; + + if (outlen < len) { + return NGTCP2_ERR_NOBUF; + } + + p = out; + + *p = (uint8_t)(hd->pkt_numlen - 1); + if (!(hd->flags & NGTCP2_PKT_FLAG_FIXED_BIT_CLEAR)) { + *p |= NGTCP2_FIXED_BIT_MASK; + } + if (hd->flags & NGTCP2_PKT_FLAG_KEY_PHASE) { + *p |= NGTCP2_SHORT_KEY_PHASE_BIT; + } + + ++p; + + if (hd->dcid.datalen) { + p = ngtcp2_cpymem(p, hd->dcid.data, hd->dcid.datalen); + } + + p = ngtcp2_put_pkt_num(p, hd->pkt_num, hd->pkt_numlen); + + assert((size_t)(p - out) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_decode_frame(ngtcp2_frame *dest, const uint8_t *payload, + size_t payloadlen) { + uint8_t type; + + if (payloadlen == 0) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + type = payload[0]; + + switch (type) { + case NGTCP2_FRAME_PADDING: + return ngtcp2_pkt_decode_padding_frame(&dest->padding, payload, payloadlen); + case NGTCP2_FRAME_RESET_STREAM: + return ngtcp2_pkt_decode_reset_stream_frame(&dest->reset_stream, payload, + payloadlen); + case NGTCP2_FRAME_CONNECTION_CLOSE: + case NGTCP2_FRAME_CONNECTION_CLOSE_APP: + return ngtcp2_pkt_decode_connection_close_frame(&dest->connection_close, + payload, payloadlen); + case NGTCP2_FRAME_MAX_DATA: + return ngtcp2_pkt_decode_max_data_frame(&dest->max_data, payload, + payloadlen); + case NGTCP2_FRAME_MAX_STREAM_DATA: + return ngtcp2_pkt_decode_max_stream_data_frame(&dest->max_stream_data, + payload, payloadlen); + case NGTCP2_FRAME_MAX_STREAMS_BIDI: + case NGTCP2_FRAME_MAX_STREAMS_UNI: + return ngtcp2_pkt_decode_max_streams_frame(&dest->max_streams, payload, + payloadlen); + case NGTCP2_FRAME_PING: + return ngtcp2_pkt_decode_ping_frame(&dest->ping, payload, payloadlen); + case NGTCP2_FRAME_DATA_BLOCKED: + return ngtcp2_pkt_decode_data_blocked_frame(&dest->data_blocked, payload, + payloadlen); + case NGTCP2_FRAME_STREAM_DATA_BLOCKED: + return ngtcp2_pkt_decode_stream_data_blocked_frame( + &dest->stream_data_blocked, payload, payloadlen); + case NGTCP2_FRAME_STREAMS_BLOCKED_BIDI: + case NGTCP2_FRAME_STREAMS_BLOCKED_UNI: + return ngtcp2_pkt_decode_streams_blocked_frame(&dest->streams_blocked, + payload, payloadlen); + case NGTCP2_FRAME_NEW_CONNECTION_ID: + return ngtcp2_pkt_decode_new_connection_id_frame(&dest->new_connection_id, + payload, payloadlen); + case NGTCP2_FRAME_STOP_SENDING: + return ngtcp2_pkt_decode_stop_sending_frame(&dest->stop_sending, payload, + payloadlen); + case NGTCP2_FRAME_ACK: + case NGTCP2_FRAME_ACK_ECN: + 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, + payload, payloadlen); + case NGTCP2_FRAME_PATH_RESPONSE: + return ngtcp2_pkt_decode_path_response_frame(&dest->path_response, payload, + payloadlen); + case NGTCP2_FRAME_CRYPTO: + 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, + payloadlen); + case NGTCP2_FRAME_RETIRE_CONNECTION_ID: + return ngtcp2_pkt_decode_retire_connection_id_frame( + &dest->retire_connection_id, payload, payloadlen); + case NGTCP2_FRAME_HANDSHAKE_DONE: + return ngtcp2_pkt_decode_handshake_done_frame(&dest->handshake_done, + payload, payloadlen); + case NGTCP2_FRAME_DATAGRAM: + case NGTCP2_FRAME_DATAGRAM_LEN: + return ngtcp2_pkt_decode_datagram_frame(&dest->datagram, payload, + payloadlen); + default: + if ((type & ~(NGTCP2_FRAME_STREAM - 1)) == NGTCP2_FRAME_STREAM) { + return ngtcp2_pkt_decode_stream_frame(&dest->stream, payload, payloadlen); + } + + /* 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). */ + + return NGTCP2_ERR_FRAME_ENCODING; + } +} + +ngtcp2_ssize ngtcp2_pkt_decode_stream_frame(ngtcp2_stream *dest, + const uint8_t *payload, + size_t payloadlen) { + uint8_t type; + size_t len = 1 + 1; + const uint8_t *p; + size_t datalen = 0; + size_t ndatalen = 0; + size_t n; + uint64_t vi; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + type = payload[0]; + + p = payload + 1; + + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p += n; + + if (type & NGTCP2_STREAM_OFF_BIT) { + ++len; + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p += n; + } + + if (type & NGTCP2_STREAM_LEN_BIT) { + ++len; + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + ndatalen = ngtcp2_get_uvarintlen(p); + len += ndatalen - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + /* p = */ ngtcp2_get_uvarint(&vi, p); + if (payloadlen - len < vi) { + return NGTCP2_ERR_FRAME_ENCODING; + } + datalen = (size_t)vi; + len += datalen; + } else { + len = payloadlen; + } + + p = payload + 1; + + dest->type = NGTCP2_FRAME_STREAM; + dest->flags = (uint8_t)(type & ~NGTCP2_FRAME_STREAM); + dest->fin = (type & NGTCP2_STREAM_FIN_BIT) != 0; + p = ngtcp2_get_varint(&dest->stream_id, p); + + if (type & NGTCP2_STREAM_OFF_BIT) { + p = ngtcp2_get_uvarint(&dest->offset, p); + } else { + dest->offset = 0; + } + + if (type & NGTCP2_STREAM_LEN_BIT) { + p += ndatalen; + } else { + datalen = payloadlen - (size_t)(p - payload); + } + + if (datalen) { + dest->data[0].len = datalen; + dest->data[0].base = (uint8_t *)p; + dest->datacnt = 1; + p += datalen; + } else { + dest->datacnt = 0; + } + + assert((size_t)(p - payload) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_decode_ack_frame(ngtcp2_ack *dest, + const uint8_t *payload, + size_t payloadlen) { + size_t rangecnt, max_rangecnt; + size_t nrangecnt; + size_t len = 1 + 1 + 1 + 1 + 1; + const uint8_t *p; + size_t i, j; + ngtcp2_ack_range *range; + size_t n; + uint8_t type; + uint64_t vi; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + type = payload[0]; + + p = payload + 1; + + /* Largest Acknowledged */ + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p += n; + + /* ACK Delay */ + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p += n; + + /* ACK Range Count */ + nrangecnt = ngtcp2_get_uvarintlen(p); + len += nrangecnt - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = ngtcp2_get_uvarint(&vi, p); + if (vi > SIZE_MAX / (1 + 1) || payloadlen - len < vi * (1 + 1)) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + rangecnt = (size_t)vi; + len += rangecnt * (1 + 1); + + /* First ACK Range */ + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p += n; + + for (i = 0; i < rangecnt; ++i) { + /* Gap, and Additional ACK Range */ + for (j = 0; j < 2; ++j) { + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p += n; + } + } + + if (type == NGTCP2_FRAME_ACK_ECN) { + len += 3; + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + for (i = 0; i < 3; ++i) { + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p += n; + } + } + + /* TODO We might not decode all ranges. It could be very large. */ + max_rangecnt = ngtcp2_min(NGTCP2_MAX_ACK_RANGES, rangecnt); + + p = payload + 1; + + dest->type = type; + p = ngtcp2_get_varint(&dest->largest_ack, p); + p = ngtcp2_get_uvarint(&dest->ack_delay, p); + /* This value will be assigned in the upper layer. */ + dest->ack_delay_unscaled = 0; + dest->rangecnt = max_rangecnt; + p += nrangecnt; + p = ngtcp2_get_uvarint(&dest->first_ack_range, p); + + for (i = 0; i < max_rangecnt; ++i) { + range = &dest->ranges[i]; + p = ngtcp2_get_uvarint(&range->gap, p); + p = ngtcp2_get_uvarint(&range->len, p); + } + for (i = max_rangecnt; i < rangecnt; ++i) { + p += ngtcp2_get_uvarintlen(p); + p += ngtcp2_get_uvarintlen(p); + } + + if (type == NGTCP2_FRAME_ACK_ECN) { + p = ngtcp2_get_uvarint(&dest->ecn.ect0, p); + p = ngtcp2_get_uvarint(&dest->ecn.ect1, p); + p = ngtcp2_get_uvarint(&dest->ecn.ce, p); + } + + assert((size_t)(p - payload) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_decode_padding_frame(ngtcp2_padding *dest, + const uint8_t *payload, + size_t payloadlen) { + const uint8_t *p, *ep; + + assert(payloadlen > 0); + + p = payload + 1; + ep = payload + payloadlen; + + for (; p != ep && *p == NGTCP2_FRAME_PADDING; ++p) + ; + + dest->type = NGTCP2_FRAME_PADDING; + dest->len = (size_t)(p - payload); + + return (ngtcp2_ssize)dest->len; +} + +ngtcp2_ssize ngtcp2_pkt_decode_reset_stream_frame(ngtcp2_reset_stream *dest, + const uint8_t *payload, + size_t payloadlen) { + size_t len = 1 + 1 + 1 + 1; + const uint8_t *p; + size_t n; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = payload + 1; + + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + p += n; + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + p += n; + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = payload + 1; + + dest->type = NGTCP2_FRAME_RESET_STREAM; + p = ngtcp2_get_varint(&dest->stream_id, p); + p = ngtcp2_get_uvarint(&dest->app_error_code, p); + p = ngtcp2_get_uvarint(&dest->final_size, p); + + assert((size_t)(p - payload) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_decode_connection_close_frame( + ngtcp2_connection_close *dest, const uint8_t *payload, size_t payloadlen) { + size_t len = 1 + 1 + 1; + const uint8_t *p; + size_t reasonlen; + size_t nreasonlen; + size_t n; + uint8_t type; + uint64_t vi; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + type = payload[0]; + + p = payload + 1; + + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p += n; + + if (type == NGTCP2_FRAME_CONNECTION_CLOSE) { + ++len; + + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p += n; + } + + nreasonlen = ngtcp2_get_uvarintlen(p); + len += nreasonlen - 1; + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = ngtcp2_get_uvarint(&vi, p); + if (payloadlen - len < vi) { + return NGTCP2_ERR_FRAME_ENCODING; + } + reasonlen = (size_t)vi; + len += reasonlen; + + p = payload + 1; + + dest->type = type; + p = ngtcp2_get_uvarint(&dest->error_code, p); + if (type == NGTCP2_FRAME_CONNECTION_CLOSE) { + p = ngtcp2_get_uvarint(&dest->frame_type, p); + } else { + dest->frame_type = 0; + } + dest->reasonlen = reasonlen; + p += nreasonlen; + if (reasonlen == 0) { + dest->reason = NULL; + } else { + dest->reason = (uint8_t *)p; + p += reasonlen; + } + + assert((size_t)(p - payload) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_decode_max_data_frame(ngtcp2_max_data *dest, + const uint8_t *payload, + size_t payloadlen) { + size_t len = 1 + 1; + const uint8_t *p; + size_t n; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = payload + 1; + + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + dest->type = NGTCP2_FRAME_MAX_DATA; + p = ngtcp2_get_uvarint(&dest->max_data, p); + + assert((size_t)(p - payload) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_decode_max_stream_data_frame( + ngtcp2_max_stream_data *dest, const uint8_t *payload, size_t payloadlen) { + size_t len = 1 + 1 + 1; + const uint8_t *p; + size_t n; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = payload + 1; + + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p += n; + + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = payload + 1; + + dest->type = NGTCP2_FRAME_MAX_STREAM_DATA; + p = ngtcp2_get_varint(&dest->stream_id, p); + p = ngtcp2_get_uvarint(&dest->max_stream_data, p); + + assert((size_t)(p - payload) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_decode_max_streams_frame(ngtcp2_max_streams *dest, + const uint8_t *payload, + size_t payloadlen) { + size_t len = 1 + 1; + const uint8_t *p; + size_t n; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = payload + 1; + + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + dest->type = payload[0]; + p = ngtcp2_get_uvarint(&dest->max_streams, p); + + assert((size_t)(p - payload) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_decode_ping_frame(ngtcp2_ping *dest, + const uint8_t *payload, + size_t payloadlen) { + (void)payload; + (void)payloadlen; + + dest->type = NGTCP2_FRAME_PING; + return 1; +} + +ngtcp2_ssize ngtcp2_pkt_decode_data_blocked_frame(ngtcp2_data_blocked *dest, + const uint8_t *payload, + size_t payloadlen) { + size_t len = 1 + 1; + const uint8_t *p; + size_t n; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = payload + 1; + + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + dest->type = NGTCP2_FRAME_DATA_BLOCKED; + p = ngtcp2_get_uvarint(&dest->offset, p); + + assert((size_t)(p - payload) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize +ngtcp2_pkt_decode_stream_data_blocked_frame(ngtcp2_stream_data_blocked *dest, + const uint8_t *payload, + size_t payloadlen) { + size_t len = 1 + 1 + 1; + const uint8_t *p; + size_t n; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = payload + 1; + + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p += n; + + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = payload + 1; + + dest->type = NGTCP2_FRAME_STREAM_DATA_BLOCKED; + p = ngtcp2_get_varint(&dest->stream_id, p); + p = ngtcp2_get_uvarint(&dest->offset, p); + + assert((size_t)(p - payload) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_decode_streams_blocked_frame( + ngtcp2_streams_blocked *dest, const uint8_t *payload, size_t payloadlen) { + size_t len = 1 + 1; + const uint8_t *p; + size_t n; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = payload + 1; + + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + dest->type = payload[0]; + p = ngtcp2_get_uvarint(&dest->max_streams, p); + + assert((size_t)(p - payload) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_decode_new_connection_id_frame( + ngtcp2_new_connection_id *dest, const uint8_t *payload, size_t payloadlen) { + size_t len = 1 + 1 + 1 + 1 + 16; + const uint8_t *p; + size_t n; + size_t cil; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = payload + 1; + + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p += n; + + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p += n; + + cil = *p; + if (cil < NGTCP2_MIN_CIDLEN || cil > NGTCP2_MAX_CIDLEN) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + len += cil; + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = payload + 1; + + dest->type = NGTCP2_FRAME_NEW_CONNECTION_ID; + p = ngtcp2_get_uvarint(&dest->seq, p); + p = ngtcp2_get_uvarint(&dest->retire_prior_to, p); + ++p; + ngtcp2_cid_init(&dest->cid, p, cil); + p += cil; + p = ngtcp2_get_bytes(dest->stateless_reset_token, p, + NGTCP2_STATELESS_RESET_TOKENLEN); + + assert((size_t)(p - payload) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_decode_stop_sending_frame(ngtcp2_stop_sending *dest, + const uint8_t *payload, + size_t payloadlen) { + size_t len = 1 + 1 + 1; + const uint8_t *p; + size_t n; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = payload + 1; + + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + p += n; + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = payload + 1; + + dest->type = NGTCP2_FRAME_STOP_SENDING; + p = ngtcp2_get_varint(&dest->stream_id, p); + p = ngtcp2_get_uvarint(&dest->app_error_code, p); + + assert((size_t)(p - payload) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_decode_path_challenge_frame(ngtcp2_path_challenge *dest, + const uint8_t *payload, + size_t payloadlen) { + size_t len = 1 + 8; + const uint8_t *p; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = payload + 1; + + dest->type = NGTCP2_FRAME_PATH_CHALLENGE; + ngtcp2_cpymem(dest->data, p, sizeof(dest->data)); + p += sizeof(dest->data); + + assert((size_t)(p - payload) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_decode_path_response_frame(ngtcp2_path_response *dest, + const uint8_t *payload, + size_t payloadlen) { + size_t len = 1 + 8; + const uint8_t *p; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = payload + 1; + + dest->type = NGTCP2_FRAME_PATH_RESPONSE; + ngtcp2_cpymem(dest->data, p, sizeof(dest->data)); + p += sizeof(dest->data); + + assert((size_t)(p - payload) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_decode_crypto_frame(ngtcp2_stream *dest, + const uint8_t *payload, + size_t payloadlen) { + size_t len = 1 + 1 + 1; + const uint8_t *p; + size_t datalen; + size_t ndatalen; + size_t n; + uint64_t vi; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = payload + 1; + + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p += n; + + ndatalen = ngtcp2_get_uvarintlen(p); + len += ndatalen - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = ngtcp2_get_uvarint(&vi, p); + if (payloadlen - len < vi) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + datalen = (size_t)vi; + len += datalen; + + p = payload + 1; + + dest->type = NGTCP2_FRAME_CRYPTO; + dest->flags = 0; + dest->fin = 0; + dest->stream_id = 0; + p = ngtcp2_get_uvarint(&dest->offset, p); + dest->data[0].len = datalen; + p += ndatalen; + if (dest->data[0].len) { + dest->data[0].base = (uint8_t *)p; + p += dest->data[0].len; + dest->datacnt = 1; + } else { + dest->data[0].base = NULL; + dest->datacnt = 0; + } + + assert((size_t)(p - payload) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_decode_new_token_frame(ngtcp2_new_token *dest, + const uint8_t *payload, + size_t payloadlen) { + size_t len = 1 + 1; + const uint8_t *p; + size_t n; + size_t datalen; + uint64_t vi; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = payload + 1; + + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = ngtcp2_get_uvarint(&vi, p); + if (payloadlen - len < vi) { + return NGTCP2_ERR_FRAME_ENCODING; + } + datalen = (size_t)vi; + len += datalen; + + dest->type = NGTCP2_FRAME_NEW_TOKEN; + dest->tokenlen = datalen; + dest->token = (uint8_t *)p; + p += dest->tokenlen; + + assert((size_t)(p - payload) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize +ngtcp2_pkt_decode_retire_connection_id_frame(ngtcp2_retire_connection_id *dest, + const uint8_t *payload, + size_t payloadlen) { + size_t len = 1 + 1; + const uint8_t *p; + size_t n; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = payload + 1; + + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + dest->type = NGTCP2_FRAME_RETIRE_CONNECTION_ID; + p = ngtcp2_get_uvarint(&dest->seq, p); + + assert((size_t)(p - payload) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_decode_handshake_done_frame(ngtcp2_handshake_done *dest, + const uint8_t *payload, + size_t payloadlen) { + (void)payload; + (void)payloadlen; + + dest->type = NGTCP2_FRAME_HANDSHAKE_DONE; + return 1; +} + +ngtcp2_ssize ngtcp2_pkt_decode_datagram_frame(ngtcp2_datagram *dest, + const uint8_t *payload, + size_t payloadlen) { + size_t len = 1; + const uint8_t *p; + uint8_t type; + size_t datalen; + size_t n; + uint64_t vi; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + type = payload[0]; + + p = payload + 1; + + switch (type) { + case NGTCP2_FRAME_DATAGRAM: + datalen = payloadlen - 1; + len = payloadlen; + break; + case NGTCP2_FRAME_DATAGRAM_LEN: + ++len; + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = ngtcp2_get_uvarint(&vi, p); + if (payloadlen - len < vi) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + datalen = (size_t)vi; + len += datalen; + break; + default: + ngtcp2_unreachable(); + } + + dest->type = type; + + if (datalen == 0) { + dest->datacnt = 0; + dest->data = NULL; + } else { + dest->datacnt = 1; + dest->data = dest->rdata; + dest->rdata[0].len = datalen; + + dest->rdata[0].base = (uint8_t *)p; + p += datalen; + } + + assert((size_t)(p - payload) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_encode_frame(uint8_t *out, size_t outlen, + ngtcp2_frame *fr) { + switch (fr->type) { + case NGTCP2_FRAME_STREAM: + return ngtcp2_pkt_encode_stream_frame(out, outlen, &fr->stream); + case NGTCP2_FRAME_ACK: + case NGTCP2_FRAME_ACK_ECN: + return ngtcp2_pkt_encode_ack_frame(out, outlen, &fr->ack); + case NGTCP2_FRAME_PADDING: + return ngtcp2_pkt_encode_padding_frame(out, outlen, &fr->padding); + case NGTCP2_FRAME_RESET_STREAM: + return ngtcp2_pkt_encode_reset_stream_frame(out, outlen, &fr->reset_stream); + case NGTCP2_FRAME_CONNECTION_CLOSE: + case NGTCP2_FRAME_CONNECTION_CLOSE_APP: + return ngtcp2_pkt_encode_connection_close_frame(out, outlen, + &fr->connection_close); + case NGTCP2_FRAME_MAX_DATA: + return ngtcp2_pkt_encode_max_data_frame(out, outlen, &fr->max_data); + case NGTCP2_FRAME_MAX_STREAM_DATA: + return ngtcp2_pkt_encode_max_stream_data_frame(out, outlen, + &fr->max_stream_data); + case NGTCP2_FRAME_MAX_STREAMS_BIDI: + case NGTCP2_FRAME_MAX_STREAMS_UNI: + return ngtcp2_pkt_encode_max_streams_frame(out, outlen, &fr->max_streams); + case NGTCP2_FRAME_PING: + return ngtcp2_pkt_encode_ping_frame(out, outlen, &fr->ping); + case NGTCP2_FRAME_DATA_BLOCKED: + return ngtcp2_pkt_encode_data_blocked_frame(out, outlen, &fr->data_blocked); + case NGTCP2_FRAME_STREAM_DATA_BLOCKED: + return ngtcp2_pkt_encode_stream_data_blocked_frame( + out, outlen, &fr->stream_data_blocked); + case NGTCP2_FRAME_STREAMS_BLOCKED_BIDI: + case NGTCP2_FRAME_STREAMS_BLOCKED_UNI: + return ngtcp2_pkt_encode_streams_blocked_frame(out, outlen, + &fr->streams_blocked); + case NGTCP2_FRAME_NEW_CONNECTION_ID: + return ngtcp2_pkt_encode_new_connection_id_frame(out, outlen, + &fr->new_connection_id); + case NGTCP2_FRAME_STOP_SENDING: + return ngtcp2_pkt_encode_stop_sending_frame(out, outlen, &fr->stop_sending); + case NGTCP2_FRAME_PATH_CHALLENGE: + return ngtcp2_pkt_encode_path_challenge_frame(out, outlen, + &fr->path_challenge); + case NGTCP2_FRAME_PATH_RESPONSE: + return ngtcp2_pkt_encode_path_response_frame(out, outlen, + &fr->path_response); + case NGTCP2_FRAME_CRYPTO: + return ngtcp2_pkt_encode_crypto_frame(out, outlen, &fr->stream); + case NGTCP2_FRAME_NEW_TOKEN: + return ngtcp2_pkt_encode_new_token_frame(out, outlen, &fr->new_token); + case NGTCP2_FRAME_RETIRE_CONNECTION_ID: + return ngtcp2_pkt_encode_retire_connection_id_frame( + out, outlen, &fr->retire_connection_id); + case NGTCP2_FRAME_HANDSHAKE_DONE: + return ngtcp2_pkt_encode_handshake_done_frame(out, outlen, + &fr->handshake_done); + case NGTCP2_FRAME_DATAGRAM: + case NGTCP2_FRAME_DATAGRAM_LEN: + return ngtcp2_pkt_encode_datagram_frame(out, outlen, &fr->datagram); + default: + return NGTCP2_ERR_INVALID_ARGUMENT; + } +} + +ngtcp2_ssize ngtcp2_pkt_encode_stream_frame(uint8_t *out, size_t outlen, + ngtcp2_stream *fr) { + size_t len = 1; + uint8_t flags = NGTCP2_STREAM_LEN_BIT; + uint8_t *p; + size_t i; + size_t datalen = 0; + + if (fr->fin) { + flags |= NGTCP2_STREAM_FIN_BIT; + } + + if (fr->offset) { + flags |= NGTCP2_STREAM_OFF_BIT; + len += ngtcp2_put_uvarintlen(fr->offset); + } + + len += ngtcp2_put_uvarintlen((uint64_t)fr->stream_id); + + for (i = 0; i < fr->datacnt; ++i) { + datalen += fr->data[i].len; + } + + len += ngtcp2_put_uvarintlen(datalen); + len += datalen; + + if (outlen < len) { + return NGTCP2_ERR_NOBUF; + } + + p = out; + + *p++ = flags | NGTCP2_FRAME_STREAM; + + fr->flags = flags; + + p = ngtcp2_put_uvarint(p, (uint64_t)fr->stream_id); + + if (fr->offset) { + p = ngtcp2_put_uvarint(p, fr->offset); + } + + p = ngtcp2_put_uvarint(p, datalen); + + for (i = 0; i < fr->datacnt; ++i) { + assert(fr->data[i].len); + assert(fr->data[i].base); + p = ngtcp2_cpymem(p, fr->data[i].base, fr->data[i].len); + } + + assert((size_t)(p - out) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_encode_ack_frame(uint8_t *out, size_t outlen, + ngtcp2_ack *fr) { + size_t len = 1 + ngtcp2_put_uvarintlen((uint64_t)fr->largest_ack) + + ngtcp2_put_uvarintlen(fr->ack_delay) + + ngtcp2_put_uvarintlen(fr->rangecnt) + + ngtcp2_put_uvarintlen(fr->first_ack_range); + uint8_t *p; + size_t i; + const ngtcp2_ack_range *range; + + for (i = 0; i < fr->rangecnt; ++i) { + range = &fr->ranges[i]; + len += ngtcp2_put_uvarintlen(range->gap); + len += ngtcp2_put_uvarintlen(range->len); + } + + if (fr->type == NGTCP2_FRAME_ACK_ECN) { + len += ngtcp2_put_uvarintlen(fr->ecn.ect0) + + ngtcp2_put_uvarintlen(fr->ecn.ect1) + + ngtcp2_put_uvarintlen(fr->ecn.ce); + } + + if (outlen < len) { + return NGTCP2_ERR_NOBUF; + } + + p = out; + + *p++ = (uint8_t)fr->type; + p = ngtcp2_put_uvarint(p, (uint64_t)fr->largest_ack); + p = ngtcp2_put_uvarint(p, fr->ack_delay); + p = ngtcp2_put_uvarint(p, fr->rangecnt); + p = ngtcp2_put_uvarint(p, fr->first_ack_range); + + for (i = 0; i < fr->rangecnt; ++i) { + range = &fr->ranges[i]; + p = ngtcp2_put_uvarint(p, range->gap); + p = ngtcp2_put_uvarint(p, range->len); + } + + if (fr->type == NGTCP2_FRAME_ACK_ECN) { + p = ngtcp2_put_uvarint(p, fr->ecn.ect0); + p = ngtcp2_put_uvarint(p, fr->ecn.ect1); + p = ngtcp2_put_uvarint(p, fr->ecn.ce); + } + + assert((size_t)(p - out) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_encode_padding_frame(uint8_t *out, size_t outlen, + const ngtcp2_padding *fr) { + if (outlen < fr->len) { + return NGTCP2_ERR_NOBUF; + } + + memset(out, 0, fr->len); + + return (ngtcp2_ssize)fr->len; +} + +ngtcp2_ssize +ngtcp2_pkt_encode_reset_stream_frame(uint8_t *out, size_t outlen, + const ngtcp2_reset_stream *fr) { + size_t len = 1 + ngtcp2_put_uvarintlen((uint64_t)fr->stream_id) + + ngtcp2_put_uvarintlen(fr->app_error_code) + + ngtcp2_put_uvarintlen(fr->final_size); + uint8_t *p; + + if (outlen < len) { + return NGTCP2_ERR_NOBUF; + } + + p = out; + + *p++ = NGTCP2_FRAME_RESET_STREAM; + p = ngtcp2_put_uvarint(p, (uint64_t)fr->stream_id); + p = ngtcp2_put_uvarint(p, fr->app_error_code); + p = ngtcp2_put_uvarint(p, fr->final_size); + + assert((size_t)(p - out) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize +ngtcp2_pkt_encode_connection_close_frame(uint8_t *out, size_t outlen, + const ngtcp2_connection_close *fr) { + size_t len = 1 + ngtcp2_put_uvarintlen(fr->error_code) + + (fr->type == NGTCP2_FRAME_CONNECTION_CLOSE + ? ngtcp2_put_uvarintlen(fr->frame_type) + : 0) + + ngtcp2_put_uvarintlen(fr->reasonlen) + fr->reasonlen; + uint8_t *p; + + if (outlen < len) { + return NGTCP2_ERR_NOBUF; + } + + p = out; + + *p++ = (uint8_t)fr->type; + p = ngtcp2_put_uvarint(p, fr->error_code); + if (fr->type == NGTCP2_FRAME_CONNECTION_CLOSE) { + p = ngtcp2_put_uvarint(p, fr->frame_type); + } + p = ngtcp2_put_uvarint(p, fr->reasonlen); + if (fr->reasonlen) { + p = ngtcp2_cpymem(p, fr->reason, fr->reasonlen); + } + + assert((size_t)(p - out) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_encode_max_data_frame(uint8_t *out, size_t outlen, + const ngtcp2_max_data *fr) { + size_t len = 1 + ngtcp2_put_uvarintlen(fr->max_data); + uint8_t *p; + + if (outlen < len) { + return NGTCP2_ERR_NOBUF; + } + + p = out; + + *p++ = NGTCP2_FRAME_MAX_DATA; + p = ngtcp2_put_uvarint(p, fr->max_data); + + assert((size_t)(p - out) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize +ngtcp2_pkt_encode_max_stream_data_frame(uint8_t *out, size_t outlen, + const ngtcp2_max_stream_data *fr) { + size_t len = 1 + ngtcp2_put_uvarintlen((uint64_t)fr->stream_id) + + ngtcp2_put_uvarintlen(fr->max_stream_data); + uint8_t *p; + + if (outlen < len) { + return NGTCP2_ERR_NOBUF; + } + + p = out; + + *p++ = NGTCP2_FRAME_MAX_STREAM_DATA; + p = ngtcp2_put_uvarint(p, (uint64_t)fr->stream_id); + p = ngtcp2_put_uvarint(p, fr->max_stream_data); + + assert((size_t)(p - out) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_encode_max_streams_frame(uint8_t *out, size_t outlen, + const ngtcp2_max_streams *fr) { + size_t len = 1 + ngtcp2_put_uvarintlen(fr->max_streams); + uint8_t *p; + + if (outlen < len) { + return NGTCP2_ERR_NOBUF; + } + + p = out; + + *p++ = (uint8_t)fr->type; + p = ngtcp2_put_uvarint(p, fr->max_streams); + + assert((size_t)(p - out) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_encode_ping_frame(uint8_t *out, size_t outlen, + const ngtcp2_ping *fr) { + (void)fr; + + if (outlen < 1) { + return NGTCP2_ERR_NOBUF; + } + + *out++ = NGTCP2_FRAME_PING; + + return 1; +} + +ngtcp2_ssize +ngtcp2_pkt_encode_data_blocked_frame(uint8_t *out, size_t outlen, + const ngtcp2_data_blocked *fr) { + size_t len = 1 + ngtcp2_put_uvarintlen(fr->offset); + uint8_t *p; + + if (outlen < len) { + return NGTCP2_ERR_NOBUF; + } + + p = out; + + *p++ = NGTCP2_FRAME_DATA_BLOCKED; + p = ngtcp2_put_uvarint(p, fr->offset); + + assert((size_t)(p - out) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_encode_stream_data_blocked_frame( + uint8_t *out, size_t outlen, const ngtcp2_stream_data_blocked *fr) { + size_t len = 1 + ngtcp2_put_uvarintlen((uint64_t)fr->stream_id) + + ngtcp2_put_uvarintlen(fr->offset); + uint8_t *p; + + if (outlen < len) { + return NGTCP2_ERR_NOBUF; + } + + p = out; + + *p++ = NGTCP2_FRAME_STREAM_DATA_BLOCKED; + p = ngtcp2_put_uvarint(p, (uint64_t)fr->stream_id); + p = ngtcp2_put_uvarint(p, fr->offset); + + assert((size_t)(p - out) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize +ngtcp2_pkt_encode_streams_blocked_frame(uint8_t *out, size_t outlen, + const ngtcp2_streams_blocked *fr) { + size_t len = 1 + ngtcp2_put_uvarintlen(fr->max_streams); + uint8_t *p; + + if (outlen < len) { + return NGTCP2_ERR_NOBUF; + } + + p = out; + + *p++ = (uint8_t)fr->type; + p = ngtcp2_put_uvarint(p, fr->max_streams); + + assert((size_t)(p - out) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize +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; + uint8_t *p; + + if (outlen < len) { + return NGTCP2_ERR_NOBUF; + } + + p = out; + + *p++ = NGTCP2_FRAME_NEW_CONNECTION_ID; + p = ngtcp2_put_uvarint(p, fr->seq); + 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); + + assert((size_t)(p - out) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize +ngtcp2_pkt_encode_stop_sending_frame(uint8_t *out, size_t outlen, + const ngtcp2_stop_sending *fr) { + size_t len = 1 + ngtcp2_put_uvarintlen((uint64_t)fr->stream_id) + + ngtcp2_put_uvarintlen(fr->app_error_code); + uint8_t *p; + + if (outlen < len) { + return NGTCP2_ERR_NOBUF; + } + + p = out; + + *p++ = NGTCP2_FRAME_STOP_SENDING; + p = ngtcp2_put_uvarint(p, (uint64_t)fr->stream_id); + p = ngtcp2_put_uvarint(p, fr->app_error_code); + + assert((size_t)(p - out) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize +ngtcp2_pkt_encode_path_challenge_frame(uint8_t *out, size_t outlen, + const ngtcp2_path_challenge *fr) { + size_t len = 1 + 8; + uint8_t *p; + + if (outlen < len) { + return NGTCP2_ERR_NOBUF; + } + + p = out; + + *p++ = NGTCP2_FRAME_PATH_CHALLENGE; + p = ngtcp2_cpymem(p, fr->data, sizeof(fr->data)); + + assert((size_t)(p - out) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize +ngtcp2_pkt_encode_path_response_frame(uint8_t *out, size_t outlen, + const ngtcp2_path_response *fr) { + size_t len = 1 + 8; + uint8_t *p; + + if (outlen < len) { + return NGTCP2_ERR_NOBUF; + } + + p = out; + + *p++ = NGTCP2_FRAME_PATH_RESPONSE; + p = ngtcp2_cpymem(p, fr->data, sizeof(fr->data)); + + assert((size_t)(p - out) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_encode_crypto_frame(uint8_t *out, size_t outlen, + const ngtcp2_stream *fr) { + size_t len = 1; + uint8_t *p; + size_t i; + size_t datalen = 0; + + len += ngtcp2_put_uvarintlen(fr->offset); + + for (i = 0; i < fr->datacnt; ++i) { + datalen += fr->data[i].len; + } + + len += ngtcp2_put_uvarintlen(datalen); + len += datalen; + + if (outlen < len) { + return NGTCP2_ERR_NOBUF; + } + + p = out; + + *p++ = NGTCP2_FRAME_CRYPTO; + + p = ngtcp2_put_uvarint(p, fr->offset); + p = ngtcp2_put_uvarint(p, datalen); + + for (i = 0; i < fr->datacnt; ++i) { + assert(fr->data[i].base); + p = ngtcp2_cpymem(p, fr->data[i].base, fr->data[i].len); + } + + assert((size_t)(p - out) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_encode_new_token_frame(uint8_t *out, size_t outlen, + const ngtcp2_new_token *fr) { + size_t len = 1 + ngtcp2_put_uvarintlen(fr->tokenlen) + fr->tokenlen; + uint8_t *p; + + assert(fr->tokenlen); + + if (outlen < len) { + return NGTCP2_ERR_NOBUF; + } + + p = out; + + *p++ = NGTCP2_FRAME_NEW_TOKEN; + + p = ngtcp2_put_uvarint(p, fr->tokenlen); + p = ngtcp2_cpymem(p, fr->token, fr->tokenlen); + + assert((size_t)(p - out) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_encode_retire_connection_id_frame( + uint8_t *out, size_t outlen, const ngtcp2_retire_connection_id *fr) { + size_t len = 1 + ngtcp2_put_uvarintlen(fr->seq); + uint8_t *p; + + if (outlen < len) { + return NGTCP2_ERR_NOBUF; + } + + p = out; + + *p++ = NGTCP2_FRAME_RETIRE_CONNECTION_ID; + + p = ngtcp2_put_uvarint(p, fr->seq); + + assert((size_t)(p - out) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize +ngtcp2_pkt_encode_handshake_done_frame(uint8_t *out, size_t outlen, + const ngtcp2_handshake_done *fr) { + (void)fr; + + if (outlen < 1) { + return NGTCP2_ERR_NOBUF; + } + + *out++ = NGTCP2_FRAME_HANDSHAKE_DONE; + + return 1; +} + +ngtcp2_ssize ngtcp2_pkt_encode_datagram_frame(uint8_t *out, size_t outlen, + const ngtcp2_datagram *fr) { + uint64_t datalen = ngtcp2_vec_len(fr->data, fr->datacnt); + uint64_t len = + 1 + + (fr->type == NGTCP2_FRAME_DATAGRAM ? 0 : ngtcp2_put_uvarintlen(datalen)) + + datalen; + uint8_t *p; + size_t i; + + assert(fr->type == NGTCP2_FRAME_DATAGRAM || + fr->type == NGTCP2_FRAME_DATAGRAM_LEN); + + if (outlen < len) { + return NGTCP2_ERR_NOBUF; + } + + p = out; + + *p++ = (uint8_t)fr->type; + if (fr->type == NGTCP2_FRAME_DATAGRAM_LEN) { + p = ngtcp2_put_uvarint(p, datalen); + } + + for (i = 0; i < fr->datacnt; ++i) { + assert(fr->data[i].len); + assert(fr->data[i].base); + p = ngtcp2_cpymem(p, fr->data[i].base, fr->data[i].len); + } + + assert((size_t)(p - out) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_write_version_negotiation( + uint8_t *dest, size_t destlen, uint8_t unused_random, const uint8_t *dcid, + size_t dcidlen, const uint8_t *scid, size_t scidlen, const uint32_t *sv, + size_t nsv) { + size_t len = 1 + 4 + 1 + dcidlen + 1 + scidlen + nsv * 4; + uint8_t *p; + size_t i; + + assert(dcidlen < 256); + assert(scidlen < 256); + + if (destlen < len) { + return NGTCP2_ERR_NOBUF; + } + + p = dest; + + *p++ = 0xc0 | unused_random; + p = ngtcp2_put_uint32be(p, 0); + *p++ = (uint8_t)dcidlen; + if (dcidlen) { + p = ngtcp2_cpymem(p, dcid, dcidlen); + } + *p++ = (uint8_t)scidlen; + if (scidlen) { + p = ngtcp2_cpymem(p, scid, scidlen); + } + + for (i = 0; i < nsv; ++i) { + p = ngtcp2_put_uint32be(p, sv[i]); + } + + assert((size_t)(p - dest) == len); + + return (ngtcp2_ssize)len; +} + +size_t ngtcp2_pkt_decode_version_negotiation(uint32_t *dest, + const uint8_t *payload, + size_t payloadlen) { + const uint8_t *end = payload + payloadlen; + + assert((payloadlen % sizeof(uint32_t)) == 0); + + for (; payload != end;) { + payload = ngtcp2_get_uint32(dest++, payload); + } + + return payloadlen / sizeof(uint32_t); +} + +int ngtcp2_pkt_decode_stateless_reset(ngtcp2_pkt_stateless_reset *sr, + const uint8_t *payload, + size_t payloadlen) { + const uint8_t *p = payload; + + if (payloadlen < + NGTCP2_MIN_STATELESS_RESET_RANDLEN + NGTCP2_STATELESS_RESET_TOKENLEN) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + sr->rand = p; + sr->randlen = payloadlen - NGTCP2_STATELESS_RESET_TOKENLEN; + p += sr->randlen; + memcpy(sr->stateless_reset_token, p, NGTCP2_STATELESS_RESET_TOKENLEN); + + return 0; +} + +int ngtcp2_pkt_decode_retry(ngtcp2_pkt_retry *dest, const uint8_t *payload, + size_t payloadlen) { + size_t len = /* token */ 1 + NGTCP2_RETRY_TAGLEN; + + if (payloadlen < len) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + dest->token = (uint8_t *)payload; + dest->tokenlen = (size_t)(payloadlen - NGTCP2_RETRY_TAGLEN); + ngtcp2_cpymem(dest->tag, payload + dest->tokenlen, NGTCP2_RETRY_TAGLEN); + + return 0; +} + +int64_t ngtcp2_pkt_adjust_pkt_num(int64_t max_pkt_num, int64_t pkt_num, + size_t pkt_numlen) { + int64_t expected = max_pkt_num + 1; + int64_t win = (int64_t)1 << (pkt_numlen * 8); + int64_t hwin = win / 2; + int64_t mask = win - 1; + int64_t cand = (expected & ~mask) | pkt_num; + + if (cand <= expected - hwin) { + assert(cand <= (int64_t)NGTCP2_MAX_VARINT - win); + return cand + win; + } + if (cand > expected + hwin && cand >= win) { + return cand - win; + } + return cand; +} + +int ngtcp2_pkt_validate_ack(ngtcp2_ack *fr, int64_t min_pkt_num) { + int64_t largest_ack = fr->largest_ack; + size_t i; + + if (largest_ack < (int64_t)fr->first_ack_range) { + return NGTCP2_ERR_ACK_FRAME; + } + + largest_ack -= (int64_t)fr->first_ack_range; + + if (largest_ack < min_pkt_num) { + return NGTCP2_ERR_PROTO; + } + + for (i = 0; i < fr->rangecnt; ++i) { + if (largest_ack < (int64_t)fr->ranges[i].gap + 2) { + return NGTCP2_ERR_ACK_FRAME; + } + + largest_ack -= (int64_t)fr->ranges[i].gap + 2; + + if (largest_ack < (int64_t)fr->ranges[i].len) { + return NGTCP2_ERR_ACK_FRAME; + } + + largest_ack -= (int64_t)fr->ranges[i].len; + + if (largest_ack < min_pkt_num) { + return NGTCP2_ERR_PROTO; + } + } + + return 0; +} + +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) { + uint8_t *p; + + if (destlen < + NGTCP2_MIN_STATELESS_RESET_RANDLEN + NGTCP2_STATELESS_RESET_TOKENLEN) { + return NGTCP2_ERR_NOBUF; + } + + if (randlen < NGTCP2_MIN_STATELESS_RESET_RANDLEN) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + p = dest; + + randlen = ngtcp2_min(destlen - NGTCP2_STATELESS_RESET_TOKENLEN, randlen); + + p = ngtcp2_cpymem(p, rand, randlen); + p = ngtcp2_cpymem(p, stateless_reset_token, NGTCP2_STATELESS_RESET_TOKENLEN); + *dest = (uint8_t)((*dest & 0x7fu) | 0x40u); + + return p - dest; +} + +ngtcp2_ssize ngtcp2_pkt_write_retry( + uint8_t *dest, size_t destlen, uint32_t version, const ngtcp2_cid *dcid, + const ngtcp2_cid *scid, const ngtcp2_cid *odcid, const uint8_t *token, + size_t tokenlen, ngtcp2_encrypt encrypt, const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx) { + ngtcp2_pkt_hd hd; + uint8_t pseudo_retry[1500]; + ngtcp2_ssize pseudo_retrylen; + uint8_t tag[NGTCP2_RETRY_TAGLEN]; + int rv; + uint8_t *p; + size_t offset; + const uint8_t *nonce; + size_t noncelen; + + assert(tokenlen > 0); + assert(!ngtcp2_cid_eq(scid, odcid)); + + /* Retry packet is sent at most once per one connection attempt. In + the first connection attempt, client has to send random DCID + which is at least NGTCP2_MIN_INITIAL_DCIDLEN bytes long. */ + if (odcid->datalen < NGTCP2_MIN_INITIAL_DCIDLEN) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_LONG_FORM, NGTCP2_PKT_RETRY, dcid, + scid, /* pkt_num = */ 0, /* pkt_numlen = */ 1, version, + /* len = */ 0); + + pseudo_retrylen = + ngtcp2_pkt_encode_pseudo_retry(pseudo_retry, sizeof(pseudo_retry), &hd, + /* unused = */ 0, odcid, token, tokenlen); + if (pseudo_retrylen < 0) { + return pseudo_retrylen; + } + + switch (version) { + case NGTCP2_PROTO_VER_V1: + default: + nonce = (const uint8_t *)NGTCP2_RETRY_NONCE_V1; + noncelen = sizeof(NGTCP2_RETRY_NONCE_V1) - 1; + break; + case NGTCP2_PROTO_VER_V2: + nonce = (const uint8_t *)NGTCP2_RETRY_NONCE_V2; + noncelen = sizeof(NGTCP2_RETRY_NONCE_V2) - 1; + break; + } + + /* OpenSSL does not like NULL plaintext. */ + rv = encrypt(tag, aead, aead_ctx, (const uint8_t *)"", 0, nonce, noncelen, + pseudo_retry, (size_t)pseudo_retrylen); + if (rv != 0) { + return rv; + } + + offset = 1 + odcid->datalen; + if (destlen < (size_t)pseudo_retrylen + sizeof(tag) - offset) { + return NGTCP2_ERR_NOBUF; + } + + p = ngtcp2_cpymem(dest, pseudo_retry + offset, + (size_t)pseudo_retrylen - offset); + p = ngtcp2_cpymem(p, tag, sizeof(tag)); + + return p - dest; +} + +ngtcp2_ssize ngtcp2_pkt_encode_pseudo_retry( + uint8_t *dest, size_t destlen, const ngtcp2_pkt_hd *hd, uint8_t unused, + const ngtcp2_cid *odcid, const uint8_t *token, size_t tokenlen) { + uint8_t *p = dest; + ngtcp2_ssize nwrite; + + if (destlen < 1 + odcid->datalen) { + return NGTCP2_ERR_NOBUF; + } + + *p++ = (uint8_t)odcid->datalen; + p = ngtcp2_cpymem(p, odcid->data, odcid->datalen); + destlen -= (size_t)(p - dest); + + nwrite = ngtcp2_pkt_encode_hd_long(p, destlen, hd); + if (nwrite < 0) { + return nwrite; + } + + if (destlen < (size_t)nwrite + tokenlen) { + return NGTCP2_ERR_NOBUF; + } + + *p &= 0xf0; + *p |= unused; + + p += nwrite; + + p = ngtcp2_cpymem(p, token, tokenlen); + + return p - dest; +} + +int ngtcp2_pkt_verify_retry_tag(uint32_t version, const ngtcp2_pkt_retry *retry, + const uint8_t *pkt, size_t pktlen, + ngtcp2_encrypt encrypt, + const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx) { + uint8_t pseudo_retry[1500]; + size_t pseudo_retrylen; + uint8_t *p = pseudo_retry; + int rv; + uint8_t tag[NGTCP2_RETRY_TAGLEN]; + const uint8_t *nonce; + size_t noncelen; + + assert(pktlen >= sizeof(retry->tag)); + + if (sizeof(pseudo_retry) < + 1 + retry->odcid.datalen + pktlen - sizeof(retry->tag)) { + return NGTCP2_ERR_PROTO; + } + + *p++ = (uint8_t)retry->odcid.datalen; + p = ngtcp2_cpymem(p, retry->odcid.data, retry->odcid.datalen); + p = ngtcp2_cpymem(p, pkt, pktlen - sizeof(retry->tag)); + + pseudo_retrylen = (size_t)(p - pseudo_retry); + + switch (version) { + case NGTCP2_PROTO_VER_V1: + default: + nonce = (const uint8_t *)NGTCP2_RETRY_NONCE_V1; + noncelen = sizeof(NGTCP2_RETRY_NONCE_V1) - 1; + break; + case NGTCP2_PROTO_VER_V2: + nonce = (const uint8_t *)NGTCP2_RETRY_NONCE_V2; + noncelen = sizeof(NGTCP2_RETRY_NONCE_V2) - 1; + break; + } + + /* OpenSSL does not like NULL plaintext. */ + rv = encrypt(tag, aead, aead_ctx, (const uint8_t *)"", 0, nonce, noncelen, + pseudo_retry, pseudo_retrylen); + if (rv != 0) { + return rv; + } + + if (0 != memcmp(retry->tag, tag, sizeof(retry->tag))) { + return NGTCP2_ERR_PROTO; + } + + return 0; +} + +size_t ngtcp2_pkt_stream_max_datalen(int64_t stream_id, uint64_t offset, + uint64_t len, size_t left) { + size_t n = 1 /* type */ + ngtcp2_put_uvarintlen((uint64_t)stream_id) + + (offset ? ngtcp2_put_uvarintlen(offset) : 0); + + if (left <= n) { + return (size_t)-1; + } + + left -= n; + + if (left > 8 + 1073741823 && len > 1073741823) { +#if SIZE_MAX > UINT32_MAX + len = ngtcp2_min(len, 4611686018427387903lu); +#endif /* SIZE_MAX > UINT32_MAX */ + return (size_t)ngtcp2_min(len, (uint64_t)(left - 8)); + } + + if (left > 4 + 16383 && len > 16383) { + len = ngtcp2_min(len, 1073741823); + return (size_t)ngtcp2_min(len, (uint64_t)(left - 4)); + } + + if (left > 2 + 63 && len > 63) { + len = ngtcp2_min(len, 16383); + return (size_t)ngtcp2_min(len, (uint64_t)(left - 2)); + } + + len = ngtcp2_min(len, 63); + return (size_t)ngtcp2_min(len, (uint64_t)(left - 1)); +} + +size_t ngtcp2_pkt_crypto_max_datalen(uint64_t offset, size_t len, size_t left) { + size_t n = 1 /* type */ + ngtcp2_put_uvarintlen(offset); + + /* CRYPTO frame must contain nonzero length data. Return -1 if + there is no space to write crypto data. */ + if (left <= n + 1) { + return (size_t)-1; + } + + left -= n; + + if (left > 8 + 1073741823 && len > 1073741823) { +#if SIZE_MAX > UINT32_MAX + len = ngtcp2_min(len, 4611686018427387903lu); +#endif /* SIZE_MAX > UINT32_MAX */ + return ngtcp2_min(len, left - 8); + } + + if (left > 4 + 16383 && len > 16383) { + len = ngtcp2_min(len, 1073741823); + return ngtcp2_min(len, left - 4); + } + + if (left > 2 + 63 && len > 63) { + len = ngtcp2_min(len, 16383); + return ngtcp2_min(len, left - 2); + } + + len = ngtcp2_min(len, 63); + return ngtcp2_min(len, left - 1); +} + +size_t ngtcp2_pkt_datagram_framelen(size_t len) { + return 1 /* type */ + ngtcp2_put_uvarintlen(len) + len; +} + +int ngtcp2_is_supported_version(uint32_t version) { + switch (version) { + case NGTCP2_PROTO_VER_V1: + case NGTCP2_PROTO_VER_V2: + return 1; + default: + return 0; + } +} + +int ngtcp2_is_reserved_version(uint32_t version) { + return (version & NGTCP2_RESERVED_VERSION_MASK) == + NGTCP2_RESERVED_VERSION_MASK; +} + +uint8_t ngtcp2_pkt_get_type_long(uint32_t version, uint8_t c) { + uint8_t pkt_type = (uint8_t)((c & NGTCP2_LONG_TYPE_MASK) >> 4); + + switch (version) { + case NGTCP2_PROTO_VER_V2: + switch (pkt_type) { + case NGTCP2_PKT_TYPE_INITIAL_V2: + return NGTCP2_PKT_INITIAL; + case NGTCP2_PKT_TYPE_0RTT_V2: + return NGTCP2_PKT_0RTT; + case NGTCP2_PKT_TYPE_HANDSHAKE_V2: + return NGTCP2_PKT_HANDSHAKE; + case NGTCP2_PKT_TYPE_RETRY_V2: + return NGTCP2_PKT_RETRY; + default: + return 0; + } + default: + if (!ngtcp2_is_supported_version(version)) { + return 0; + } + + switch (pkt_type) { + case NGTCP2_PKT_TYPE_INITIAL_V1: + return NGTCP2_PKT_INITIAL; + case NGTCP2_PKT_TYPE_0RTT_V1: + return NGTCP2_PKT_0RTT; + case NGTCP2_PKT_TYPE_HANDSHAKE_V1: + return NGTCP2_PKT_HANDSHAKE; + case NGTCP2_PKT_TYPE_RETRY_V1: + return NGTCP2_PKT_RETRY; + default: + return 0; + } + } +} + +uint8_t ngtcp2_pkt_versioned_type(uint32_t version, uint32_t pkt_type) { + switch (version) { + case NGTCP2_PROTO_VER_V2: + switch (pkt_type) { + case NGTCP2_PKT_INITIAL: + return NGTCP2_PKT_TYPE_INITIAL_V2; + case NGTCP2_PKT_0RTT: + return NGTCP2_PKT_TYPE_0RTT_V2; + case NGTCP2_PKT_HANDSHAKE: + return NGTCP2_PKT_TYPE_HANDSHAKE_V2; + case NGTCP2_PKT_RETRY: + return NGTCP2_PKT_TYPE_RETRY_V2; + default: + ngtcp2_unreachable(); + } + default: + /* Assume that unsupported versions share the numeric long packet + types with QUIC v1 in order to send a packet to elicit Version + Negotiation packet. */ + + switch (pkt_type) { + case NGTCP2_PKT_INITIAL: + return NGTCP2_PKT_TYPE_INITIAL_V1; + case NGTCP2_PKT_0RTT: + return NGTCP2_PKT_TYPE_0RTT_V1; + case NGTCP2_PKT_HANDSHAKE: + return NGTCP2_PKT_TYPE_HANDSHAKE_V1; + case NGTCP2_PKT_RETRY: + return NGTCP2_PKT_TYPE_RETRY_V1; + default: + ngtcp2_unreachable(); + } + } +} + +int ngtcp2_pkt_verify_reserved_bits(uint8_t c) { + if (c & NGTCP2_HEADER_FORM_BIT) { + return (c & NGTCP2_LONG_RESERVED_BIT_MASK) == 0 ? 0 : NGTCP2_ERR_PROTO; + } + return (c & NGTCP2_SHORT_RESERVED_BIT_MASK) == 0 ? 0 : NGTCP2_ERR_PROTO; +} diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_pkt.h b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_pkt.h new file mode 100644 index 0000000..feec4d3 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_pkt.h @@ -0,0 +1,1232 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 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_PKT_H +#define NGTCP2_PKT_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +/* QUIC header macros */ +#define NGTCP2_HEADER_FORM_BIT 0x80 +#define NGTCP2_FIXED_BIT_MASK 0x40 +#define NGTCP2_PKT_NUMLEN_MASK 0x03 + +/* Long header specific macros */ +#define NGTCP2_LONG_TYPE_MASK 0x30 +#define NGTCP2_LONG_RESERVED_BIT_MASK 0x0c + +/* Short header specific macros */ +#define NGTCP2_SHORT_SPIN_BIT_MASK 0x20 +#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 + +/* 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> + + LENGTH<1> + PKN<1> */ +#define NGTCP2_MIN_LONG_HEADERLEN (1 + 4 + 1 + 1 + 1 + 1) + +#define NGTCP2_STREAM_FIN_BIT 0x01 +#define NGTCP2_STREAM_LEN_BIT 0x02 +#define NGTCP2_STREAM_OFF_BIT 0x04 + +/* NGTCP2_STREAM_OVERHEAD is the maximum number of bytes required + other than payload for STREAM frame. That is from type field to + the beginning of the payload. */ +#define NGTCP2_STREAM_OVERHEAD (1 + 8 + 8 + 8) + +/* NGTCP2_CRYPTO_OVERHEAD is the maximum number of bytes required + other than payload for CRYPTO frame. That is from type field to + the beginning of the payload. */ +#define NGTCP2_CRYPTO_OVERHEAD (1 + 8 + 8) + +/* NGTCP2_DATAGRAM_OVERHEAD is the maximum number of bytes required + other than payload for DATAGRAM frame. That is from type field to + the beginning of the payload. */ +#define NGTCP2_DATAGRAM_OVERHEAD (1 + 8) + +/* NGTCP2_MIN_FRAME_PAYLOADLEN is the minimum frame payload length. */ +#define NGTCP2_MIN_FRAME_PAYLOADLEN 16 + +/* NGTCP2_MAX_SERVER_STREAM_ID_BIDI is the maximum bidirectional + server stream ID. */ +#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) +/* NGTCP2_MAX_SERVER_STREAM_ID_UNI is the maximum unidirectional + server stream ID. */ +#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) + +/* 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)) + +/* NGTCP2_MIN_PKT_EXPANDLEN is the minimum packet size expansion in + addition to the minimum DCID length to hide/trigger Stateless + Reset. */ +#define NGTCP2_MIN_PKT_EXPANDLEN 22 + +/* 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 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 + +/* NGTCP2_PKT_TYPE_INITIAL_V1 is Initial long header packet type for + QUIC v1. */ +#define NGTCP2_PKT_TYPE_INITIAL_V1 0x0 +/* NGTCP2_PKT_TYPE_0RTT_V1 is 0RTT long header packet type for QUIC + v1. */ +#define NGTCP2_PKT_TYPE_0RTT_V1 0x1 +/* NGTCP2_PKT_TYPE_HANDSHAKE_V1 is Handshake long header packet type + for QUIC v1. */ +#define NGTCP2_PKT_TYPE_HANDSHAKE_V1 0x2 +/* NGTCP2_PKT_TYPE_RETRY_V1 is Retry long header packet type for QUIC + v1. */ +#define NGTCP2_PKT_TYPE_RETRY_V1 0x3 + +/* NGTCP2_PKT_TYPE_INITIAL_V2 is Initial long header packet type for + QUIC v2. */ +#define NGTCP2_PKT_TYPE_INITIAL_V2 0x1 +/* NGTCP2_PKT_TYPE_0RTT_V2 is 0RTT long header packet type for QUIC + v2. */ +#define NGTCP2_PKT_TYPE_0RTT_V2 0x2 +/* NGTCP2_PKT_TYPE_HANDSHAKE_V2 is Handshake long header packet type + for QUIC v2. */ +#define NGTCP2_PKT_TYPE_HANDSHAKE_V2 0x3 +/* NGTCP2_PKT_TYPE_RETRY_V2 is Retry long header packet type for QUIC + v2. */ +#define NGTCP2_PKT_TYPE_RETRY_V2 0x0 + +typedef struct ngtcp2_pkt_retry { + ngtcp2_cid odcid; + uint8_t *token; + size_t tokenlen; + uint8_t tag[NGTCP2_RETRY_TAGLEN]; +} ngtcp2_pkt_retry; + +#define NGTCP2_FRAME_PADDING 0x00 +#define NGTCP2_FRAME_PING 0x01 +#define NGTCP2_FRAME_ACK 0x02 +#define NGTCP2_FRAME_ACK_ECN 0x03 +#define NGTCP2_FRAME_RESET_STREAM 0x04 +#define NGTCP2_FRAME_STOP_SENDING 0x05 +#define NGTCP2_FRAME_CRYPTO 0x06 +#define NGTCP2_FRAME_NEW_TOKEN 0x07 +#define NGTCP2_FRAME_STREAM 0x08 +#define NGTCP2_FRAME_MAX_DATA 0x10 +#define NGTCP2_FRAME_MAX_STREAM_DATA 0x11 +#define NGTCP2_FRAME_MAX_STREAMS_BIDI 0x12 +#define NGTCP2_FRAME_MAX_STREAMS_UNI 0x13 +#define NGTCP2_FRAME_DATA_BLOCKED 0x14 +#define NGTCP2_FRAME_STREAM_DATA_BLOCKED 0x15 +#define NGTCP2_FRAME_STREAMS_BLOCKED_BIDI 0x16 +#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_DATAGRAM 0x30 +#define NGTCP2_FRAME_DATAGRAM_LEN 0x31 + +/* ngtcp2_stream represents STREAM and CRYPTO frames. */ +typedef struct ngtcp2_stream { + uint64_t type; + /** + * flags of decoded STREAM frame. This gets ignored when encoding + * STREAM frame. CRYPTO frame does not include this field, and must + * set it to 0. + */ + uint8_t flags; + /* CRYPTO frame does not include this field, and must set it to + 0. */ + uint8_t fin; + /* CRYPTO frame does not include this field, and must set it to + 0. */ + int64_t stream_id; + uint64_t offset; + /* datacnt is the number of elements that data contains. Although + 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]; +} ngtcp2_stream; + +typedef struct ngtcp2_ack_range { + uint64_t gap; + uint64_t len; +} ngtcp2_ack_range; + +typedef struct ngtcp2_ack { + uint64_t type; + int64_t largest_ack; + uint64_t ack_delay; + /** + * ack_delay_unscaled is an ack_delay multiplied by + * 2**ack_delay_component * NGTCP2_MICROSECONDS. + */ + ngtcp2_duration ack_delay_unscaled; + struct { + uint64_t ect0; + uint64_t ect1; + uint64_t ce; + } ecn; + uint64_t first_ack_range; + size_t rangecnt; + ngtcp2_ack_range ranges[1]; +} ngtcp2_ack; + +typedef struct ngtcp2_padding { + uint64_t type; + /** + * The length of contiguous PADDING frames. + */ + size_t len; +} ngtcp2_padding; + +typedef struct ngtcp2_reset_stream { + uint64_t type; + int64_t stream_id; + uint64_t app_error_code; + uint64_t final_size; +} ngtcp2_reset_stream; + +typedef struct ngtcp2_connection_close { + uint64_t type; + uint64_t error_code; + uint64_t frame_type; + size_t reasonlen; + uint8_t *reason; +} ngtcp2_connection_close; + +typedef struct ngtcp2_max_data { + uint64_t type; + /** + * max_data is Maximum Data. + */ + uint64_t max_data; +} ngtcp2_max_data; + +typedef struct ngtcp2_max_stream_data { + uint64_t type; + int64_t stream_id; + uint64_t max_stream_data; +} ngtcp2_max_stream_data; + +typedef struct ngtcp2_max_streams { + uint64_t type; + uint64_t max_streams; +} ngtcp2_max_streams; + +typedef struct ngtcp2_ping { + uint64_t type; +} ngtcp2_ping; + +typedef struct ngtcp2_data_blocked { + uint64_t type; + uint64_t offset; +} ngtcp2_data_blocked; + +typedef struct ngtcp2_stream_data_blocked { + uint64_t type; + int64_t stream_id; + uint64_t offset; +} ngtcp2_stream_data_blocked; + +typedef struct ngtcp2_streams_blocked { + uint64_t type; + uint64_t max_streams; +} ngtcp2_streams_blocked; + +typedef struct ngtcp2_new_connection_id { + uint64_t type; + uint64_t seq; + uint64_t retire_prior_to; + ngtcp2_cid cid; + uint8_t stateless_reset_token[NGTCP2_STATELESS_RESET_TOKENLEN]; +} ngtcp2_new_connection_id; + +typedef struct ngtcp2_stop_sending { + uint64_t type; + int64_t stream_id; + uint64_t app_error_code; +} ngtcp2_stop_sending; + +typedef struct ngtcp2_path_challenge { + uint64_t type; + uint8_t data[NGTCP2_PATH_CHALLENGE_DATALEN]; +} ngtcp2_path_challenge; + +typedef struct ngtcp2_path_response { + uint64_t type; + uint8_t data[NGTCP2_PATH_CHALLENGE_DATALEN]; +} ngtcp2_path_response; + +typedef struct ngtcp2_new_token { + uint64_t type; + uint8_t *token; + size_t tokenlen; +} ngtcp2_new_token; + +typedef struct ngtcp2_retire_connection_id { + uint64_t type; + uint64_t seq; +} ngtcp2_retire_connection_id; + +typedef struct ngtcp2_handshake_done { + uint64_t type; +} ngtcp2_handshake_done; + +typedef struct ngtcp2_datagram { + uint64_t type; + /* dgram_id is an opaque identifier chosen by an application. */ + 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. */ + 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_stream stream; + ngtcp2_ack ack; + ngtcp2_padding padding; + ngtcp2_reset_stream reset_stream; + ngtcp2_connection_close connection_close; + ngtcp2_max_data max_data; + ngtcp2_max_stream_data max_stream_data; + ngtcp2_max_streams max_streams; + ngtcp2_ping ping; + ngtcp2_data_blocked data_blocked; + ngtcp2_stream_data_blocked stream_data_blocked; + ngtcp2_streams_blocked streams_blocked; + ngtcp2_new_connection_id new_connection_id; + ngtcp2_stop_sending stop_sending; + ngtcp2_path_challenge path_challenge; + ngtcp2_path_response path_response; + ngtcp2_new_token new_token; + ngtcp2_retire_connection_id retire_connection_id; + ngtcp2_handshake_done handshake_done; + ngtcp2_datagram datagram; +} ngtcp2_frame; + +typedef struct ngtcp2_pkt_chain ngtcp2_pkt_chain; + +/* + * ngtcp2_pkt_chain is the chain of incoming packets buffered. + */ +struct ngtcp2_pkt_chain { + ngtcp2_path_storage path; + ngtcp2_pkt_info pi; + ngtcp2_pkt_chain *next; + uint8_t *pkt; + /* pktlen is length of a QUIC packet. */ + size_t pktlen; + /* dgramlen is length of UDP datagram that a QUIC packet is + included. */ + size_t dgramlen; + ngtcp2_tstamp ts; +}; + +/* + * ngtcp2_pkt_chain_new allocates ngtcp2_pkt_chain objects, and + * assigns its pointer to |*ppc|. The content of buffer pointed by + * |pkt| of length |pktlen| is copied into |*ppc|. The packet is + * obtained via the network |path|. The values of path->local and + * path->remote are copied into |*ppc|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +int ngtcp2_pkt_chain_new(ngtcp2_pkt_chain **ppc, const ngtcp2_path *path, + const ngtcp2_pkt_info *pi, const uint8_t *pkt, + size_t pktlen, size_t dgramlen, ngtcp2_tstamp ts, + const ngtcp2_mem *mem); + +/* + * ngtcp2_pkt_chain_del deallocates |pc|. It also frees the memory + * pointed by |pc|. + */ +void ngtcp2_pkt_chain_del(ngtcp2_pkt_chain *pc, const ngtcp2_mem *mem); + +/* + * ngtcp2_pkt_hd_init initializes |hd| with the given values. If + * |dcid| and/or |scid| is NULL, DCID and SCID of |hd| is empty + * respectively. |pkt_numlen| is the number of bytes used to encode + * |pkt_num| and either 1, 2, or 4. |version| is QUIC version for + * long header. |len| is the length field of Initial, 0RTT, and + * Handshake packets. + */ +void ngtcp2_pkt_hd_init(ngtcp2_pkt_hd *hd, uint8_t flags, uint8_t type, + const ngtcp2_cid *dcid, const ngtcp2_cid *scid, + int64_t pkt_num, size_t pkt_numlen, uint32_t version, + size_t len); + +/* + * ngtcp2_pkt_encode_hd_long encodes |hd| as QUIC long header into + * |out| which has length |outlen|. It returns the number of bytes + * written into |outlen| if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer is too short + */ +ngtcp2_ssize ngtcp2_pkt_encode_hd_long(uint8_t *out, size_t outlen, + const ngtcp2_pkt_hd *hd); + +/* + * ngtcp2_pkt_encode_hd_short encodes |hd| as QUIC short header into + * |out| which has length |outlen|. It returns the number of bytes + * written into |outlen| if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer is too short + */ +ngtcp2_ssize ngtcp2_pkt_encode_hd_short(uint8_t *out, size_t outlen, + const ngtcp2_pkt_hd *hd); + +/** + * @function + * + * `ngtcp2_pkt_decode_frame` 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 + * frame if it succeeds, or one of the following negative error codes: + * + * :enum:`NGTCP2_ERR_FRAME_ENCODING` + * 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); + +/** + * @function + * + * `ngtcp2_pkt_encode_frame` encodes a frame |fm| into the buffer + * pointed by |out| of length |outlen|. + * + * This function returns the number of bytes written to the buffer, or + * one of the following negative error codes: + * + * :enum:`NGTCP2_ERR_NOBUF` + * Buffer does not have enough capacity to write a frame. + */ +ngtcp2_ssize ngtcp2_pkt_encode_frame(uint8_t *out, size_t outlen, + ngtcp2_frame *fr); + +/* + * ngtcp2_pkt_decode_version_negotiation decodes Version Negotiation + * packet payload |payload| of length |payloadlen|, and stores the + * result in |dest|. |dest| must have enough capacity to store the + * result. |payloadlen| also must be a multiple of sizeof(uint32_t). + * + * This function returns the number of versions written in |dest|. + */ +size_t ngtcp2_pkt_decode_version_negotiation(uint32_t *dest, + const uint8_t *payload, + size_t payloadlen); + +/* + * ngtcp2_pkt_decode_stateless_reset decodes Stateless Reset payload + * |payload| of length |payloadlen|. The |payload| must start with + * Stateless Reset Token. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_INVALID_ARGUMENT + * Payloadlen is too short. + */ +int ngtcp2_pkt_decode_stateless_reset(ngtcp2_pkt_stateless_reset *sr, + const uint8_t *payload, + size_t payloadlen); + +/* + * ngtcp2_pkt_decode_retry decodes Retry packet payload |payload| of + * length |payloadlen|. The |payload| must start with Retry token + * field. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_INVALID_ARGUMENT + * Payloadlen is too short. + */ +int ngtcp2_pkt_decode_retry(ngtcp2_pkt_retry *dest, const uint8_t *payload, + size_t payloadlen); + +/* + * ngtcp2_pkt_decode_stream_frame decodes STREAM frame from |payload| + * of length |payloadlen|. The result is stored in the object pointed + * by |dest|. STREAM frame must start at payload[0]. This function + * finishes when it decodes one STREAM frame, and returns the exact + * number of bytes read to decode a frame if it succeeds, or one of + * the following negative error codes: + * + * NGTCP2_ERR_FRAME_ENCODING + * Payload is too short to include STREAM frame. + */ +ngtcp2_ssize ngtcp2_pkt_decode_stream_frame(ngtcp2_stream *dest, + const uint8_t *payload, + size_t payloadlen); + +/* + * ngtcp2_pkt_decode_ack_frame decodes ACK frame from |payload| of + * length |payloadlen|. The result is stored in the object pointed by + * |dest|. ACK frame must start at payload[0]. This function + * finishes when it decodes one ACK frame, and returns the exact + * number of bytes read to decode a frame if it succeeds, or one of + * the following negative error codes: + * + * NGTCP2_ERR_FRAME_ENCODING + * Payload is too short to include ACK frame. + */ +ngtcp2_ssize ngtcp2_pkt_decode_ack_frame(ngtcp2_ack *dest, + const uint8_t *payload, + size_t payloadlen); + +/* + * ngtcp2_pkt_decode_padding_frame decodes contiguous PADDING frames + * from |payload| of length |payloadlen|. It continues to parse + * frames as long as the frame type is PADDING. This finishes when it + * encounters the frame type which is not PADDING, or all input data + * is read. The first byte (payload[0]) must be NGTCP2_FRAME_PADDING. + * This function returns the exact number of bytes read to decode + * PADDING frames. + */ +ngtcp2_ssize ngtcp2_pkt_decode_padding_frame(ngtcp2_padding *dest, + const uint8_t *payload, + size_t payloadlen); + +/* + * ngtcp2_pkt_decode_reset_stream_frame decodes RESET_STREAM frame + * from |payload| of length |payloadlen|. The result is stored in the + * object pointed by |dest|. RESET_STREAM frame must start at + * payload[0]. This function finishes when it decodes one + * RESET_STREAM frame, and returns the exact number of bytes read to + * decode a frame if it succeeds, or one of the following negative + * error codes: + * + * NGTCP2_ERR_FRAME_ENCODING + * Payload is too short to include RESET_STREAM frame. + */ +ngtcp2_ssize ngtcp2_pkt_decode_reset_stream_frame(ngtcp2_reset_stream *dest, + const uint8_t *payload, + size_t payloadlen); + +/* + * ngtcp2_pkt_decode_connection_close_frame decodes CONNECTION_CLOSE + * frame from |payload| of length |payloadlen|. The result is stored + * in the object pointed by |dest|. CONNECTION_CLOSE frame must start + * at payload[0]. This function finishes it decodes one + * CONNECTION_CLOSE frame, and returns the exact number of bytes read + * to decode a frame if it succeeds, or one of the following negative + * error codes: + * + * NGTCP2_ERR_FRAME_ENCODING + * Payload is too short to include CONNECTION_CLOSE frame. + */ +ngtcp2_ssize ngtcp2_pkt_decode_connection_close_frame( + ngtcp2_connection_close *dest, const uint8_t *payload, size_t payloadlen); + +/* + * ngtcp2_pkt_decode_max_data_frame decodes MAX_DATA frame from + * |payload| of length |payloadlen|. The result is stored in the + * object pointed by |dest|. MAX_DATA frame must start at payload[0]. + * This function finishes when it decodes one MAX_DATA frame, and + * returns the exact number of bytes read to decode a frame if it + * succeeds, or one of the following negative error codes: + * + * NGTCP2_ERR_FRAME_ENCODING + * Payload is too short to include MAX_DATA frame. + */ +ngtcp2_ssize ngtcp2_pkt_decode_max_data_frame(ngtcp2_max_data *dest, + const uint8_t *payload, + size_t payloadlen); + +/* + * ngtcp2_pkt_decode_max_stream_data_frame decodes MAX_STREAM_DATA + * frame from |payload| of length |payloadlen|. The result is stored + * in the object pointed by |dest|. MAX_STREAM_DATA frame must start + * at payload[0]. This function finishes when it decodes one + * MAX_STREAM_DATA frame, and returns the exact number of bytes read + * to decode a frame if it succeeds, or one of the following negative + * error codes: + * + * NGTCP2_ERR_FRAME_ENCODING + * Payload is too short to include MAX_STREAM_DATA frame. + */ +ngtcp2_ssize ngtcp2_pkt_decode_max_stream_data_frame( + ngtcp2_max_stream_data *dest, const uint8_t *payload, size_t payloadlen); + +/* + * ngtcp2_pkt_decode_max_streams_frame decodes MAX_STREAMS frame from + * |payload| of length |payloadlen|. The result is stored in the + * object pointed by |dest|. MAX_STREAMS frame must start at + * payload[0]. This function finishes when it decodes one MAX_STREAMS + * frame, and returns the exact number of bytes read to decode a frame + * if it succeeds, or one of the following negative error codes: + * + * NGTCP2_ERR_FRAME_ENCODING + * Payload is too short to include MAX_STREAMS frame. + */ +ngtcp2_ssize ngtcp2_pkt_decode_max_streams_frame(ngtcp2_max_streams *dest, + const uint8_t *payload, + size_t payloadlen); + +/* + * ngtcp2_pkt_decode_ping_frame decodes PING frame from |payload| of + * length |payloadlen|. The result is stored in the object pointed by + * |dest|. PING frame must start at payload[0]. This function + * finishes when it decodes one PING frame, and returns the exact + * number of bytes read to decode a frame. + */ +ngtcp2_ssize ngtcp2_pkt_decode_ping_frame(ngtcp2_ping *dest, + const uint8_t *payload, + size_t payloadlen); + +/* + * ngtcp2_pkt_decode_data_blocked_frame decodes DATA_BLOCKED frame + * from |payload| of length |payloadlen|. The result is stored in the + * object pointed by |dest|. DATA_BLOCKED frame must start at + * payload[0]. This function finishes when it decodes one + * DATA_BLOCKED frame, and returns the exact number of bytes read to + * decode a frame if it succeeds, or one of the following negative + * error codes: + * + * NGTCP2_ERR_FRAME_ENCODING + * Payload is too short to include DATA_BLOCKED frame. + */ +ngtcp2_ssize ngtcp2_pkt_decode_data_blocked_frame(ngtcp2_data_blocked *dest, + const uint8_t *payload, + size_t payloadlen); + +/* + * ngtcp2_pkt_decode_stream_data_blocked_frame decodes + * STREAM_DATA_BLOCKED frame from |payload| of length |payloadlen|. + * The result is stored in the object pointed by |dest|. + * STREAM_DATA_BLOCKED frame must start at payload[0]. This function + * finishes when it decodes one STREAM_DATA_BLOCKED frame, and returns + * the exact number of bytes read to decode a frame if it succeeds, or + * one of the following negative error codes: + * + * NGTCP2_ERR_FRAME_ENCODING + * Payload is too short to include STREAM_DATA_BLOCKED frame. + */ +ngtcp2_ssize +ngtcp2_pkt_decode_stream_data_blocked_frame(ngtcp2_stream_data_blocked *dest, + const uint8_t *payload, + size_t payloadlen); + +/* + * ngtcp2_pkt_decode_streams_blocked_frame decodes STREAMS_BLOCKED + * frame from |payload| of length |payloadlen|. The result is stored + * in the object pointed by |dest|. STREAMS_BLOCKED frame must start + * at payload[0]. This function finishes when it decodes one + * STREAMS_BLOCKED frame, and returns the exact number of bytes read + * to decode a frame if it succeeds, or one of the following negative + * error codes: + * + * NGTCP2_ERR_FRAME_ENCODING + * Payload is too short to include STREAMS_BLOCKED frame. + */ +ngtcp2_ssize ngtcp2_pkt_decode_streams_blocked_frame( + ngtcp2_streams_blocked *dest, const uint8_t *payload, size_t payloadlen); + +/* + * ngtcp2_pkt_decode_new_connection_id_frame decodes NEW_CONNECTION_ID + * frame from |payload| of length |payloadlen|. The result is stored + * in the object pointed by |dest|. NEW_CONNECTION_ID frame must + * start at payload[0]. This function finishes when it decodes one + * NEW_CONNECTION_ID frame, and returns the exact number of bytes read + * to decode a frame if it succeeds, or one of the following negative + * error codes: + * + * NGTCP2_ERR_FRAME_ENCODING + * Payload is too short to include NEW_CONNECTION_ID frame; or the + * length of CID is strictly less than NGTCP2_MIN_CIDLEN or + * greater than NGTCP2_MAX_CIDLEN. + */ +ngtcp2_ssize ngtcp2_pkt_decode_new_connection_id_frame( + ngtcp2_new_connection_id *dest, const uint8_t *payload, size_t payloadlen); + +/* + * ngtcp2_pkt_decode_stop_sending_frame decodes STOP_SENDING frame + * from |payload| of length |payloadlen|. The result is stored in the + * object pointed by |dest|. STOP_SENDING frame must start at + * payload[0]. This function finishes when it decodes one + * STOP_SENDING frame, and returns the exact number of bytes read to + * decode a frame if it succeeds, or one of the following negative + * error codes: + * + * NGTCP2_ERR_FRAME_ENCODING + * Payload is too short to include STOP_SENDING frame. + */ +ngtcp2_ssize ngtcp2_pkt_decode_stop_sending_frame(ngtcp2_stop_sending *dest, + const uint8_t *payload, + size_t payloadlen); + +/* + * ngtcp2_pkt_decode_path_challenge_frame decodes PATH_CHALLENGE frame + * from |payload| of length |payloadlen|. The result is stored in the + * object pointed by |dest|. PATH_CHALLENGE frame must start at + * payload[0]. This function finishes when it decodes one + * PATH_CHALLENGE frame, and returns the exact number of bytes read to + * decode a frame if it succeeds, or one of the following negative + * error codes: + * + * NGTCP2_ERR_FRAME_ENCODING + * Payload is too short to include PATH_CHALLENGE frame. + */ +ngtcp2_ssize ngtcp2_pkt_decode_path_challenge_frame(ngtcp2_path_challenge *dest, + const uint8_t *payload, + size_t payloadlen); + +/* + * ngtcp2_pkt_decode_path_response_frame decodes PATH_RESPONSE frame + * from |payload| of length |payloadlen|. The result is stored in the + * object pointed by |dest|. PATH_RESPONSE frame must start at + * payload[0]. This function finishes when it decodes one + * PATH_RESPONSE frame, and returns the exact number of bytes read to + * decode a frame if it succeeds, or one of the following negative + * error codes: + * + * NGTCP2_ERR_FRAME_ENCODING + * Payload is too short to include PATH_RESPONSE frame. + */ +ngtcp2_ssize ngtcp2_pkt_decode_path_response_frame(ngtcp2_path_response *dest, + const uint8_t *payload, + size_t payloadlen); + +/* + * ngtcp2_pkt_decode_crypto_frame decodes CRYPTO frame from |payload| + * of length |payloadlen|. The result is stored in the object pointed + * by |dest|. CRYPTO frame must start at payload[0]. This function + * finishes when it decodes one CRYPTO frame, and returns the exact + * number of bytes read to decode a frame if it succeeds, or one of + * the following negative error codes: + * + * NGTCP2_ERR_FRAME_ENCODING + * Payload is too short to include CRYPTO frame. + */ +ngtcp2_ssize ngtcp2_pkt_decode_crypto_frame(ngtcp2_stream *dest, + const uint8_t *payload, + size_t payloadlen); + +/* + * ngtcp2_pkt_decode_new_token_frame decodes NEW_TOKEN frame from + * |payload| of length |payloadlen|. The result is stored in the + * object pointed by |dest|. NEW_TOKEN frame must start at + * payload[0]. This function finishes when it decodes one NEW_TOKEN + * frame, and returns the exact number of bytes read to decode a frame + * if it succeeds, or one of the following negative error codes: + * + * NGTCP2_ERR_FRAME_ENCODING + * Payload is too short to include NEW_TOKEN frame. + */ +ngtcp2_ssize ngtcp2_pkt_decode_new_token_frame(ngtcp2_new_token *dest, + const uint8_t *payload, + size_t payloadlen); + +/* + * ngtcp2_pkt_decode_retire_connection_id_frame decodes RETIRE_CONNECTION_ID + * frame from |payload| of length |payloadlen|. The result is stored in the + * object pointed by |dest|. RETIRE_CONNECTION_ID frame must start at + * payload[0]. This function finishes when it decodes one RETIRE_CONNECTION_ID + * frame, and returns the exact number of bytes read to decode a frame + * if it succeeds, or one of the following negative error codes: + * + * NGTCP2_ERR_FRAME_ENCODING + * Payload is too short to include RETIRE_CONNECTION_ID frame. + */ +ngtcp2_ssize +ngtcp2_pkt_decode_retire_connection_id_frame(ngtcp2_retire_connection_id *dest, + const uint8_t *payload, + size_t payloadlen); + +/* + * ngtcp2_pkt_decode_handshake_done_frame decodes HANDSHAKE_DONE frame + * from |payload| of length |payloadlen|. The result is stored in the + * object pointed by |dest|. HANDSHAKE_DONE frame must start at + * payload[0]. This function finishes when it decodes one + * HANDSHAKE_DONE frame, and returns the exact number of bytes read to + * decode a frame. + */ +ngtcp2_ssize ngtcp2_pkt_decode_handshake_done_frame(ngtcp2_handshake_done *dest, + const uint8_t *payload, + size_t payloadlen); + +/* + * ngtcp2_pkt_decode_datagram_frame decodes DATAGRAM frame from + * |payload| of length |payloadlen|. The result is stored in the + * object pointed by |dest|. DATAGRAM frame must start at payload[0]. + * This function finishes when it decodes one DATAGRAM frame, and + * returns the exact number of bytes read to decode a frame if it + * succeeds, or one of the following negative error codes: + * + * NGTCP2_ERR_FRAME_ENCODING + * Payload is too short to include DATAGRAM frame. + */ +ngtcp2_ssize ngtcp2_pkt_decode_datagram_frame(ngtcp2_datagram *dest, + const uint8_t *payload, + size_t payloadlen); + +/* + * ngtcp2_pkt_encode_stream_frame encodes STREAM frame |fr| into the + * buffer pointed by |out| of length |outlen|. + * + * This function assigns <the serialized frame type> & + * ~NGTCP2_FRAME_STREAM to fr->flags. + * + * This function returns the number of bytes written if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer does not have enough capacity to write a frame. + */ +ngtcp2_ssize ngtcp2_pkt_encode_stream_frame(uint8_t *out, size_t outlen, + ngtcp2_stream *fr); + +/* + * ngtcp2_pkt_encode_ack_frame encodes ACK frame |fr| into the buffer + * pointed by |out| of length |outlen|. + * + * This function assigns <the serialized frame type> & + * ~NGTCP2_FRAME_ACK to fr->flags. + * + * This function returns the number of bytes written if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer does not have enough capacity to write a frame. + */ +ngtcp2_ssize ngtcp2_pkt_encode_ack_frame(uint8_t *out, size_t outlen, + ngtcp2_ack *fr); + +/* + * ngtcp2_pkt_encode_padding_frame encodes PADDING frame |fr| into the + * buffer pointed by |out| of length |outlen|. + * + * This function encodes consecutive fr->len PADDING frames. + * + * This function returns the number of bytes written if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer does not have enough capacity to write frame(s). + */ +ngtcp2_ssize ngtcp2_pkt_encode_padding_frame(uint8_t *out, size_t outlen, + const ngtcp2_padding *fr); + +/* + * ngtcp2_pkt_encode_reset_stream_frame encodes RESET_STREAM frame + * |fr| into the buffer pointed by |out| of length |buflen|. + * + * This function returns the number of bytes written if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer does not have enough capacity to write a frame. + */ +ngtcp2_ssize +ngtcp2_pkt_encode_reset_stream_frame(uint8_t *out, size_t outlen, + const ngtcp2_reset_stream *fr); + +/* + * ngtcp2_pkt_encode_connection_close_frame encodes CONNECTION_CLOSE + * frame |fr| into the buffer pointed by |out| of length |outlen|. + * + * This function returns the number of bytes written if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer does not have enough capacity to write a frame. + */ +ngtcp2_ssize +ngtcp2_pkt_encode_connection_close_frame(uint8_t *out, size_t outlen, + const ngtcp2_connection_close *fr); + +/* + * ngtcp2_pkt_encode_max_data_frame encodes MAX_DATA frame |fr| into + * the buffer pointed by |out| of length |outlen|. + * + * This function returns the number of bytes written if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer does not have enough capacity to write a frame. + */ +ngtcp2_ssize ngtcp2_pkt_encode_max_data_frame(uint8_t *out, size_t outlen, + const ngtcp2_max_data *fr); + +/* + * ngtcp2_pkt_encode_max_stream_data_frame encodes MAX_STREAM_DATA + * frame |fr| into the buffer pointed by |out| of length |outlen|. + * + * This function returns the number of bytes written if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer does not have enough capacity to write a frame. + */ +ngtcp2_ssize +ngtcp2_pkt_encode_max_stream_data_frame(uint8_t *out, size_t outlen, + const ngtcp2_max_stream_data *fr); + +/* + * ngtcp2_pkt_encode_max_streams_frame encodes MAX_STREAMS + * frame |fr| into the buffer pointed by |out| of length |outlen|. + * + * This function returns the number of bytes written if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer does not have enough capacity to write a frame. + */ +ngtcp2_ssize ngtcp2_pkt_encode_max_streams_frame(uint8_t *out, size_t outlen, + const ngtcp2_max_streams *fr); + +/* + * ngtcp2_pkt_encode_ping_frame encodes PING frame |fr| into the + * buffer pointed by |out| of length |outlen|. + * + * This function returns the number of bytes written if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer does not have enough capacity to write a frame. + */ +ngtcp2_ssize ngtcp2_pkt_encode_ping_frame(uint8_t *out, size_t outlen, + const ngtcp2_ping *fr); + +/* + * ngtcp2_pkt_encode_data_blocked_frame encodes DATA_BLOCKED frame + * |fr| into the buffer pointed by |out| of length |outlen|. + * + * This function returns the number of bytes written if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer does not have enough capacity to write a frame. + */ +ngtcp2_ssize +ngtcp2_pkt_encode_data_blocked_frame(uint8_t *out, size_t outlen, + const ngtcp2_data_blocked *fr); + +/* + * ngtcp2_pkt_encode_stream_data_blocked_frame encodes + * STREAM_DATA_BLOCKED frame |fr| into the buffer pointed by |out| of + * length |outlen|. + * + * This function returns the number of bytes written if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer does not have enough capacity to write a frame. + */ +ngtcp2_ssize ngtcp2_pkt_encode_stream_data_blocked_frame( + uint8_t *out, size_t outlen, const ngtcp2_stream_data_blocked *fr); + +/* + * ngtcp2_pkt_encode_streams_blocked_frame encodes STREAMS_BLOCKED + * frame |fr| into the buffer pointed by |out| of length |outlen|. + * + * This function returns the number of bytes written if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer does not have enough capacity to write a frame. + */ +ngtcp2_ssize +ngtcp2_pkt_encode_streams_blocked_frame(uint8_t *out, size_t outlen, + const ngtcp2_streams_blocked *fr); + +/* + * ngtcp2_pkt_encode_new_connection_id_frame encodes NEW_CONNECTION_ID + * frame |fr| into the buffer pointed by |out| of length |outlen|. + * + * This function returns the number of bytes written if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer does not have enough capacity to write a frame. + */ +ngtcp2_ssize +ngtcp2_pkt_encode_new_connection_id_frame(uint8_t *out, size_t outlen, + const ngtcp2_new_connection_id *fr); + +/* + * ngtcp2_pkt_encode_stop_sending_frame encodes STOP_SENDING frame + * |fr| into the buffer pointed by |out| of length |outlen|. + * + * This function returns the number of bytes written if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer does not have enough capacity to write a frame. + */ +ngtcp2_ssize +ngtcp2_pkt_encode_stop_sending_frame(uint8_t *out, size_t outlen, + const ngtcp2_stop_sending *fr); + +/* + * ngtcp2_pkt_encode_path_challenge_frame encodes PATH_CHALLENGE frame + * |fr| into the buffer pointed by |out| of length |outlen|. + * + * This function returns the number of bytes written if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer does not have enough capacity to write a frame. + */ +ngtcp2_ssize +ngtcp2_pkt_encode_path_challenge_frame(uint8_t *out, size_t outlen, + const ngtcp2_path_challenge *fr); + +/* + * ngtcp2_pkt_encode_path_response_frame encodes PATH_RESPONSE frame + * |fr| into the buffer pointed by |out| of length |outlen|. + * + * This function returns the number of bytes written if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer does not have enough capacity to write a frame. + */ +ngtcp2_ssize +ngtcp2_pkt_encode_path_response_frame(uint8_t *out, size_t outlen, + const ngtcp2_path_response *fr); + +/* + * ngtcp2_pkt_encode_crypto_frame encodes CRYPTO frame |fr| into the + * buffer pointed by |out| of length |outlen|. + * + * This function returns the number of bytes written if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer does not have enough capacity to write a frame. + */ +ngtcp2_ssize ngtcp2_pkt_encode_crypto_frame(uint8_t *out, size_t outlen, + const ngtcp2_stream *fr); + +/* + * ngtcp2_pkt_encode_new_token_frame encodes NEW_TOKEN frame |fr| into + * the buffer pointed by |out| of length |outlen|. + * + * This function returns the number of bytes written if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer does not have enough capacity to write a frame. + */ +ngtcp2_ssize ngtcp2_pkt_encode_new_token_frame(uint8_t *out, size_t outlen, + const ngtcp2_new_token *fr); + +/* + * ngtcp2_pkt_encode_retire_connection_id_frame encodes RETIRE_CONNECTION_ID + * frame |fr| into the buffer pointed by |out| of length |outlen|. + * + * This function returns the number of bytes written if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer does not have enough capacity to write a frame. + */ +ngtcp2_ssize ngtcp2_pkt_encode_retire_connection_id_frame( + uint8_t *out, size_t outlen, const ngtcp2_retire_connection_id *fr); + +/* + * ngtcp2_pkt_encode_handshake_done_frame encodes HANDSHAKE_DONE frame + * |fr| into the buffer pointed by |out| of length |outlen|. + * + * This function returns the number of bytes written if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer does not have enough capacity to write a frame. + */ +ngtcp2_ssize +ngtcp2_pkt_encode_handshake_done_frame(uint8_t *out, size_t outlen, + const ngtcp2_handshake_done *fr); + +/* + * ngtcp2_pkt_encode_datagram_frame encodes DATAGRAM frame |fr| into + * the buffer pointed by |out| of length |outlen|. + * + * This function returns the number of bytes written if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer does not have enough capacity to write a frame. + */ +ngtcp2_ssize ngtcp2_pkt_encode_datagram_frame(uint8_t *out, size_t outlen, + const ngtcp2_datagram *fr); + +/* + * ngtcp2_pkt_adjust_pkt_num find the full 64 bits packet number for + * |pkt_num|, which is encoded in |pkt_numlen| bytes. The + * |max_pkt_num| is the highest successfully authenticated packet + * number. + */ +int64_t ngtcp2_pkt_adjust_pkt_num(int64_t max_pkt_num, int64_t pkt_num, + size_t pkt_numlen); + +/* + * ngtcp2_pkt_validate_ack checks that ack is malformed or not. + * |min_pkt_num| is the minimum packet number that an endpoint sends. + * It is an error to receive acknowledgements for a packet less than + * |min_pkt_num|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_ACK_FRAME + * ACK frame is malformed + * NGTCP2_ERR_PROTO + * |fr| contains a packet number less than |min_pkt_num|. + */ +int ngtcp2_pkt_validate_ack(ngtcp2_ack *fr, int64_t min_pkt_num); + +/* + * ngtcp2_pkt_stream_max_datalen returns the maximum number of bytes + * which can be sent for stream denoted by |stream_id|. |offset| is + * an offset of within the stream. |len| is the estimated number of + * bytes to be sent. |left| is the size of buffer. If |left| is too + * small to write STREAM frame, this function returns (size_t)-1. + */ +size_t ngtcp2_pkt_stream_max_datalen(int64_t stream_id, uint64_t offset, + uint64_t len, size_t left); + +/* + * ngtcp2_pkt_crypto_max_datalen returns the maximum number of bytes + * which can be sent for crypto stream. |offset| is an offset of + * within the crypto stream. |len| is the estimated number of bytes + * to be sent. |left| is the size of buffer. If |left| is too small + * to write CRYPTO frame, this function returns (size_t)-1. + */ +size_t ngtcp2_pkt_crypto_max_datalen(uint64_t offset, size_t len, size_t left); + +/* + * ngtcp2_pkt_datagram_framelen returns the length of DATAGRAM frame + * to encode |len| bytes of data. + */ +size_t ngtcp2_pkt_datagram_framelen(size_t len); + +/* + * ngtcp2_pkt_verify_reserved_bits verifies that the first byte |c| of + * the packet header has the correct reserved bits. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_PROTO + * Reserved bits has wrong value. + */ +int ngtcp2_pkt_verify_reserved_bits(uint8_t c); + +/* + * ngtcp2_pkt_encode_pseudo_retry encodes Retry pseudo-packet in the + * buffer pointed by |dest| of length |destlen|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_BUF + * Buffer is too short. + */ +ngtcp2_ssize ngtcp2_pkt_encode_pseudo_retry( + uint8_t *dest, size_t destlen, const ngtcp2_pkt_hd *hd, uint8_t unused, + const ngtcp2_cid *odcid, const uint8_t *token, size_t tokenlen); + +/* + * ngtcp2_pkt_verify_retry_tag verifies Retry packet. The buffer + * pointed by |pkt| of length |pktlen| must contain Retry packet + * including packet header. The odcid and tag fields of |retry| must + * be specified. |aead| must be AEAD_AES_128_GCM. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_PROTO + * Verification failed. + */ +int ngtcp2_pkt_verify_retry_tag(uint32_t version, const ngtcp2_pkt_retry *retry, + const uint8_t *pkt, size_t pktlen, + ngtcp2_encrypt encrypt, + const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx); + +/* + * ngtcp2_pkt_versioned_type returns versioned packet type for a + * version |version| that corresponds to the version-independent + * |pkt_type|. + */ +uint8_t ngtcp2_pkt_versioned_type(uint32_t version, uint32_t pkt_type); + +/** + * @function + * + * `ngtcp2_pkt_get_type_long` returns the version-independent long + * packet type. |version| is the QUIC version. |c| is the first byte + * of Long packet header. If |version| is not supported by the + * library, it returns 0. + */ +uint8_t ngtcp2_pkt_get_type_long(uint32_t version, uint8_t c); + +#endif /* NGTCP2_PKT_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_pktns_id.h b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_pktns_id.h new file mode 100644 index 0000000..66b0ee9 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_pktns_id.h @@ -0,0 +1,62 @@ +/* + * ngtcp2 + * + * Copyright (c) 2023 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_PKTNS_ID_H +#define NGTCP2_PKTNS_ID_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +/** + * @enum + * + * :type:`ngtcp2_pktns_id` defines packet number space identifier. + */ +typedef enum ngtcp2_pktns_id { + /** + * :enum:`NGTCP2_PKTNS_ID_INITIAL` is the Initial packet number + * space. + */ + NGTCP2_PKTNS_ID_INITIAL, + /** + * :enum:`NGTCP2_PKTNS_ID_HANDSHAKE` is the Handshake packet number + * space. + */ + NGTCP2_PKTNS_ID_HANDSHAKE, + /** + * :enum:`NGTCP2_PKTNS_ID_APPLICATION` is the Application data + * packet number space. + */ + NGTCP2_PKTNS_ID_APPLICATION, + /** + * :enum:`NGTCP2_PKTNS_ID_MAX` is defined to get the number of + * packet number spaces. + */ + NGTCP2_PKTNS_ID_MAX +} ngtcp2_pktns_id; + +#endif /* NGTCP2_PKTNS_ID_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_pmtud.c b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_pmtud.c new file mode 100644 index 0000000..771ef5e --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_pmtud.c @@ -0,0 +1,160 @@ +/* + * ngtcp2 + * + * Copyright (c) 2022 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_pmtud.h" + +#include <assert.h> + +#include "ngtcp2_mem.h" +#include "ngtcp2_macro.h" + +/* NGTCP2_PMTUD_PROBE_NUM_MAX is the maximum number of packets sent + for each probe. */ +#define NGTCP2_PMTUD_PROBE_NUM_MAX 3 + +static size_t mtu_probes[] = { + 1454 - 48, /* The well known MTU used by a domestic optic fiber + service in Japan. */ + 1390 - 48, /* Typical Tunneled MTU */ + 1280 - 48, /* IPv6 minimum MTU */ + 1492 - 48, /* PPPoE */ +}; + +#define NGTCP2_MTU_PROBESLEN ngtcp2_arraylen(mtu_probes) + +int ngtcp2_pmtud_new(ngtcp2_pmtud **ppmtud, size_t max_udp_payload_size, + size_t hard_max_udp_payload_size, int64_t tx_pkt_num, + const ngtcp2_mem *mem) { + ngtcp2_pmtud *pmtud = ngtcp2_mem_malloc(mem, sizeof(ngtcp2_pmtud)); + + if (pmtud == NULL) { + return NGTCP2_ERR_NOMEM; + } + + pmtud->mem = mem; + pmtud->mtu_idx = 0; + pmtud->num_pkts_sent = 0; + pmtud->expiry = UINT64_MAX; + pmtud->tx_pkt_num = tx_pkt_num; + pmtud->max_udp_payload_size = max_udp_payload_size; + pmtud->hard_max_udp_payload_size = hard_max_udp_payload_size; + pmtud->min_fail_udp_payload_size = SIZE_MAX; + + for (; pmtud->mtu_idx < NGTCP2_MTU_PROBESLEN; ++pmtud->mtu_idx) { + if (mtu_probes[pmtud->mtu_idx] > pmtud->hard_max_udp_payload_size) { + continue; + } + if (mtu_probes[pmtud->mtu_idx] > pmtud->max_udp_payload_size) { + break; + } + } + + *ppmtud = pmtud; + + return 0; +} + +void ngtcp2_pmtud_del(ngtcp2_pmtud *pmtud) { + if (!pmtud) { + return; + } + + ngtcp2_mem_free(pmtud->mem, pmtud); +} + +size_t ngtcp2_pmtud_probelen(ngtcp2_pmtud *pmtud) { + assert(pmtud->mtu_idx < NGTCP2_MTU_PROBESLEN); + + return mtu_probes[pmtud->mtu_idx]; +} + +void ngtcp2_pmtud_probe_sent(ngtcp2_pmtud *pmtud, ngtcp2_duration pto, + ngtcp2_tstamp ts) { + ngtcp2_tstamp timeout; + + if (++pmtud->num_pkts_sent < NGTCP2_PMTUD_PROBE_NUM_MAX) { + timeout = pto; + } else { + timeout = 3 * pto; + } + + pmtud->expiry = ts + timeout; +} + +int ngtcp2_pmtud_require_probe(ngtcp2_pmtud *pmtud) { + return pmtud->expiry == UINT64_MAX; +} + +static void pmtud_next_probe(ngtcp2_pmtud *pmtud) { + assert(pmtud->mtu_idx < NGTCP2_MTU_PROBESLEN); + + ++pmtud->mtu_idx; + pmtud->num_pkts_sent = 0; + pmtud->expiry = UINT64_MAX; + + for (; pmtud->mtu_idx < NGTCP2_MTU_PROBESLEN; ++pmtud->mtu_idx) { + if (mtu_probes[pmtud->mtu_idx] <= pmtud->max_udp_payload_size || + mtu_probes[pmtud->mtu_idx] > pmtud->hard_max_udp_payload_size) { + continue; + } + + if (mtu_probes[pmtud->mtu_idx] < pmtud->min_fail_udp_payload_size) { + break; + } + } +} + +void ngtcp2_pmtud_probe_success(ngtcp2_pmtud *pmtud, size_t payloadlen) { + pmtud->max_udp_payload_size = + ngtcp2_max(pmtud->max_udp_payload_size, payloadlen); + + assert(pmtud->mtu_idx < NGTCP2_MTU_PROBESLEN); + + if (mtu_probes[pmtud->mtu_idx] > pmtud->max_udp_payload_size) { + return; + } + + pmtud_next_probe(pmtud); +} + +void ngtcp2_pmtud_handle_expiry(ngtcp2_pmtud *pmtud, ngtcp2_tstamp ts) { + if (ts < pmtud->expiry) { + return; + } + + pmtud->expiry = UINT64_MAX; + + if (pmtud->num_pkts_sent < NGTCP2_PMTUD_PROBE_NUM_MAX) { + return; + } + + pmtud->min_fail_udp_payload_size = + ngtcp2_min(pmtud->min_fail_udp_payload_size, mtu_probes[pmtud->mtu_idx]); + + pmtud_next_probe(pmtud); +} + +int ngtcp2_pmtud_finished(ngtcp2_pmtud *pmtud) { + return pmtud->mtu_idx >= NGTCP2_MTU_PROBESLEN; +} diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_pmtud.h b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_pmtud.h new file mode 100644 index 0000000..6b2e691 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_pmtud.h @@ -0,0 +1,123 @@ +/* + * ngtcp2 + * + * Copyright (c) 2022 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_PMTUD_H +#define NGTCP2_PMTUD_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +typedef struct ngtcp2_pmtud { + const ngtcp2_mem *mem; + /* mtu_idx is the index of UDP payload size candidates to try + out. */ + size_t mtu_idx; + /* num_pkts_sent is the number of mtu_idx sized UDP datagram payload + sent */ + size_t num_pkts_sent; + /* expiry is the expired, if it is reached, send out the next UDP + datagram. UINT64_MAX means no expiry, or expiration is canceled. + In either case, new probe packet should be sent unless we have + done all attempts. */ + ngtcp2_tstamp expiry; + /* tx_pkt_num is the smallest outgoing packet number where the + current discovery is performed. In other words, acknowledging + packet whose packet number lower than that does not indicate the + success of Path MTU Discovery. */ + int64_t tx_pkt_num; + /* max_udp_payload_size is the maximum UDP payload size which is + known to work. */ + size_t max_udp_payload_size; + /* hard_max_udp_payload_size is the maximum UDP payload size that is + going to be probed. */ + size_t hard_max_udp_payload_size; + /* min_fail_udp_payload_size is the minimum UDP payload size that is + known to fail. */ + size_t min_fail_udp_payload_size; +} ngtcp2_pmtud; + +/* + * ngtcp2_pmtud_new creates new ngtcp2_pmtud object, and assigns its + * pointer to |*ppmtud|. |max_udp_payload_size| is the maximum UDP + * payload size that is known to work for the current path. + * |tx_pkt_num| should be the next packet number to send, which is + * used to differentiate the PMTUD probe packet sent by the previous + * PMTUD. PMTUD might finish immediately if |max_udp_payload_size| is + * larger than or equal to all UDP payload probe candidates. + * Therefore, call ngtcp2_pmtud_finished to check this situation. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +int ngtcp2_pmtud_new(ngtcp2_pmtud **ppmtud, size_t max_udp_payload_size, + size_t hard_max_udp_payload_size, int64_t tx_pkt_num, + const ngtcp2_mem *mem); + +/* + * ngtcp2_pmtud_del deletes |pmtud|. + */ +void ngtcp2_pmtud_del(ngtcp2_pmtud *pmtud); + +/* + * ngtcp2_pmtud_probelen returns the length of UDP payload size for a + * PMTUD probe packet. + */ +size_t ngtcp2_pmtud_probelen(ngtcp2_pmtud *pmtud); + +/* + * ngtcp2_pmtud_probe_sent should be invoked when a PMTUD probe packet is + * sent. + */ +void ngtcp2_pmtud_probe_sent(ngtcp2_pmtud *pmtud, ngtcp2_duration pto, + ngtcp2_tstamp ts); + +/* + * ngtcp2_pmtud_require_probe returns nonzero if a PMTUD probe packet + * should be sent. + */ +int ngtcp2_pmtud_require_probe(ngtcp2_pmtud *pmtud); + +/* + * ngtcp2_pmtud_probe_success should be invoked when a PMTUD probe + * UDP datagram sized |payloadlen| is acknowledged. + */ +void ngtcp2_pmtud_probe_success(ngtcp2_pmtud *pmtud, size_t payloadlen); + +/* + * ngtcp2_pmtud_handle_expiry handles expiry. + */ +void ngtcp2_pmtud_handle_expiry(ngtcp2_pmtud *pmtud, ngtcp2_tstamp ts); + +/* + * ngtcp2_pmtud_finished returns nonzero if PMTUD has finished. + */ +int ngtcp2_pmtud_finished(ngtcp2_pmtud *pmtud); + +#endif /* NGTCP2_PMTUD_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_ppe.c b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_ppe.c new file mode 100644 index 0000000..f7c122b --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_ppe.c @@ -0,0 +1,236 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 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_ppe.h" + +#include <string.h> +#include <assert.h> + +#include "ngtcp2_str.h" +#include "ngtcp2_conv.h" + +void ngtcp2_ppe_init(ngtcp2_ppe *ppe, uint8_t *out, size_t outlen, + ngtcp2_crypto_cc *cc) { + ngtcp2_buf_init(&ppe->buf, out, outlen); + + ppe->hdlen = 0; + ppe->len_offset = 0; + ppe->pkt_num_offset = 0; + ppe->pkt_numlen = 0; + ppe->pkt_num = 0; + ppe->cc = cc; +} + +int ngtcp2_ppe_encode_hd(ngtcp2_ppe *ppe, const ngtcp2_pkt_hd *hd) { + ngtcp2_ssize rv; + ngtcp2_buf *buf = &ppe->buf; + ngtcp2_crypto_cc *cc = ppe->cc; + + if (ngtcp2_buf_left(buf) < cc->aead.max_overhead) { + return NGTCP2_ERR_NOBUF; + } + + if (hd->flags & NGTCP2_PKT_FLAG_LONG_FORM) { + ppe->len_offset = 1 + 4 + 1 + hd->dcid.datalen + 1 + hd->scid.datalen; + if (hd->type == NGTCP2_PKT_INITIAL) { + ppe->len_offset += ngtcp2_put_uvarintlen(hd->tokenlen) + hd->tokenlen; + } + ppe->pkt_num_offset = ppe->len_offset + NGTCP2_PKT_LENGTHLEN; + rv = ngtcp2_pkt_encode_hd_long( + buf->last, ngtcp2_buf_left(buf) - cc->aead.max_overhead, hd); + } else { + ppe->pkt_num_offset = 1 + hd->dcid.datalen; + rv = ngtcp2_pkt_encode_hd_short( + buf->last, ngtcp2_buf_left(buf) - cc->aead.max_overhead, hd); + } + if (rv < 0) { + return (int)rv; + } + + buf->last += rv; + + ppe->pkt_numlen = hd->pkt_numlen; + ppe->hdlen = (size_t)rv; + + ppe->pkt_num = hd->pkt_num; + + return 0; +} + +int ngtcp2_ppe_encode_frame(ngtcp2_ppe *ppe, ngtcp2_frame *fr) { + ngtcp2_ssize rv; + ngtcp2_buf *buf = &ppe->buf; + ngtcp2_crypto_cc *cc = ppe->cc; + + if (ngtcp2_buf_left(buf) < cc->aead.max_overhead) { + return NGTCP2_ERR_NOBUF; + } + + rv = ngtcp2_pkt_encode_frame( + buf->last, ngtcp2_buf_left(buf) - cc->aead.max_overhead, fr); + if (rv < 0) { + return (int)rv; + } + + buf->last += rv; + + return 0; +} + +/* + * ppe_sample_offset returns the offset to sample for packet number + * encryption. + */ +static size_t ppe_sample_offset(ngtcp2_ppe *ppe) { + return ppe->pkt_num_offset + 4; +} + +ngtcp2_ssize ngtcp2_ppe_final(ngtcp2_ppe *ppe, const uint8_t **ppkt) { + ngtcp2_buf *buf = &ppe->buf; + ngtcp2_crypto_cc *cc = ppe->cc; + uint8_t *payload = buf->begin + ppe->hdlen; + size_t payloadlen = ngtcp2_buf_len(buf) - ppe->hdlen; + uint8_t mask[NGTCP2_HP_SAMPLELEN]; + uint8_t *p; + size_t i; + int rv; + + assert(cc->encrypt); + assert(cc->hp_mask); + + if (ppe->len_offset) { + ngtcp2_put_uvarint30( + buf->begin + ppe->len_offset, + (uint16_t)(payloadlen + ppe->pkt_numlen + cc->aead.max_overhead)); + } + + ngtcp2_crypto_create_nonce(ppe->nonce, cc->ckm->iv.base, cc->ckm->iv.len, + ppe->pkt_num); + + rv = cc->encrypt(payload, &cc->aead, &cc->ckm->aead_ctx, payload, payloadlen, + ppe->nonce, cc->ckm->iv.len, buf->begin, ppe->hdlen); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + buf->last = payload + payloadlen + cc->aead.max_overhead; + + /* TODO Check that we have enough space to get sample */ + assert(ppe_sample_offset(ppe) + NGTCP2_HP_SAMPLELEN <= ngtcp2_buf_len(buf)); + + rv = cc->hp_mask(mask, &cc->hp, &cc->hp_ctx, + buf->begin + ppe_sample_offset(ppe)); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + p = buf->begin; + if (*p & NGTCP2_HEADER_FORM_BIT) { + *p = (uint8_t)(*p ^ (mask[0] & 0x0f)); + } else { + *p = (uint8_t)(*p ^ (mask[0] & 0x1f)); + } + + p = buf->begin + ppe->pkt_num_offset; + for (i = 0; i < ppe->pkt_numlen; ++i) { + *(p + i) ^= mask[i + 1]; + } + + if (ppkt != NULL) { + *ppkt = buf->begin; + } + + return (ngtcp2_ssize)ngtcp2_buf_len(buf); +} + +size_t ngtcp2_ppe_left(ngtcp2_ppe *ppe) { + ngtcp2_crypto_cc *cc = ppe->cc; + + if (ngtcp2_buf_left(&ppe->buf) < cc->aead.max_overhead) { + return 0; + } + + return ngtcp2_buf_left(&ppe->buf) - cc->aead.max_overhead; +} + +size_t ngtcp2_ppe_pktlen(ngtcp2_ppe *ppe) { + ngtcp2_crypto_cc *cc = ppe->cc; + + return ngtcp2_buf_len(&ppe->buf) + cc->aead.max_overhead; +} + +size_t ngtcp2_ppe_padding(ngtcp2_ppe *ppe) { + ngtcp2_crypto_cc *cc = ppe->cc; + ngtcp2_buf *buf = &ppe->buf; + size_t len; + + assert(ngtcp2_buf_left(buf) >= cc->aead.max_overhead); + + len = ngtcp2_buf_left(buf) - cc->aead.max_overhead; + memset(buf->last, 0, len); + buf->last += len; + + return len; +} + +size_t ngtcp2_ppe_padding_hp_sample(ngtcp2_ppe *ppe) { + ngtcp2_crypto_cc *cc = ppe->cc; + ngtcp2_buf *buf = &ppe->buf; + size_t max_samplelen; + size_t len = 0; + + assert(cc->aead.max_overhead); + + max_samplelen = + ngtcp2_buf_len(buf) + cc->aead.max_overhead - ppe_sample_offset(ppe); + if (max_samplelen < NGTCP2_HP_SAMPLELEN) { + len = NGTCP2_HP_SAMPLELEN - max_samplelen; + assert(ngtcp2_ppe_left(ppe) >= len); + memset(buf->last, 0, len); + buf->last += len; + } + + return len; +} + +size_t ngtcp2_ppe_padding_size(ngtcp2_ppe *ppe, size_t n) { + ngtcp2_crypto_cc *cc = ppe->cc; + ngtcp2_buf *buf = &ppe->buf; + size_t pktlen = ngtcp2_buf_len(buf) + cc->aead.max_overhead; + size_t len; + + if (pktlen >= n) { + return 0; + } + + len = n - pktlen; + buf->last = ngtcp2_setmem(buf->last, 0, len); + + return len; +} + +int ngtcp2_ppe_ensure_hp_sample(ngtcp2_ppe *ppe) { + ngtcp2_buf *buf = &ppe->buf; + return ngtcp2_buf_left(buf) >= (4 - ppe->pkt_numlen) + NGTCP2_HP_SAMPLELEN; +} diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_ppe.h b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_ppe.h new file mode 100644 index 0000000..2a069ef --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_ppe.h @@ -0,0 +1,150 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 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_PPE_H +#define NGTCP2_PPE_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_pkt.h" +#include "ngtcp2_buf.h" +#include "ngtcp2_crypto.h" + +/* + * ngtcp2_ppe is the Protected Packet Encoder. + */ +typedef struct ngtcp2_ppe { + ngtcp2_buf buf; + ngtcp2_crypto_cc *cc; + /* hdlen is the number of bytes for packet header written in buf. */ + size_t hdlen; + /* len_offset is the offset to Length field. */ + size_t len_offset; + /* pkt_num_offset is the offset to packet number field. */ + size_t pkt_num_offset; + /* pkt_numlen is the number of bytes used to encode a packet + number */ + size_t pkt_numlen; + /* pkt_num is the packet number written in buf. */ + int64_t pkt_num; + /* nonce is the buffer to store nonce. It should be equal or longer + than then length of IV. */ + uint8_t nonce[32]; +} ngtcp2_ppe; + +/* + * ngtcp2_ppe_init initializes |ppe| with the given buffer. + */ +void ngtcp2_ppe_init(ngtcp2_ppe *ppe, uint8_t *out, size_t outlen, + ngtcp2_crypto_cc *cc); + +/* + * ngtcp2_ppe_encode_hd encodes |hd|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOBUF + * The buffer is too small. + */ +int ngtcp2_ppe_encode_hd(ngtcp2_ppe *ppe, const ngtcp2_pkt_hd *hd); + +/* + * ngtcp2_ppe_encode_frame encodes |fr|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOBUF + * The buffer is too small. + */ +int ngtcp2_ppe_encode_frame(ngtcp2_ppe *ppe, ngtcp2_frame *fr); + +/* + * ngtcp2_ppe_final encrypts QUIC packet payload. If |**ppkt| is not + * NULL, the pointer to the packet is assigned to it. + * + * This function returns the length of QUIC packet, including header, + * and payload if it succeeds, or one of the following negative error + * codes: + * + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + */ +ngtcp2_ssize ngtcp2_ppe_final(ngtcp2_ppe *ppe, const uint8_t **ppkt); + +/* + * ngtcp2_ppe_left returns the number of bytes left to write + * additional frames. This does not count AEAD overhead. + */ +size_t ngtcp2_ppe_left(ngtcp2_ppe *ppe); + +/* + * ngtcp2_ppe_pktlen returns the provisional packet length. It + * includes AEAD overhead. + */ +size_t ngtcp2_ppe_pktlen(ngtcp2_ppe *ppe); + +/** + * @function + * + * `ngtcp2_ppe_padding` encodes PADDING frames to the end of the + * buffer. This function returns the number of bytes padded. + */ +size_t ngtcp2_ppe_padding(ngtcp2_ppe *ppe); + +/* + * ngtcp2_ppe_padding_hp_sample adds PADDING frame if the current + * payload does not have enough space for header protection sample. + * This function should be called just before calling + * ngtcp2_ppe_final(). + * + * This function returns the number of bytes added as padding. + */ +size_t ngtcp2_ppe_padding_hp_sample(ngtcp2_ppe *ppe); + +/* + * ngtcp2_ppe_padding_size adds PADDING frame so that the size of QUIC + * packet is at least |n| bytes long. If it is unable to add PADDING + * in that way, this function still adds PADDING frame as much as + * possible. This function should be called just before calling + * ngtcp2_ppe_final(). For Short packet, this function should be + * called instead of ngtcp2_ppe_padding_hp_sample. + * + * This function returns the number of bytes added as padding. + */ +size_t ngtcp2_ppe_padding_size(ngtcp2_ppe *ppe, size_t n); + +/* + * ngtcp2_ppe_ensure_hp_sample returns nonzero if the buffer has + * enough space for header protection sample. This should be called + * right after packet header is written. + */ +int ngtcp2_ppe_ensure_hp_sample(ngtcp2_ppe *ppe); + +#endif /* NGTCP2_PPE_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_pq.c b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_pq.c new file mode 100644 index 0000000..5e1003d --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_pq.c @@ -0,0 +1,164 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * Copyright (c) 2012 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_pq.h" + +#include <assert.h> + +#include "ngtcp2_macro.h" + +void ngtcp2_pq_init(ngtcp2_pq *pq, ngtcp2_less less, const ngtcp2_mem *mem) { + pq->mem = mem; + pq->capacity = 0; + pq->q = NULL; + pq->length = 0; + pq->less = less; +} + +void ngtcp2_pq_free(ngtcp2_pq *pq) { + ngtcp2_mem_free(pq->mem, pq->q); + pq->q = NULL; +} + +static void swap(ngtcp2_pq *pq, size_t i, size_t j) { + ngtcp2_pq_entry *a = pq->q[i]; + ngtcp2_pq_entry *b = pq->q[j]; + + pq->q[i] = b; + b->index = i; + pq->q[j] = a; + a->index = j; +} + +static void bubble_up(ngtcp2_pq *pq, size_t index) { + size_t parent; + while (index != 0) { + parent = (index - 1) / 2; + if (!pq->less(pq->q[index], pq->q[parent])) { + return; + } + swap(pq, parent, index); + index = parent; + } +} + +int ngtcp2_pq_push(ngtcp2_pq *pq, ngtcp2_pq_entry *item) { + if (pq->capacity <= pq->length) { + void *nq; + size_t ncapacity; + + ncapacity = ngtcp2_max(4, (pq->capacity * 2)); + + nq = ngtcp2_mem_realloc(pq->mem, pq->q, + ncapacity * sizeof(ngtcp2_pq_entry *)); + if (nq == NULL) { + return NGTCP2_ERR_NOMEM; + } + pq->capacity = ncapacity; + pq->q = nq; + } + pq->q[pq->length] = item; + item->index = pq->length; + ++pq->length; + bubble_up(pq, pq->length - 1); + return 0; +} + +ngtcp2_pq_entry *ngtcp2_pq_top(ngtcp2_pq *pq) { + assert(pq->length); + return pq->q[0]; +} + +static void bubble_down(ngtcp2_pq *pq, size_t index) { + size_t i, j, minindex; + for (;;) { + j = index * 2 + 1; + minindex = index; + for (i = 0; i < 2; ++i, ++j) { + if (j >= pq->length) { + break; + } + if (pq->less(pq->q[j], pq->q[minindex])) { + minindex = j; + } + } + if (minindex == index) { + return; + } + swap(pq, index, minindex); + index = minindex; + } +} + +void ngtcp2_pq_pop(ngtcp2_pq *pq) { + if (pq->length > 0) { + pq->q[0] = pq->q[pq->length - 1]; + pq->q[0]->index = 0; + --pq->length; + bubble_down(pq, 0); + } +} + +void ngtcp2_pq_remove(ngtcp2_pq *pq, ngtcp2_pq_entry *item) { + assert(pq->q[item->index] == item); + + if (item->index == 0) { + ngtcp2_pq_pop(pq); + return; + } + + if (item->index == pq->length - 1) { + --pq->length; + return; + } + + pq->q[item->index] = pq->q[pq->length - 1]; + pq->q[item->index]->index = item->index; + --pq->length; + + if (pq->less(item, pq->q[item->index])) { + bubble_down(pq, item->index); + } else { + bubble_up(pq, item->index); + } +} + +int ngtcp2_pq_empty(ngtcp2_pq *pq) { return pq->length == 0; } + +size_t ngtcp2_pq_size(ngtcp2_pq *pq) { return pq->length; } + +int ngtcp2_pq_each(ngtcp2_pq *pq, ngtcp2_pq_item_cb fun, void *arg) { + size_t i; + + if (pq->length == 0) { + return 0; + } + for (i = 0; i < pq->length; ++i) { + if ((*fun)(pq->q[i], arg)) { + return 1; + } + } + return 0; +} diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_pq.h b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_pq.h new file mode 100644 index 0000000..484c8f2 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_pq.h @@ -0,0 +1,126 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * Copyright (c) 2012 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_PQ_H +#define NGTCP2_PQ_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_mem.h" + +/* Implementation of priority queue */ + +/* NGTCP2_PQ_BAD_INDEX is the priority queue index which indicates + that an entry is not queued. Assigning this value to + ngtcp2_pq_entry.index can check that the entry is queued or not. */ +#define NGTCP2_PQ_BAD_INDEX SIZE_MAX + +typedef struct ngtcp2_pq_entry { + size_t index; +} ngtcp2_pq_entry; + +/* "less" function, return nonzero if |lhs| is less than |rhs|. */ +typedef int (*ngtcp2_less)(const ngtcp2_pq_entry *lhs, + const ngtcp2_pq_entry *rhs); + +typedef struct ngtcp2_pq { + /* The pointer to the pointer to the item stored */ + ngtcp2_pq_entry **q; + /* Memory allocator */ + const ngtcp2_mem *mem; + /* The number of items stored */ + size_t length; + /* The maximum number of items this pq can store. This is + automatically extended when length is reached to this value. */ + size_t capacity; + /* The less function between items */ + ngtcp2_less less; +} ngtcp2_pq; + +/* + * Initializes priority queue |pq| with compare function |cmp|. + */ +void ngtcp2_pq_init(ngtcp2_pq *pq, ngtcp2_less less, const ngtcp2_mem *mem); + +/* + * Deallocates any resources allocated for |pq|. The stored items are + * not freed by this function. + */ +void ngtcp2_pq_free(ngtcp2_pq *pq); + +/* + * Adds |item| to the priority queue |pq|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +int ngtcp2_pq_push(ngtcp2_pq *pq, ngtcp2_pq_entry *item); + +/* + * Returns item at the top of the queue |pq|. It is undefined if the + * queue is empty. + */ +ngtcp2_pq_entry *ngtcp2_pq_top(ngtcp2_pq *pq); + +/* + * Pops item at the top of the queue |pq|. The popped item is not + * freed by this function. + */ +void ngtcp2_pq_pop(ngtcp2_pq *pq); + +/* + * Returns nonzero if the queue |pq| is empty. + */ +int ngtcp2_pq_empty(ngtcp2_pq *pq); + +/* + * Returns the number of items in the queue |pq|. + */ +size_t ngtcp2_pq_size(ngtcp2_pq *pq); + +typedef int (*ngtcp2_pq_item_cb)(ngtcp2_pq_entry *item, void *arg); + +/* + * Applies |fun| to each item in |pq|. The |arg| is passed as arg + * parameter to callback function. This function must not change the + * ordering key. If the return value from callback is nonzero, this + * function returns 1 immediately without iterating remaining items. + * Otherwise this function returns 0. + */ +int ngtcp2_pq_each(ngtcp2_pq *pq, ngtcp2_pq_item_cb fun, void *arg); + +/* + * Removes |item| from priority queue. + */ +void ngtcp2_pq_remove(ngtcp2_pq *pq, ngtcp2_pq_entry *item); + +#endif /* NGTCP2_PQ_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_pv.c b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_pv.c new file mode 100644 index 0000000..314e005 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_pv.c @@ -0,0 +1,172 @@ +/* + * 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. + */ +#include "ngtcp2_pv.h" + +#include <string.h> +#include <assert.h> + +#include "ngtcp2_mem.h" +#include "ngtcp2_log.h" +#include "ngtcp2_macro.h" +#include "ngtcp2_addr.h" + +void ngtcp2_pv_entry_init(ngtcp2_pv_entry *pvent, const uint8_t *data, + ngtcp2_tstamp expiry, uint8_t flags) { + memcpy(pvent->data, data, sizeof(pvent->data)); + pvent->expiry = expiry; + pvent->flags = flags; +} + +int ngtcp2_pv_new(ngtcp2_pv **ppv, const ngtcp2_dcid *dcid, + ngtcp2_duration timeout, uint8_t flags, ngtcp2_log *log, + const ngtcp2_mem *mem) { + (*ppv) = ngtcp2_mem_malloc(mem, sizeof(ngtcp2_pv)); + if (*ppv == NULL) { + return NGTCP2_ERR_NOMEM; + } + + ngtcp2_static_ringbuf_pv_ents_init(&(*ppv)->ents); + + ngtcp2_dcid_copy(&(*ppv)->dcid, dcid); + + (*ppv)->mem = mem; + (*ppv)->log = log; + (*ppv)->timeout = timeout; + (*ppv)->fallback_pto = 0; + (*ppv)->started_ts = UINT64_MAX; + (*ppv)->probe_pkt_left = NGTCP2_PV_NUM_PROBE_PKT; + (*ppv)->round = 0; + (*ppv)->flags = flags; + + return 0; +} + +void ngtcp2_pv_del(ngtcp2_pv *pv) { + if (pv == NULL) { + return; + } + ngtcp2_mem_free(pv->mem, pv); +} + +void ngtcp2_pv_add_entry(ngtcp2_pv *pv, const uint8_t *data, + ngtcp2_tstamp expiry, uint8_t flags, + ngtcp2_tstamp ts) { + ngtcp2_pv_entry *ent; + + assert(pv->probe_pkt_left); + + if (ngtcp2_ringbuf_len(&pv->ents.rb) == 0) { + pv->started_ts = ts; + } + + ent = ngtcp2_ringbuf_push_back(&pv->ents.rb); + ngtcp2_pv_entry_init(ent, data, expiry, flags); + + pv->flags &= (uint8_t)~NGTCP2_PV_FLAG_CANCEL_TIMER; + --pv->probe_pkt_left; +} + +int ngtcp2_pv_validate(ngtcp2_pv *pv, uint8_t *pflags, const uint8_t *data) { + size_t len = ngtcp2_ringbuf_len(&pv->ents.rb); + size_t i; + ngtcp2_pv_entry *ent; + + if (len == 0) { + return NGTCP2_ERR_INVALID_STATE; + } + + for (i = 0; i < len; ++i) { + ent = ngtcp2_ringbuf_get(&pv->ents.rb, i); + if (memcmp(ent->data, data, sizeof(ent->data)) == 0) { + *pflags = ent->flags; + ngtcp2_log_info(pv->log, NGTCP2_LOG_EVENT_PTV, "path has been validated"); + return 0; + } + } + + return NGTCP2_ERR_INVALID_ARGUMENT; +} + +void ngtcp2_pv_handle_entry_expiry(ngtcp2_pv *pv, ngtcp2_tstamp ts) { + ngtcp2_pv_entry *ent; + + if (ngtcp2_ringbuf_len(&pv->ents.rb) == 0) { + return; + } + + ent = ngtcp2_ringbuf_get(&pv->ents.rb, ngtcp2_ringbuf_len(&pv->ents.rb) - 1); + + if (ent->expiry > ts) { + return; + } + + ++pv->round; + pv->probe_pkt_left = NGTCP2_PV_NUM_PROBE_PKT; +} + +int ngtcp2_pv_should_send_probe(ngtcp2_pv *pv) { + return pv->probe_pkt_left > 0; +} + +int ngtcp2_pv_validation_timed_out(ngtcp2_pv *pv, ngtcp2_tstamp ts) { + ngtcp2_tstamp t; + ngtcp2_pv_entry *ent; + + if (pv->started_ts == UINT64_MAX) { + return 0; + } + + assert(ngtcp2_ringbuf_len(&pv->ents.rb)); + + ent = ngtcp2_ringbuf_get(&pv->ents.rb, ngtcp2_ringbuf_len(&pv->ents.rb) - 1); + + t = pv->started_ts + pv->timeout; + t = ngtcp2_max(t, ent->expiry); + + return t <= ts; +} + +ngtcp2_tstamp ngtcp2_pv_next_expiry(ngtcp2_pv *pv) { + ngtcp2_pv_entry *ent; + + if ((pv->flags & NGTCP2_PV_FLAG_CANCEL_TIMER) || + ngtcp2_ringbuf_len(&pv->ents.rb) == 0) { + return UINT64_MAX; + } + + ent = ngtcp2_ringbuf_get(&pv->ents.rb, ngtcp2_ringbuf_len(&pv->ents.rb) - 1); + + return ent->expiry; +} + +void ngtcp2_pv_cancel_expired_timer(ngtcp2_pv *pv, ngtcp2_tstamp ts) { + ngtcp2_tstamp expiry = ngtcp2_pv_next_expiry(pv); + + if (expiry > ts) { + return; + } + + pv->flags |= NGTCP2_PV_FLAG_CANCEL_TIMER; +} diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_pv.h b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_pv.h new file mode 100644 index 0000000..c9da152 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_pv.h @@ -0,0 +1,194 @@ +/* + * 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. + */ +#ifndef NGTCP2_PV_H +#define NGTCP2_PV_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_cid.h" +#include "ngtcp2_ringbuf.h" + +/* NGTCP2_PV_MAX_ENTRIES is the maximum number of entries that + ngtcp2_pv can contain. It must be power of 2. */ +#define NGTCP2_PV_MAX_ENTRIES 8 +/* NGTCP2_PV_NUM_PROBE_PKT is the number of probe packets containing + PATH_CHALLENGE sent at a time. */ +#define NGTCP2_PV_NUM_PROBE_PKT 2 + +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 +/* NGTCP2_PV_ENTRY_FLAG_UNDERSIZED indicates that UDP datagram which + contains PATH_CHALLENGE is undersized (< 1200 bytes) */ +#define NGTCP2_PV_ENTRY_FLAG_UNDERSIZED 0x01u + +typedef struct ngtcp2_pv_entry { + /* expiry is the timestamp when this PATH_CHALLENGE expires. */ + ngtcp2_tstamp expiry; + /* 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_pv_entry; + +void ngtcp2_pv_entry_init(ngtcp2_pv_entry *pvent, const uint8_t *data, + ngtcp2_tstamp expiry, uint8_t flags); + +/* NGTCP2_PV_FLAG_NONE indicates no flag is set. */ +#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 +/* NGTCP2_PV_FLAG_CANCEL_TIMER indicates that the expiry timer is + cancelled. */ +#define NGTCP2_PV_FLAG_CANCEL_TIMER 0x02u +/* NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE indicates that fallback DCID is + available in ngtcp2_pv. If path validation fails, fallback to the + fallback DCID. If path validation succeeds, fallback DCID is + retired if it does not equal to the current DCID. */ +#define NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE 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 + +typedef struct ngtcp2_pv ngtcp2_pv; + +ngtcp2_static_ringbuf_def(pv_ents, NGTCP2_PV_MAX_ENTRIES, + sizeof(ngtcp2_pv_entry)); +/* + * ngtcp2_pv is the context of a single path validation. + */ +struct ngtcp2_pv { + const ngtcp2_mem *mem; + ngtcp2_log *log; + /* dcid is DCID and path this path validation uses. */ + ngtcp2_dcid dcid; + /* fallback_dcid is the usually validated DCID and used as a + fallback if this path validation fails. */ + ngtcp2_dcid fallback_dcid; + /* ents is the ring buffer of ngtcp2_pv_entry */ + ngtcp2_static_ringbuf_pv_ents ents; + /* timeout is the duration within which this path validation should + succeed. */ + ngtcp2_duration timeout; + /* fallback_pto is PTO of fallback connection. */ + ngtcp2_duration fallback_pto; + /* started_ts is the timestamp this path validation starts. */ + ngtcp2_tstamp started_ts; + /* round is the number of times that probe_pkt_left is reset. */ + size_t round; + /* probe_pkt_left is the number of probe packets containing + PATH_CHALLENGE which can be send without waiting for an + expiration of a previous flight. */ + size_t probe_pkt_left; + /* flags is bitwise-OR of zero or more of NGTCP2_PV_FLAG_*. */ + uint8_t flags; +}; + +/* + * ngtcp2_pv_new creates new ngtcp2_pv object and assigns its pointer + * to |*ppv|. This function makes a copy of |dcid|. |timeout| is a + * duration within which this path validation must succeed. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory + */ +int ngtcp2_pv_new(ngtcp2_pv **ppv, const ngtcp2_dcid *dcid, + ngtcp2_duration timeout, uint8_t flags, ngtcp2_log *log, + const ngtcp2_mem *mem); + +/* + * ngtcp2_pv_del deallocates |pv|. This function frees memory |pv| + * points too. + */ +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, + ngtcp2_tstamp expiry, uint8_t flags, ngtcp2_tstamp ts); + +/* + * ngtcp2_pv_full returns nonzero if |pv| is full of ngtcp2_pv_entry. + */ +int ngtcp2_pv_full(ngtcp2_pv *pv); + +/* + * ngtcp2_pv_validate validates that the received |data| matches the + * one of the existing entry. The flag of ngtcp2_pv_entry that + * matches |data| is assigned to |*pflags| if this function succeeds. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_PATH_VALIDATION_FAILED + * path validation has failed and must be abandoned + * NGTCP2_ERR_INVALID_STATE + * |pv| includes no entry + * 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); + +/* + * ngtcp2_pv_handle_entry_expiry checks expiry of existing entries. + */ +void ngtcp2_pv_handle_entry_expiry(ngtcp2_pv *pv, ngtcp2_tstamp ts); + +/* + * ngtcp2_pv_should_send_probe returns nonzero if new entry can be + * added by ngtcp2_pv_add_entry. + */ +int ngtcp2_pv_should_send_probe(ngtcp2_pv *pv); + +/* + * ngtcp2_pv_validation_timed_out returns nonzero if the path + * validation fails because of timeout. + */ +int ngtcp2_pv_validation_timed_out(ngtcp2_pv *pv, ngtcp2_tstamp ts); + +/* + * ngtcp2_pv_next_expiry returns the earliest expiry. + */ +ngtcp2_tstamp ngtcp2_pv_next_expiry(ngtcp2_pv *pv); + +/* + * ngtcp2_pv_cancel_expired_timer cancels the expired timer. + */ +void ngtcp2_pv_cancel_expired_timer(ngtcp2_pv *pv, ngtcp2_tstamp ts); + +#endif /* NGTCP2_PV_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_qlog.c b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_qlog.c new file mode 100644 index 0000000..2767534 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_qlog.c @@ -0,0 +1,1227 @@ +/* + * 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. + */ +#include "ngtcp2_qlog.h" + +#include <assert.h> + +#include "ngtcp2_str.h" +#include "ngtcp2_vec.h" +#include "ngtcp2_conv.h" +#include "ngtcp2_net.h" +#include "ngtcp2_unreachable.h" +#include "ngtcp2_conn_stat.h" + +void ngtcp2_qlog_init(ngtcp2_qlog *qlog, ngtcp2_qlog_write write, + ngtcp2_tstamp ts, void *user_data) { + qlog->write = write; + qlog->ts = qlog->last_ts = ts; + qlog->user_data = user_data; +} + +#define write_verbatim(DEST, S) ngtcp2_cpymem((DEST), (S), sizeof(S) - 1) + +static uint8_t *write_string_impl(uint8_t *p, const uint8_t *data, + size_t datalen) { + *p++ = '"'; + if (datalen) { + p = ngtcp2_cpymem(p, data, datalen); + } + *p++ = '"'; + return p; +} + +#define write_string(DEST, S) \ + write_string_impl((DEST), (const uint8_t *)(S), sizeof(S) - 1) + +#define NGTCP2_LOWER_XDIGITS "0123456789abcdef" + +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++ = '"'; + return p; +} + +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); +} + +static uint8_t *write_duration(uint8_t *p, ngtcp2_duration duration) { + return write_number(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, "false", sizeof("false") - 1); +} + +static uint8_t *write_pair_impl(uint8_t *p, const uint8_t *name, size_t namelen, + const ngtcp2_vec *value) { + p = write_string_impl(p, name, namelen); + *p++ = ':'; + return write_string_impl(p, value->base, value->len); +} + +#define write_pair(DEST, NAME, VALUE) \ + write_pair_impl((DEST), (const uint8_t *)(NAME), sizeof(NAME) - 1, (VALUE)) + +static uint8_t *write_pair_hex_impl(uint8_t *p, const uint8_t *name, + size_t namelen, const uint8_t *value, + size_t valuelen) { + p = write_string_impl(p, name, namelen); + *p++ = ':'; + return write_hex(p, value, valuelen); +} + +#define write_pair_hex(DEST, NAME, VALUE, VALUELEN) \ + write_pair_hex_impl((DEST), (const uint8_t *)(NAME), sizeof(NAME) - 1, \ + (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); +} + +#define write_pair_number(DEST, NAME, VALUE) \ + write_pair_number_impl((DEST), (const uint8_t *)(NAME), sizeof(NAME) - 1, \ + (VALUE)) + +static uint8_t *write_pair_duration_impl(uint8_t *p, const uint8_t *name, + size_t namelen, + ngtcp2_duration duration) { + p = write_string_impl(p, name, namelen); + *p++ = ':'; + return write_duration(p, duration); +} + +#define write_pair_duration(DEST, NAME, VALUE) \ + write_pair_duration_impl((DEST), (const uint8_t *)(NAME), sizeof(NAME) - 1, \ + (VALUE)) + +static uint8_t *write_pair_tstamp_impl(uint8_t *p, const uint8_t *name, + size_t namelen, ngtcp2_tstamp ts) { + p = write_string_impl(p, name, namelen); + *p++ = ':'; + return write_tstamp(p, ts); +} + +#define write_pair_tstamp(DEST, NAME, VALUE) \ + write_pair_tstamp_impl((DEST), (const uint8_t *)(NAME), sizeof(NAME) - 1, \ + (VALUE)) + +static uint8_t *write_pair_bool_impl(uint8_t *p, const uint8_t *name, + size_t namelen, int b) { + p = write_string_impl(p, name, namelen); + *p++ = ':'; + return write_bool(p, b); +} + +#define write_pair_bool(DEST, NAME, VALUE) \ + write_pair_bool_impl((DEST), (const uint8_t *)(NAME), sizeof(NAME) - 1, \ + (VALUE)) + +static uint8_t *write_pair_cid_impl(uint8_t *p, const uint8_t *name, + size_t namelen, const ngtcp2_cid *cid) { + p = write_string_impl(p, name, namelen); + *p++ = ':'; + return write_cid(p, cid); +} + +#define write_pair_cid(DEST, NAME, VALUE) \ + write_pair_cid_impl((DEST), (const uint8_t *)(NAME), sizeof(NAME) - 1, \ + (VALUE)) + +#define ngtcp2_make_vec_lit(S) \ + { (uint8_t *)(S), sizeof((S)) - 1 } + +static uint8_t *write_common_fields(uint8_t *p, const ngtcp2_cid *odcid) { + p = write_verbatim( + p, "\"common_fields\":{\"protocol_type\":[\"QUIC\"],\"time_format\":" + "\"relative\",\"reference_time\":0,\"group_id\":"); + p = write_cid(p, odcid); + *p++ = '}'; + return p; +} + +static uint8_t *write_trace(uint8_t *p, int server, const ngtcp2_cid *odcid) { + p = write_verbatim( + p, "\"trace\":{\"vantage_point\":{\"name\":\"ngtcp2\",\"type\":"); + if (server) { + p = write_string(p, "server"); + } else { + p = write_string(p, "client"); + } + p = write_verbatim(p, "},"); + p = write_common_fields(p, odcid); + *p++ = '}'; + return p; +} + +void ngtcp2_qlog_start(ngtcp2_qlog *qlog, const ngtcp2_cid *odcid, int server) { + uint8_t buf[1024]; + uint8_t *p = buf; + + if (!qlog->write) { + return; + } + + p = write_verbatim( + p, "\x1e{\"qlog_format\":\"JSON-SEQ\",\"qlog_version\":\"0.3\","); + p = write_trace(p, server, odcid); + p = write_verbatim(p, "}\n"); + + qlog->write(qlog->user_data, NGTCP2_QLOG_WRITE_FLAG_NONE, buf, + (size_t)(p - buf)); +} + +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); +} + +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 = + ngtcp2_make_vec_lit("version_negotiation"); +static 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 *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_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; + } +} + +static uint8_t *write_pkt_hd(uint8_t *p, const ngtcp2_pkt_hd *hd) { + /* + * {"packet_type":"version_negotiation","packet_number":"0000000000000000000","token":{"data":""}} + */ +#define NGTCP2_QLOG_PKT_HD_OVERHEAD 95 + + *p++ = '{'; + p = write_pair(p, "packet_type", qlog_pkt_type(hd)); + *p++ = ','; + p = write_pair_number(p, "packet_number", (uint64_t)hd->pkt_num); + if (hd->type == NGTCP2_PKT_INITIAL && hd->tokenlen) { + p = write_verbatim(p, ",\"token\":{"); + p = write_pair_hex(p, "data", hd->token, hd->tokenlen); + *p++ = '}'; + } + /* TODO Write DCIL and DCID */ + /* TODO Write SCIL and SCID */ + *p++ = '}'; + return p; +} + +static uint8_t *write_padding_frame(uint8_t *p, const ngtcp2_padding *fr) { + (void)fr; + + /* {"frame_type":"padding"} */ +#define NGTCP2_QLOG_PADDING_FRAME_OVERHEAD 24 + + return write_verbatim(p, "{\"frame_type\":\"padding\"}"); +} + +static uint8_t *write_ping_frame(uint8_t *p, const ngtcp2_ping *fr) { + (void)fr; + + /* {"frame_type":"ping"} */ +#define NGTCP2_QLOG_PING_FRAME_OVERHEAD 21 + + return write_verbatim(p, "{\"frame_type\":\"ping\"}"); +} + +static uint8_t *write_ack_frame(uint8_t *p, const ngtcp2_ack *fr) { + int64_t largest_ack, min_ack; + size_t i; + const ngtcp2_ack_range *range; + + /* + * {"frame_type":"ack","ack_delay":0000000000000000000,"acked_ranges":[]} + * + * each range: + * [0000000000000000000,0000000000000000000], + * + * ecn: + * ,"ect1":0000000000000000000,"ect0":0000000000000000000,"ce":0000000000000000000 + */ +#define NGTCP2_QLOG_ACK_FRAME_BASE_OVERHEAD 70 +#define NGTCP2_QLOG_ACK_FRAME_RANGE_OVERHEAD 42 +#define NGTCP2_QLOG_ACK_FRAME_ECN_OVERHEAD 79 + + p = write_verbatim(p, "{\"frame_type\":\"ack\","); + p = write_pair_duration(p, "ack_delay", fr->ack_delay_unscaled); + p = write_verbatim(p, ",\"acked_ranges\":["); + + largest_ack = fr->largest_ack; + min_ack = fr->largest_ack - (int64_t)fr->first_ack_range; + + *p++ = '['; + p = write_number(p, (uint64_t)min_ack); + if (largest_ack != min_ack) { + *p++ = ','; + p = write_number(p, (uint64_t)largest_ack); + } + *p++ = ']'; + + for (i = 0; i < fr->rangecnt; ++i) { + range = &fr->ranges[i]; + largest_ack = min_ack - (int64_t)range->gap - 2; + min_ack = largest_ack - (int64_t)range->len; + *p++ = ','; + *p++ = '['; + p = write_number(p, (uint64_t)min_ack); + if (largest_ack != min_ack) { + *p++ = ','; + p = write_number(p, (uint64_t)largest_ack); + } + *p++ = ']'; + } + + *p++ = ']'; + + if (fr->type == NGTCP2_FRAME_ACK_ECN) { + *p++ = ','; + p = write_pair_number(p, "ect1", fr->ecn.ect1); + *p++ = ','; + p = write_pair_number(p, "ect0", fr->ecn.ect0); + *p++ = ','; + p = write_pair_number(p, "ce", fr->ecn.ce); + } + + *p++ = '}'; + + return p; +} + +static uint8_t *write_reset_stream_frame(uint8_t *p, + const ngtcp2_reset_stream *fr) { + /* + * {"frame_type":"reset_stream","stream_id":0000000000000000000,"error_code":0000000000000000000,"final_size":0000000000000000000} + */ +#define NGTCP2_QLOG_RESET_STREAM_FRAME_OVERHEAD 127 + + p = write_verbatim(p, "{\"frame_type\":\"reset_stream\","); + p = write_pair_number(p, "stream_id", (uint64_t)fr->stream_id); + *p++ = ','; + p = write_pair_number(p, "error_code", fr->app_error_code); + *p++ = ','; + p = write_pair_number(p, "final_size", fr->final_size); + *p++ = '}'; + + return p; +} + +static uint8_t *write_stop_sending_frame(uint8_t *p, + const ngtcp2_stop_sending *fr) { + /* + * {"frame_type":"stop_sending","stream_id":0000000000000000000,"error_code":0000000000000000000} + */ +#define NGTCP2_QLOG_STOP_SENDING_FRAME_OVERHEAD 94 + + p = write_verbatim(p, "{\"frame_type\":\"stop_sending\","); + p = write_pair_number(p, "stream_id", (uint64_t)fr->stream_id); + *p++ = ','; + p = write_pair_number(p, "error_code", fr->app_error_code); + *p++ = '}'; + + return p; +} + +static uint8_t *write_crypto_frame(uint8_t *p, const ngtcp2_stream *fr) { + /* + * {"frame_type":"crypto","offset":0000000000000000000,"length":0000000000000000000} + */ +#define NGTCP2_QLOG_CRYPTO_FRAME_OVERHEAD 81 + + p = write_verbatim(p, "{\"frame_type\":\"crypto\","); + p = write_pair_number(p, "offset", fr->offset); + *p++ = ','; + p = write_pair_number(p, "length", ngtcp2_vec_len(fr->data, fr->datacnt)); + *p++ = '}'; + + return p; +} + +static uint8_t *write_new_token_frame(uint8_t *p, const ngtcp2_new_token *fr) { + /* + * {"frame_type":"new_token","length":0000000000000000000,"token":{"data":""}} + */ +#define NGTCP2_QLOG_NEW_TOKEN_FRAME_OVERHEAD 75 + + p = write_verbatim(p, "{\"frame_type\":\"new_token\","); + p = write_pair_number(p, "length", fr->tokenlen); + p = write_verbatim(p, ",\"token\":{"); + p = write_pair_hex(p, "data", fr->token, fr->tokenlen); + *p++ = '}'; + *p++ = '}'; + + return p; +} + +static uint8_t *write_stream_frame(uint8_t *p, const ngtcp2_stream *fr) { + /* + * {"frame_type":"stream","stream_id":0000000000000000000,"offset":0000000000000000000,"length":0000000000000000000,"fin":true} + */ +#define NGTCP2_QLOG_STREAM_FRAME_OVERHEAD 124 + + p = write_verbatim(p, "{\"frame_type\":\"stream\","); + p = write_pair_number(p, "stream_id", (uint64_t)fr->stream_id); + *p++ = ','; + p = write_pair_number(p, "offset", fr->offset); + *p++ = ','; + p = write_pair_number(p, "length", ngtcp2_vec_len(fr->data, fr->datacnt)); + if (fr->fin) { + *p++ = ','; + p = write_pair_bool(p, "fin", 1); + } + *p++ = '}'; + + return p; +} + +static uint8_t *write_max_data_frame(uint8_t *p, const ngtcp2_max_data *fr) { + /* + * {"frame_type":"max_data","maximum":0000000000000000000} + */ +#define NGTCP2_QLOG_MAX_DATA_FRAME_OVERHEAD 55 + + p = write_verbatim(p, "{\"frame_type\":\"max_data\","); + p = write_pair_number(p, "maximum", fr->max_data); + *p++ = '}'; + + return p; +} + +static uint8_t *write_max_stream_data_frame(uint8_t *p, + const ngtcp2_max_stream_data *fr) { + /* + * {"frame_type":"max_stream_data","stream_id":0000000000000000000,"maximum":0000000000000000000} + */ +#define NGTCP2_QLOG_MAX_STREAM_DATA_FRAME_OVERHEAD 94 + + p = write_verbatim(p, "{\"frame_type\":\"max_stream_data\","); + p = write_pair_number(p, "stream_id", (uint64_t)fr->stream_id); + *p++ = ','; + p = write_pair_number(p, "maximum", fr->max_stream_data); + *p++ = '}'; + + return p; +} + +static uint8_t *write_max_streams_frame(uint8_t *p, + const ngtcp2_max_streams *fr) { + /* + * {"frame_type":"max_streams","stream_type":"unidirectional","maximum":0000000000000000000} + */ +#define NGTCP2_QLOG_MAX_STREAMS_FRAME_OVERHEAD 89 + + p = write_verbatim(p, "{\"frame_type\":\"max_streams\",\"stream_type\":"); + if (fr->type == NGTCP2_FRAME_MAX_STREAMS_BIDI) { + p = write_string(p, "bidirectional"); + } else { + p = write_string(p, "unidirectional"); + } + *p++ = ','; + p = write_pair_number(p, "maximum", fr->max_streams); + *p++ = '}'; + + return p; +} + +static uint8_t *write_data_blocked_frame(uint8_t *p, + const ngtcp2_data_blocked *fr) { + /* + * {"frame_type":"data_blocked","limit":0000000000000000000} + */ +#define NGTCP2_QLOG_DATA_BLOCKED_FRAME_OVERHEAD 57 + + p = write_verbatim(p, "{\"frame_type\":\"data_blocked\","); + p = write_pair_number(p, "limit", fr->offset); + *p++ = '}'; + + return p; +} + +static uint8_t * +write_stream_data_blocked_frame(uint8_t *p, + const ngtcp2_stream_data_blocked *fr) { + /* + * {"frame_type":"stream_data_blocked","stream_id":0000000000000000000,"limit":0000000000000000000} + */ +#define NGTCP2_QLOG_STREAM_DATA_BLOCKED_FRAME_OVERHEAD 96 + + p = write_verbatim(p, "{\"frame_type\":\"stream_data_blocked\","); + p = write_pair_number(p, "stream_id", (uint64_t)fr->stream_id); + *p++ = ','; + p = write_pair_number(p, "limit", fr->offset); + *p++ = '}'; + + return p; +} + +static uint8_t *write_streams_blocked_frame(uint8_t *p, + const ngtcp2_streams_blocked *fr) { + /* + * {"frame_type":"streams_blocked","stream_type":"unidirectional","limit":0000000000000000000} + */ +#define NGTCP2_QLOG_STREAMS_BLOCKED_FRAME_OVERHEAD 91 + + p = write_verbatim(p, "{\"frame_type\":\"streams_blocked\",\"stream_type\":"); + if (fr->type == NGTCP2_FRAME_STREAMS_BLOCKED_BIDI) { + p = write_string(p, "bidirectional"); + } else { + p = write_string(p, "unidirectional"); + } + *p++ = ','; + p = write_pair_number(p, "limit", fr->max_streams); + *p++ = '}'; + + return p; +} + +static uint8_t * +write_new_connection_id_frame(uint8_t *p, const ngtcp2_new_connection_id *fr) { + /* + * {"frame_type":"new_connection_id","sequence_number":0000000000000000000,"retire_prior_to":0000000000000000000,"connection_id_length":0000000000000000000,"connection_id":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","stateless_reset_token":{"data":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}} + */ +#define NGTCP2_QLOG_NEW_CONNECTION_ID_FRAME_OVERHEAD 280 + + p = write_verbatim(p, "{\"frame_type\":\"new_connection_id\","); + p = write_pair_number(p, "sequence_number", fr->seq); + *p++ = ','; + p = write_pair_number(p, "retire_prior_to", fr->retire_prior_to); + *p++ = ','; + p = write_pair_number(p, "connection_id_length", fr->cid.datalen); + *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++ = '}'; + *p++ = '}'; + + return p; +} + +static uint8_t * +write_retire_connection_id_frame(uint8_t *p, + const ngtcp2_retire_connection_id *fr) { + /* + * {"frame_type":"retire_connection_id","sequence_number":0000000000000000000} + */ +#define NGTCP2_QLOG_RETIRE_CONNECTION_ID_FRAME_OVERHEAD 75 + + p = write_verbatim(p, "{\"frame_type\":\"retire_connection_id\","); + p = write_pair_number(p, "sequence_number", fr->seq); + *p++ = '}'; + + return p; +} + +static uint8_t *write_path_challenge_frame(uint8_t *p, + const ngtcp2_path_challenge *fr) { + /* + * {"frame_type":"path_challenge","data":"xxxxxxxxxxxxxxxx"} + */ +#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++ = '}'; + + return p; +} + +static uint8_t *write_path_response_frame(uint8_t *p, + const ngtcp2_path_response *fr) { + /* + * {"frame_type":"path_response","data":"xxxxxxxxxxxxxxxx"} + */ +#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++ = '}'; + + return p; +} + +static uint8_t * +write_connection_close_frame(uint8_t *p, const ngtcp2_connection_close *fr) { + /* + * {"frame_type":"connection_close","error_space":"application","error_code":0000000000000000000,"raw_error_code":0000000000000000000} + */ +#define NGTCP2_QLOG_CONNECTION_CLOSE_FRAME_OVERHEAD 131 + + p = write_verbatim(p, + "{\"frame_type\":\"connection_close\",\"error_space\":"); + if (fr->type == NGTCP2_FRAME_CONNECTION_CLOSE) { + p = write_string(p, "transport"); + } else { + p = write_string(p, "application"); + } + *p++ = ','; + p = write_pair_number(p, "error_code", fr->error_code); + *p++ = ','; + p = write_pair_number(p, "raw_error_code", fr->error_code); + /* TODO Write reason by escaping non-printables */ + /* TODO Write trigger_frame_type */ + *p++ = '}'; + + return p; +} + +static uint8_t *write_handshake_done_frame(uint8_t *p, + const ngtcp2_handshake_done *fr) { + (void)fr; + + /* + * {"frame_type":"handshake_done"} + */ +#define NGTCP2_QLOG_HANDSHAKE_DONE_FRAME_OVERHEAD 31 + + return write_verbatim(p, "{\"frame_type\":\"handshake_done\"}"); +} + +static uint8_t *write_datagram_frame(uint8_t *p, const ngtcp2_datagram *fr) { + /* + * {"frame_type":"datagram","length":0000000000000000000} + */ +#define NGTCP2_QLOG_DATAGRAM_FRAME_OVERHEAD 54 + + p = write_verbatim(p, "{\"frame_type\":\"datagram\","); + p = write_pair_number(p, "length", ngtcp2_vec_len(fr->data, fr->datacnt)); + *p++ = '}'; + + return p; +} + +static uint8_t *qlog_write_time(ngtcp2_qlog *qlog, uint8_t *p) { + return write_pair_tstamp(p, "time", qlog->last_ts - qlog->ts); +} + +static void qlog_pkt_write_start(ngtcp2_qlog *qlog, int sent) { + uint8_t *p; + + if (!qlog->write) { + return; + } + + ngtcp2_buf_reset(&qlog->buf); + p = qlog->buf.last; + + *p++ = '\x1e'; + *p++ = '{'; + p = qlog_write_time(qlog, p); + p = write_verbatim(p, ",\"name\":"); + if (sent) { + p = write_string(p, "transport:packet_sent"); + } else { + p = write_string(p, "transport:packet_received"); + } + p = write_verbatim(p, ",\"data\":{\"frames\":["); + qlog->buf.last = p; +} + +static void qlog_pkt_write_end(ngtcp2_qlog *qlog, const ngtcp2_pkt_hd *hd, + size_t pktlen) { + uint8_t *p = qlog->buf.last; + + if (!qlog->write) { + return; + } + + /* + * ],"header":,"raw":{"length":0000000000000000000}}} + * + * plus, terminating LF + */ +#define NGTCP2_QLOG_PKT_WRITE_END_OVERHEAD \ + (1 + 50 + NGTCP2_QLOG_PKT_HD_OVERHEAD) + + if (ngtcp2_buf_left(&qlog->buf) < + NGTCP2_QLOG_PKT_WRITE_END_OVERHEAD + hd->tokenlen * 2) { + return; + } + + assert(ngtcp2_buf_len(&qlog->buf)); + + /* Eat last ',' */ + if (*(p - 1) == ',') { + --p; + } + + p = write_verbatim(p, "],\"header\":"); + p = write_pkt_hd(p, hd); + p = write_verbatim(p, ",\"raw\":{\"length\":"); + p = write_number(p, pktlen); + p = write_verbatim(p, "}}}\n"); + + qlog->buf.last = p; + + qlog->write(qlog->user_data, NGTCP2_QLOG_WRITE_FLAG_NONE, qlog->buf.pos, + ngtcp2_buf_len(&qlog->buf)); +} + +void ngtcp2_qlog_write_frame(ngtcp2_qlog *qlog, const ngtcp2_frame *fr) { + uint8_t *p = qlog->buf.last; + + if (!qlog->write) { + return; + } + + switch (fr->type) { + case NGTCP2_FRAME_PADDING: + if (ngtcp2_buf_left(&qlog->buf) < NGTCP2_QLOG_PADDING_FRAME_OVERHEAD + 1) { + return; + } + p = write_padding_frame(p, &fr->padding); + break; + case NGTCP2_FRAME_PING: + if (ngtcp2_buf_left(&qlog->buf) < NGTCP2_QLOG_PING_FRAME_OVERHEAD + 1) { + return; + } + p = write_ping_frame(p, &fr->ping); + break; + case NGTCP2_FRAME_ACK: + 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 + ? NGTCP2_QLOG_ACK_FRAME_ECN_OVERHEAD + : 0) + + NGTCP2_QLOG_ACK_FRAME_RANGE_OVERHEAD * (1 + fr->ack.rangecnt) + 1) { + return; + } + p = write_ack_frame(p, &fr->ack); + break; + case NGTCP2_FRAME_RESET_STREAM: + if (ngtcp2_buf_left(&qlog->buf) < + NGTCP2_QLOG_RESET_STREAM_FRAME_OVERHEAD + 1) { + return; + } + p = write_reset_stream_frame(p, &fr->reset_stream); + break; + case NGTCP2_FRAME_STOP_SENDING: + if (ngtcp2_buf_left(&qlog->buf) < + NGTCP2_QLOG_STOP_SENDING_FRAME_OVERHEAD + 1) { + return; + } + p = write_stop_sending_frame(p, &fr->stop_sending); + break; + case NGTCP2_FRAME_CRYPTO: + if (ngtcp2_buf_left(&qlog->buf) < NGTCP2_QLOG_CRYPTO_FRAME_OVERHEAD + 1) { + return; + } + p = write_crypto_frame(p, &fr->stream); + break; + case NGTCP2_FRAME_NEW_TOKEN: + if (ngtcp2_buf_left(&qlog->buf) < + NGTCP2_QLOG_NEW_TOKEN_FRAME_OVERHEAD + fr->new_token.tokenlen * 2 + 1) { + return; + } + p = write_new_token_frame(p, &fr->new_token); + break; + case NGTCP2_FRAME_STREAM: + if (ngtcp2_buf_left(&qlog->buf) < NGTCP2_QLOG_STREAM_FRAME_OVERHEAD + 1) { + return; + } + p = write_stream_frame(p, &fr->stream); + break; + case NGTCP2_FRAME_MAX_DATA: + if (ngtcp2_buf_left(&qlog->buf) < NGTCP2_QLOG_MAX_DATA_FRAME_OVERHEAD + 1) { + return; + } + p = write_max_data_frame(p, &fr->max_data); + break; + case NGTCP2_FRAME_MAX_STREAM_DATA: + if (ngtcp2_buf_left(&qlog->buf) < + NGTCP2_QLOG_MAX_STREAM_DATA_FRAME_OVERHEAD + 1) { + return; + } + p = write_max_stream_data_frame(p, &fr->max_stream_data); + break; + case NGTCP2_FRAME_MAX_STREAMS_BIDI: + case NGTCP2_FRAME_MAX_STREAMS_UNI: + if (ngtcp2_buf_left(&qlog->buf) < + NGTCP2_QLOG_MAX_STREAMS_FRAME_OVERHEAD + 1) { + return; + } + p = write_max_streams_frame(p, &fr->max_streams); + break; + case NGTCP2_FRAME_DATA_BLOCKED: + if (ngtcp2_buf_left(&qlog->buf) < + NGTCP2_QLOG_DATA_BLOCKED_FRAME_OVERHEAD + 1) { + return; + } + p = write_data_blocked_frame(p, &fr->data_blocked); + break; + case NGTCP2_FRAME_STREAM_DATA_BLOCKED: + if (ngtcp2_buf_left(&qlog->buf) < + NGTCP2_QLOG_STREAM_DATA_BLOCKED_FRAME_OVERHEAD + 1) { + return; + } + p = write_stream_data_blocked_frame(p, &fr->stream_data_blocked); + break; + case NGTCP2_FRAME_STREAMS_BLOCKED_BIDI: + case NGTCP2_FRAME_STREAMS_BLOCKED_UNI: + if (ngtcp2_buf_left(&qlog->buf) < + NGTCP2_QLOG_STREAMS_BLOCKED_FRAME_OVERHEAD + 1) { + return; + } + p = write_streams_blocked_frame(p, &fr->streams_blocked); + break; + case NGTCP2_FRAME_NEW_CONNECTION_ID: + if (ngtcp2_buf_left(&qlog->buf) < + NGTCP2_QLOG_NEW_CONNECTION_ID_FRAME_OVERHEAD + 1) { + return; + } + p = write_new_connection_id_frame(p, &fr->new_connection_id); + break; + case NGTCP2_FRAME_RETIRE_CONNECTION_ID: + if (ngtcp2_buf_left(&qlog->buf) < + NGTCP2_QLOG_RETIRE_CONNECTION_ID_FRAME_OVERHEAD + 1) { + return; + } + p = write_retire_connection_id_frame(p, &fr->retire_connection_id); + break; + case NGTCP2_FRAME_PATH_CHALLENGE: + if (ngtcp2_buf_left(&qlog->buf) < + NGTCP2_QLOG_PATH_CHALLENGE_FRAME_OVERHEAD + 1) { + return; + } + p = write_path_challenge_frame(p, &fr->path_challenge); + break; + case NGTCP2_FRAME_PATH_RESPONSE: + if (ngtcp2_buf_left(&qlog->buf) < + NGTCP2_QLOG_PATH_RESPONSE_FRAME_OVERHEAD + 1) { + return; + } + p = write_path_response_frame(p, &fr->path_response); + break; + case NGTCP2_FRAME_CONNECTION_CLOSE: + case NGTCP2_FRAME_CONNECTION_CLOSE_APP: + if (ngtcp2_buf_left(&qlog->buf) < + NGTCP2_QLOG_CONNECTION_CLOSE_FRAME_OVERHEAD + 1) { + return; + } + p = write_connection_close_frame(p, &fr->connection_close); + break; + case NGTCP2_FRAME_HANDSHAKE_DONE: + if (ngtcp2_buf_left(&qlog->buf) < + NGTCP2_QLOG_HANDSHAKE_DONE_FRAME_OVERHEAD + 1) { + return; + } + p = write_handshake_done_frame(p, &fr->handshake_done); + break; + case NGTCP2_FRAME_DATAGRAM: + case NGTCP2_FRAME_DATAGRAM_LEN: + if (ngtcp2_buf_left(&qlog->buf) < NGTCP2_QLOG_DATAGRAM_FRAME_OVERHEAD + 1) { + return; + } + p = write_datagram_frame(p, &fr->datagram); + break; + default: + ngtcp2_unreachable(); + } + + *p++ = ','; + + qlog->buf.last = p; +} + +void ngtcp2_qlog_pkt_received_start(ngtcp2_qlog *qlog) { + qlog_pkt_write_start(qlog, /* sent = */ 0); +} + +void ngtcp2_qlog_pkt_received_end(ngtcp2_qlog *qlog, const ngtcp2_pkt_hd *hd, + size_t pktlen) { + qlog_pkt_write_end(qlog, hd, pktlen); +} + +void ngtcp2_qlog_pkt_sent_start(ngtcp2_qlog *qlog) { + qlog_pkt_write_start(qlog, /* sent = */ 1); +} + +void ngtcp2_qlog_pkt_sent_end(ngtcp2_qlog *qlog, const ngtcp2_pkt_hd *hd, + size_t pktlen) { + qlog_pkt_write_end(qlog, hd, pktlen); +} + +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 *p = buf; + const ngtcp2_preferred_addr *paddr; + const ngtcp2_sockaddr_in *sa_in; + const ngtcp2_sockaddr_in6 *sa_in6; + + if (!qlog->write) { + return; + } + + *p++ = '\x1e'; + *p++ = '{'; + p = qlog_write_time(qlog, p); + p = write_verbatim( + p, ",\"name\":\"transport:parameters_set\",\"data\":{\"owner\":"); + + if (side == NGTCP2_QLOG_SIDE_LOCAL) { + p = write_string(p, "local"); + } else { + p = write_string(p, "remote"); + } + + *p++ = ','; + p = write_pair_cid(p, "initial_source_connection_id", ¶ms->initial_scid); + *p++ = ','; + if (side == (server ? NGTCP2_QLOG_SIDE_LOCAL : NGTCP2_QLOG_SIDE_REMOTE)) { + p = write_pair_cid(p, "original_destination_connection_id", + ¶ms->original_dcid); + *p++ = ','; + } + if (params->retry_scid_present) { + p = write_pair_cid(p, "retry_source_connection_id", ¶ms->retry_scid); + *p++ = ','; + } + if (params->stateless_reset_token_present) { + p = write_verbatim(p, "\"stateless_reset_token\":{"); + p = write_pair_hex(p, "data", params->stateless_reset_token, + sizeof(params->stateless_reset_token)); + *p++ = '}'; + *p++ = ','; + } + p = write_pair_bool(p, "disable_active_migration", + params->disable_active_migration); + *p++ = ','; + p = write_pair_duration(p, "max_idle_timeout", params->max_idle_timeout); + *p++ = ','; + p = write_pair_number(p, "max_udp_payload_size", + params->max_udp_payload_size); + *p++ = ','; + p = write_pair_number(p, "ack_delay_exponent", params->ack_delay_exponent); + *p++ = ','; + p = write_pair_duration(p, "max_ack_delay", params->max_ack_delay); + *p++ = ','; + p = write_pair_number(p, "active_connection_id_limit", + params->active_connection_id_limit); + *p++ = ','; + p = write_pair_number(p, "initial_max_data", params->initial_max_data); + *p++ = ','; + p = write_pair_number(p, "initial_max_stream_data_bidi_local", + params->initial_max_stream_data_bidi_local); + *p++ = ','; + p = write_pair_number(p, "initial_max_stream_data_bidi_remote", + params->initial_max_stream_data_bidi_remote); + *p++ = ','; + p = write_pair_number(p, "initial_max_stream_data_uni", + params->initial_max_stream_data_uni); + *p++ = ','; + p = write_pair_number(p, "initial_max_streams_bidi", + params->initial_max_streams_bidi); + *p++ = ','; + p = write_pair_number(p, "initial_max_streams_uni", + params->initial_max_streams_uni); + if (params->preferred_addr_present) { + *p++ = ','; + paddr = ¶ms->preferred_addr; + p = write_string(p, "preferred_address"); + *p++ = ':'; + *p++ = '{'; + + if (paddr->ipv4_present) { + sa_in = &paddr->ipv4; + + p = write_pair_hex(p, "ip_v4", (const uint8_t *)&sa_in->sin_addr, + sizeof(sa_in->sin_addr)); + *p++ = ','; + p = write_pair_number(p, "port_v4", ngtcp2_ntohs(sa_in->sin_port)); + *p++ = ','; + } + + if (paddr->ipv6_present) { + sa_in6 = &paddr->ipv6; + + p = write_pair_hex(p, "ip_v6", (const uint8_t *)&sa_in6->sin6_addr, + sizeof(sa_in6->sin6_addr)); + *p++ = ','; + p = write_pair_number(p, "port_v6", ngtcp2_ntohs(sa_in6->sin6_port)); + *p++ = ','; + } + + p = write_pair_cid(p, "connection_id", &paddr->cid); + p = write_verbatim(p, ",\"stateless_reset_token\":{"); + p = write_pair_hex(p, "data", paddr->stateless_reset_token, + sizeof(paddr->stateless_reset_token)); + *p++ = '}'; + *p++ = '}'; + } + *p++ = ','; + p = write_pair_number(p, "max_datagram_frame_size", + params->max_datagram_frame_size); + *p++ = ','; + p = write_pair_bool(p, "grease_quic_bit", params->grease_quic_bit); + p = write_verbatim(p, "}}\n"); + + qlog->write(qlog->user_data, NGTCP2_QLOG_WRITE_FLAG_NONE, buf, + (size_t)(p - buf)); +} + +void ngtcp2_qlog_metrics_updated(ngtcp2_qlog *qlog, + const ngtcp2_conn_stat *cstat) { + uint8_t buf[1024]; + uint8_t *p = buf; + + if (!qlog->write) { + return; + } + + *p++ = '\x1e'; + *p++ = '{'; + p = qlog_write_time(qlog, p); + p = write_verbatim(p, ",\"name\":\"recovery:metrics_updated\",\"data\":{"); + + if (cstat->min_rtt != UINT64_MAX) { + p = write_pair_duration(p, "min_rtt", cstat->min_rtt); + *p++ = ','; + } + p = write_pair_duration(p, "smoothed_rtt", cstat->smoothed_rtt); + *p++ = ','; + p = write_pair_duration(p, "latest_rtt", cstat->latest_rtt); + *p++ = ','; + p = write_pair_duration(p, "rtt_variance", cstat->rttvar); + *p++ = ','; + p = write_pair_number(p, "pto_count", cstat->pto_count); + *p++ = ','; + p = write_pair_number(p, "congestion_window", cstat->cwnd); + *p++ = ','; + p = write_pair_number(p, "bytes_in_flight", cstat->bytes_in_flight); + if (cstat->ssthresh != UINT64_MAX) { + *p++ = ','; + p = write_pair_number(p, "ssthresh", cstat->ssthresh); + } + + p = write_verbatim(p, "}}\n"); + + qlog->write(qlog->user_data, NGTCP2_QLOG_WRITE_FLAG_NONE, buf, + (size_t)(p - buf)); +} + +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++ = '{'; + 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_verbatim(p, "}}\n"); + + qlog->write(qlog->user_data, NGTCP2_QLOG_WRITE_FLAG_NONE, buf, + (size_t)(p - buf)); +} + +void ngtcp2_qlog_retry_pkt_received(ngtcp2_qlog *qlog, const ngtcp2_pkt_hd *hd, + const ngtcp2_pkt_retry *retry) { + uint8_t rawbuf[1024]; + ngtcp2_buf buf; + + if (!qlog->write) { + return; + } + + ngtcp2_buf_init(&buf, rawbuf, sizeof(rawbuf)); + + *buf.last++ = '\x1e'; + *buf.last++ = '{'; + buf.last = qlog_write_time(qlog, buf.last); + buf.last = write_verbatim( + buf.last, + ",\"name\":\"transport:packet_received\",\"data\":{\"header\":"); + + if (ngtcp2_buf_left(&buf) < + NGTCP2_QLOG_PKT_HD_OVERHEAD + hd->tokenlen * 2 + + sizeof(",\"retry_token\":{\"data\":\"\"}}}\n") - 1 + + retry->tokenlen * 2) { + return; + } + + buf.last = write_pkt_hd(buf.last, hd); + buf.last = write_verbatim(buf.last, ",\"retry_token\":{"); + buf.last = write_pair_hex(buf.last, "data", retry->token, retry->tokenlen); + buf.last = write_verbatim(buf.last, "}}}\n"); + + qlog->write(qlog->user_data, NGTCP2_QLOG_WRITE_FLAG_NONE, buf.pos, + ngtcp2_buf_len(&buf)); +} + +void ngtcp2_qlog_stateless_reset_pkt_received( + ngtcp2_qlog *qlog, const ngtcp2_pkt_stateless_reset *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++ = '{'; + p = qlog_write_time(qlog, p); + p = write_verbatim( + p, ",\"name\":\"transport:packet_received\",\"data\":{\"header\":"); + p = write_pkt_hd(p, &hd); + *p++ = ','; + p = write_pair_hex(p, "stateless_reset_token", sr->stateless_reset_token, + NGTCP2_STATELESS_RESET_TOKENLEN); + p = write_verbatim(p, "}}\n"); + + qlog->write(qlog->user_data, NGTCP2_QLOG_WRITE_FLAG_NONE, buf, + (size_t)(p - buf)); +} + +void ngtcp2_qlog_version_negotiation_pkt_received(ngtcp2_qlog *qlog, + const ngtcp2_pkt_hd *hd, + const uint32_t *sv, + size_t nsv) { + uint8_t rawbuf[512]; + ngtcp2_buf buf; + size_t i; + uint32_t v; + + if (!qlog->write) { + return; + } + + ngtcp2_buf_init(&buf, rawbuf, sizeof(rawbuf)); + + *buf.last++ = '\x1e'; + *buf.last++ = '{'; + buf.last = qlog_write_time(qlog, buf.last); + buf.last = write_verbatim( + buf.last, + ",\"name\":\"transport:packet_received\",\"data\":{\"header\":"); + buf.last = write_pkt_hd(buf.last, hd); + buf.last = write_verbatim(buf.last, ",\"supported_versions\":["); + + if (nsv) { + if (ngtcp2_buf_left(&buf) < + (sizeof("\"xxxxxxxx\",") - 1) * nsv - 1 + sizeof("]}}\n") - 1) { + return; + } + + v = ngtcp2_htonl(sv[0]); + buf.last = write_hex(buf.last, (const uint8_t *)&v, sizeof(v)); + + for (i = 1; i < nsv; ++i) { + *buf.last++ = ','; + v = ngtcp2_htonl(sv[i]); + buf.last = write_hex(buf.last, (const uint8_t *)&v, sizeof(v)); + } + } + + buf.last = write_verbatim(buf.last, "]}}\n"); + + qlog->write(qlog->user_data, NGTCP2_QLOG_WRITE_FLAG_NONE, buf.pos, + ngtcp2_buf_len(&buf)); +} diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_qlog.h b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_qlog.h new file mode 100644 index 0000000..b9107c0 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_qlog.h @@ -0,0 +1,161 @@ +/* + * 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. + */ +#ifndef NGTCP2_QLOG_H +#define NGTCP2_QLOG_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_pkt.h" +#include "ngtcp2_cc.h" +#include "ngtcp2_buf.h" +#include "ngtcp2_rtb.h" + +/* NGTCP2_QLOG_BUFLEN is the length of heap allocated buffer for + qlog. */ +#define NGTCP2_QLOG_BUFLEN 4096 + +typedef enum ngtcp2_qlog_side { + NGTCP2_QLOG_SIDE_LOCAL, + NGTCP2_QLOG_SIDE_REMOTE, +} ngtcp2_qlog_side; + +typedef struct ngtcp2_qlog { + /* write is a callback function to write qlog. */ + ngtcp2_qlog_write write; + /* ts is the initial timestamp */ + ngtcp2_tstamp ts; + /* last_ts is the timestamp observed last time. */ + ngtcp2_tstamp last_ts; + /* buf is a heap allocated buffer to write exclusively + packet_received and packet_sent. */ + ngtcp2_buf buf; + /* user_data is an opaque pointer which is passed to write + callback. */ + void *user_data; +} ngtcp2_qlog; + +/* + * ngtcp2_qlog_init initializes |qlog|. + */ +void ngtcp2_qlog_init(ngtcp2_qlog *qlog, ngtcp2_qlog_write write, + ngtcp2_tstamp ts, void *user_data); + +/* + * ngtcp2_qlog_start writes qlog preamble. + */ +void ngtcp2_qlog_start(ngtcp2_qlog *qlog, const ngtcp2_cid *odcid, int server); + +/* + * ngtcp2_qlog_end writes closing part of qlog. + */ +void ngtcp2_qlog_end(ngtcp2_qlog *qlog); + +/* + * ngtcp2_qlog_write_frame writes |fr| to qlog->buf. + * ngtcp2_qlog_pkt_received_start or ngtcp2_qlog_pkt_sent_start must + * be called before calling this function. + */ +void ngtcp2_qlog_write_frame(ngtcp2_qlog *qlog, const ngtcp2_frame *fr); + +/* + * ngtcp2_qlog_pkt_received_start starts to write packet_received + * event. It initializes qlog->buf. It writes qlog to qlog->buf. + * ngtcp2_qlog_pkt_received_end will flush the content of qlog->buf to + * write callback. + */ +void ngtcp2_qlog_pkt_received_start(ngtcp2_qlog *qlog); + +/* + * ngtcp2_qlog_pkt_received_end ends packet_received event and sends + * the content of qlog->buf to qlog->write callback. + */ +void ngtcp2_qlog_pkt_received_end(ngtcp2_qlog *qlog, const ngtcp2_pkt_hd *hd, + size_t pktlen); + +/* + * ngtcp2_qlog_pkt_sent_start starts to write packet_sent event. It + * initializes qlog->buf. It writes qlog to qlog->buf. + * ngtcp2_qlog_pkt_sent_end will flush the content of qlog->buf to + * write callback. + */ +void ngtcp2_qlog_pkt_sent_start(ngtcp2_qlog *qlog); + +/* + * ngtcp2_qlog_pkt_sent_end ends packet_sent event and sends the + * content of qlog->buf to qlog->write callback. + */ +void ngtcp2_qlog_pkt_sent_end(ngtcp2_qlog *qlog, const ngtcp2_pkt_hd *hd, + size_t pktlen); + +/* + * ngtcp2_qlog_parameters_set_transport_params writes |params| to qlog + * as parameters_set event. |server| is nonzero if the local endpoint + * is server. If |local| is nonzero, it is "owner" field becomes + * "local", otherwise "remote". + */ +void ngtcp2_qlog_parameters_set_transport_params( + ngtcp2_qlog *qlog, const ngtcp2_transport_params *params, int server, + ngtcp2_qlog_side side); + +/* + * ngtcp2_qlog_metrics_updated writes metrics_updated event of + * recovery category. + */ +void ngtcp2_qlog_metrics_updated(ngtcp2_qlog *qlog, + const ngtcp2_conn_stat *cstat); + +/* + * ngtcp2_qlog_pkt_lost writes packet_lost event. + */ +void ngtcp2_qlog_pkt_lost(ngtcp2_qlog *qlog, ngtcp2_rtb_entry *ent); + +/* + * ngtcp2_qlog_retry_pkt_received writes packet_received event for a + * received Retry packet. + */ +void ngtcp2_qlog_retry_pkt_received(ngtcp2_qlog *qlog, const ngtcp2_pkt_hd *hd, + const ngtcp2_pkt_retry *retry); + +/* + * ngtcp2_qlog_stateless_reset_pkt_received writes packet_received + * 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_version_negotiation_pkt_received writes packet_received + * event for a received Version Negotiation packet. + */ +void ngtcp2_qlog_version_negotiation_pkt_received(ngtcp2_qlog *qlog, + const ngtcp2_pkt_hd *hd, + const uint32_t *sv, + size_t nsv); + +#endif /* NGTCP2_QLOG_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_range.c b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_range.c new file mode 100644 index 0000000..9379496 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_range.c @@ -0,0 +1,61 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 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_range.h" +#include "ngtcp2_macro.h" + +void ngtcp2_range_init(ngtcp2_range *r, uint64_t begin, uint64_t end) { + r->begin = begin; + r->end = end; +} + +ngtcp2_range ngtcp2_range_intersect(const ngtcp2_range *a, + const ngtcp2_range *b) { + ngtcp2_range r = {0, 0}; + uint64_t begin = ngtcp2_max(a->begin, b->begin); + uint64_t end = ngtcp2_min(a->end, b->end); + if (begin < end) { + ngtcp2_range_init(&r, begin, end); + } + return r; +} + +uint64_t ngtcp2_range_len(const ngtcp2_range *r) { return r->end - r->begin; } + +int ngtcp2_range_eq(const ngtcp2_range *a, const ngtcp2_range *b) { + return a->begin == b->begin && a->end == b->end; +} + +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; +} + +int ngtcp2_range_not_after(const ngtcp2_range *a, const ngtcp2_range *b) { + return a->end <= b->end; +} diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_range.h b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_range.h new file mode 100644 index 0000000..a776c4e --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_range.h @@ -0,0 +1,80 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 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_RANGE_H +#define NGTCP2_RANGE_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +/* + * ngtcp2_range represents half-closed range [begin, end). + */ +typedef struct ngtcp2_range { + uint64_t begin; + uint64_t end; +} ngtcp2_range; + +/* + * ngtcp2_range_init initializes |r| with the range [|begin|, |end|). + */ +void ngtcp2_range_init(ngtcp2_range *r, uint64_t begin, uint64_t end); + +/* + * ngtcp2_range_intersect returns the intersection of |a| and |b|. If + * they do not overlap, it returns empty range. + */ +ngtcp2_range ngtcp2_range_intersect(const ngtcp2_range *a, + const ngtcp2_range *b); + +/* + * ngtcp2_range_len returns the length of |r|. + */ +uint64_t ngtcp2_range_len(const ngtcp2_range *r); + +/* + * ngtcp2_range_eq returns nonzero if |a| equals |b|, such that + * a->begin == b->begin, and a->end == b->end hold. + */ +int ngtcp2_range_eq(const ngtcp2_range *a, const ngtcp2_range *b); + +/* + * ngtcp2_range_cut returns the left and right range after removing + * |b| from |a|. This function assumes that |a| completely includes + * |b|. In other words, a->begin <= b->begin and b->end <= a->end + * hold. + */ +void ngtcp2_range_cut(ngtcp2_range *left, ngtcp2_range *right, + const ngtcp2_range *a, const ngtcp2_range *b); + +/* + * ngtcp2_range_not_after returns nonzero if the right edge of |a| + * does not go beyond of the right edge of |b|. + */ +int ngtcp2_range_not_after(const ngtcp2_range *a, const ngtcp2_range *b); + +#endif /* NGTCP2_RANGE_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_rcvry.h b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_rcvry.h new file mode 100644 index 0000000..4cb4088 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_rcvry.h @@ -0,0 +1,40 @@ +/* + * 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. + */ +#ifndef NGTCP2_RCVRY_H +#define NGTCP2_RCVRY_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +/* NGTCP2_PKT_THRESHOLD is kPacketThreshold described in RFC 9002. */ +#define NGTCP2_PKT_THRESHOLD 3 + +/* NGTCP2_GRANULARITY is kGranularity described in RFC 9002. */ +#define NGTCP2_GRANULARITY NGTCP2_MILLISECONDS + +#endif /* NGTCP2_RCVRY_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_ringbuf.c b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_ringbuf.c new file mode 100644 index 0000000..c381c23 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_ringbuf.c @@ -0,0 +1,123 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 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_ringbuf.h" + +#include <assert.h> +#ifdef WIN32 +# include <intrin.h> +#endif + +#include "ngtcp2_macro.h" + +#if defined(_MSC_VER) && !defined(__clang__) && \ + (defined(_M_ARM) || defined(_M_ARM64)) +static unsigned int __popcnt(unsigned int x) { + unsigned int c = 0; + for (; x; ++c) { + x &= x - 1; + } + return c; +} +#endif + +int ngtcp2_ringbuf_init(ngtcp2_ringbuf *rb, size_t nmemb, size_t size, + const ngtcp2_mem *mem) { + uint8_t *buf = ngtcp2_mem_malloc(mem, nmemb * size); + if (buf == NULL) { + return NGTCP2_ERR_NOMEM; + } + + ngtcp2_ringbuf_buf_init(rb, nmemb, size, buf, mem); + + return 0; +} + +void ngtcp2_ringbuf_buf_init(ngtcp2_ringbuf *rb, size_t nmemb, size_t size, + uint8_t *buf, const ngtcp2_mem *mem) { +#ifdef WIN32 + assert(1 == __popcnt((unsigned int)nmemb)); +#else + assert(1 == __builtin_popcount((unsigned int)nmemb)); +#endif + + rb->buf = buf; + rb->mem = mem; + rb->mask = nmemb - 1; + rb->size = size; + rb->first = 0; + rb->len = 0; +} + +void ngtcp2_ringbuf_free(ngtcp2_ringbuf *rb) { + if (rb == NULL) { + return; + } + + ngtcp2_mem_free(rb->mem, rb->buf); +} + +void *ngtcp2_ringbuf_push_front(ngtcp2_ringbuf *rb) { + rb->first = (rb->first - 1) & rb->mask; + if (rb->len < rb->mask + 1) { + ++rb->len; + } + + return (void *)&rb->buf[rb->first * rb->size]; +} + +void *ngtcp2_ringbuf_push_back(ngtcp2_ringbuf *rb) { + size_t offset = (rb->first + rb->len) & rb->mask; + + if (rb->len == rb->mask + 1) { + rb->first = (rb->first + 1) & rb->mask; + } else { + ++rb->len; + } + + return (void *)&rb->buf[offset * rb->size]; +} + +void ngtcp2_ringbuf_pop_front(ngtcp2_ringbuf *rb) { + rb->first = (rb->first + 1) & rb->mask; + --rb->len; +} + +void ngtcp2_ringbuf_pop_back(ngtcp2_ringbuf *rb) { + assert(rb->len); + --rb->len; +} + +void ngtcp2_ringbuf_resize(ngtcp2_ringbuf *rb, size_t len) { + assert(len <= rb->mask + 1); + rb->len = len; +} + +void *ngtcp2_ringbuf_get(ngtcp2_ringbuf *rb, size_t offset) { + assert(offset < rb->len); + offset = (rb->first + offset) & rb->mask; + return &rb->buf[offset * rb->size]; +} + +int ngtcp2_ringbuf_full(ngtcp2_ringbuf *rb) { return rb->len == rb->mask + 1; } diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_ringbuf.h b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_ringbuf.h new file mode 100644 index 0000000..b28a882 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_ringbuf.h @@ -0,0 +1,132 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 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_RINGBUF_H +#define NGTCP2_RINGBUF_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_mem.h" + +typedef struct ngtcp2_ringbuf { + /* buf points to the underlying buffer. */ + uint8_t *buf; + const ngtcp2_mem *mem; + /* mask is the bit mask to cover all bits for the maximum number of + elements. The maximum number of elements is mask + 1. */ + size_t mask; + /* size is the size of each element. */ + size_t size; + /* first is the offset to the first element. */ + size_t first; + /* len is the number of elements actually stored. */ + size_t len; +} ngtcp2_ringbuf; + +/* + * ngtcp2_ringbuf_init initializes |rb|. |nmemb| is the number of + * elements that can be stored in this buffer. |size| is the size of + * each element. |size| must be power of 2. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +int ngtcp2_ringbuf_init(ngtcp2_ringbuf *rb, size_t nmemb, size_t size, + const ngtcp2_mem *mem); + +/* + * ngtcp2_ringbuf_buf_init initializes |rb| with given buffer and + * size. + */ +void ngtcp2_ringbuf_buf_init(ngtcp2_ringbuf *rb, size_t nmemb, size_t size, + uint8_t *buf, const ngtcp2_mem *mem); + +/* + * ngtcp2_ringbuf_free frees resources allocated for |rb|. This + * function does not free the memory pointed by |rb|. + */ +void ngtcp2_ringbuf_free(ngtcp2_ringbuf *rb); + +/* ngtcp2_ringbuf_push_front moves the offset to the first element in + the buffer backward, and returns the pointer to the element. + Caller can store data to the buffer pointed by the returned + pointer. If this action exceeds the capacity of the ring buffer, + the last element is silently overwritten, and rb->len remains + unchanged. */ +void *ngtcp2_ringbuf_push_front(ngtcp2_ringbuf *rb); + +/* ngtcp2_ringbuf_push_back moves the offset to the last element in + the buffer forward, and returns the pointer to the element. Caller + can store data to the buffer pointed by the returned pointer. If + this action exceeds the capacity of the ring buffer, the first + element is silently overwritten, and rb->len remains unchanged. */ +void *ngtcp2_ringbuf_push_back(ngtcp2_ringbuf *rb); + +/* + * ngtcp2_ringbuf_pop_front removes first element in |rb|. + */ +void ngtcp2_ringbuf_pop_front(ngtcp2_ringbuf *rb); + +/* + * ngtcp2_ringbuf_pop_back removes the last element in |rb|. + */ +void ngtcp2_ringbuf_pop_back(ngtcp2_ringbuf *rb); + +/* ngtcp2_ringbuf_resize changes the number of elements stored. This + does not change the capacity of the underlying buffer. */ +void ngtcp2_ringbuf_resize(ngtcp2_ringbuf *rb, size_t len); + +/* ngtcp2_ringbuf_get returns the pointer to the element at + |offset|. */ +void *ngtcp2_ringbuf_get(ngtcp2_ringbuf *rb, size_t offset); + +/* ngtcp2_ringbuf_len returns the number of elements stored. */ +#define ngtcp2_ringbuf_len(RB) ((RB)->len) + +/* ngtcp2_ringbuf_full returns nonzero if |rb| is full. */ +int ngtcp2_ringbuf_full(ngtcp2_ringbuf *rb); + +/* ngtcp2_static_ringbuf_def defines ngtcp2_ringbuf struct wrapper + which uses a statically allocated buffer that is suitable for a + usage that does not change buffer size with ngtcp2_ringbuf_resize. + ngtcp2_ringbuf_free should never be called for rb field. */ +#define ngtcp2_static_ringbuf_def(NAME, NMEMB, SIZE) \ + typedef struct ngtcp2_static_ringbuf_##NAME { \ + ngtcp2_ringbuf rb; \ + uint8_t buf[(NMEMB) * (SIZE)]; \ + } ngtcp2_static_ringbuf_##NAME; \ + \ + static inline void ngtcp2_static_ringbuf_##NAME##_init( \ + ngtcp2_static_ringbuf_##NAME *srb) { \ + ngtcp2_ringbuf_buf_init(&srb->rb, (NMEMB), (SIZE), srb->buf, NULL); \ + } + +#endif /* NGTCP2_RINGBUF_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_rob.c b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_rob.c new file mode 100644 index 0000000..5cac383 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_rob.c @@ -0,0 +1,317 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 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_rob.h" + +#include <string.h> +#include <assert.h> + +#include "ngtcp2_macro.h" + +int ngtcp2_rob_gap_new(ngtcp2_rob_gap **pg, uint64_t begin, uint64_t end, + const ngtcp2_mem *mem) { + *pg = ngtcp2_mem_malloc(mem, sizeof(ngtcp2_rob_gap)); + if (*pg == NULL) { + return NGTCP2_ERR_NOMEM; + } + + (*pg)->range.begin = begin; + (*pg)->range.end = end; + + return 0; +} + +void ngtcp2_rob_gap_del(ngtcp2_rob_gap *g, const ngtcp2_mem *mem) { + ngtcp2_mem_free(mem, g); +} + +int ngtcp2_rob_data_new(ngtcp2_rob_data **pd, uint64_t offset, size_t chunk, + const ngtcp2_mem *mem) { + *pd = ngtcp2_mem_malloc(mem, sizeof(ngtcp2_rob_data) + chunk); + if (*pd == NULL) { + return NGTCP2_ERR_NOMEM; + } + + (*pd)->range.begin = offset; + (*pd)->range.end = offset + chunk; + (*pd)->begin = (uint8_t *)(*pd) + sizeof(ngtcp2_rob_data); + (*pd)->end = (*pd)->begin + chunk; + + return 0; +} + +void ngtcp2_rob_data_del(ngtcp2_rob_data *d, const ngtcp2_mem *mem) { + ngtcp2_mem_free(mem, d); +} + +int ngtcp2_rob_init(ngtcp2_rob *rob, size_t chunk, const ngtcp2_mem *mem) { + int rv; + ngtcp2_rob_gap *g; + + ngtcp2_ksl_init(&rob->gapksl, ngtcp2_ksl_range_compar, sizeof(ngtcp2_range), + mem); + + rv = ngtcp2_rob_gap_new(&g, 0, UINT64_MAX, mem); + if (rv != 0) { + goto fail_rob_gap_new; + } + + rv = ngtcp2_ksl_insert(&rob->gapksl, NULL, &g->range, g); + if (rv != 0) { + goto fail_gapksl_ksl_insert; + } + + ngtcp2_ksl_init(&rob->dataksl, ngtcp2_ksl_range_compar, sizeof(ngtcp2_range), + mem); + + rob->chunk = chunk; + rob->mem = mem; + + return 0; + +fail_gapksl_ksl_insert: + ngtcp2_rob_gap_del(g, mem); +fail_rob_gap_new: + ngtcp2_ksl_free(&rob->gapksl); + return rv; +} + +void ngtcp2_rob_free(ngtcp2_rob *rob) { + ngtcp2_ksl_it it; + + if (rob == NULL) { + return; + } + + for (it = ngtcp2_ksl_begin(&rob->dataksl); !ngtcp2_ksl_it_end(&it); + ngtcp2_ksl_it_next(&it)) { + ngtcp2_rob_data_del(ngtcp2_ksl_it_get(&it), rob->mem); + } + + for (it = ngtcp2_ksl_begin(&rob->gapksl); !ngtcp2_ksl_it_end(&it); + ngtcp2_ksl_it_next(&it)) { + ngtcp2_rob_gap_del(ngtcp2_ksl_it_get(&it), rob->mem); + } + + ngtcp2_ksl_free(&rob->dataksl); + ngtcp2_ksl_free(&rob->gapksl); +} + +static int rob_write_data(ngtcp2_rob *rob, uint64_t offset, const uint8_t *data, + size_t len) { + size_t n; + int rv; + ngtcp2_rob_data *d; + ngtcp2_range range = {offset, offset + len}; + ngtcp2_ksl_it it; + + for (it = ngtcp2_ksl_lower_bound_compar(&rob->dataksl, &range, + ngtcp2_ksl_range_exclusive_compar); + len; ngtcp2_ksl_it_next(&it)) { + if (ngtcp2_ksl_it_end(&it)) { + d = NULL; + } else { + d = ngtcp2_ksl_it_get(&it); + } + + if (d == NULL || offset < d->range.begin) { + rv = ngtcp2_rob_data_new(&d, (offset / rob->chunk) * rob->chunk, + rob->chunk, rob->mem); + if (rv != 0) { + return rv; + } + + rv = ngtcp2_ksl_insert(&rob->dataksl, &it, &d->range, d); + if (rv != 0) { + ngtcp2_rob_data_del(d, rob->mem); + return rv; + } + } + + n = (size_t)ngtcp2_min((uint64_t)len, d->range.begin + rob->chunk - offset); + memcpy(d->begin + (offset - d->range.begin), data, n); + offset += n; + data += n; + len -= n; + } + + return 0; +} + +int 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, q = {offset, offset + datalen}; + ngtcp2_ksl_it it; + + it = ngtcp2_ksl_lower_bound_compar(&rob->gapksl, &q, + ngtcp2_ksl_range_exclusive_compar); + + for (; !ngtcp2_ksl_it_end(&it);) { + g = ngtcp2_ksl_it_get(&it); + + m = ngtcp2_range_intersect(&q, &g->range); + if (!ngtcp2_range_len(&m)) { + break; + } + if (ngtcp2_range_eq(&g->range, &m)) { + 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)); + if (rv != 0) { + return rv; + } + + continue; + } + ngtcp2_range_cut(&l, &r, &g->range, &m); + if (ngtcp2_range_len(&l)) { + ngtcp2_ksl_update_key(&rob->gapksl, &g->range, &l); + g->range = l; + + if (ngtcp2_range_len(&r)) { + ngtcp2_rob_gap *ng; + rv = ngtcp2_rob_gap_new(&ng, r.begin, r.end, rob->mem); + if (rv != 0) { + return rv; + } + rv = ngtcp2_ksl_insert(&rob->gapksl, &it, &ng->range, ng); + if (rv != 0) { + ngtcp2_rob_gap_del(ng, rob->mem); + return rv; + } + } + } else if (ngtcp2_range_len(&r)) { + ngtcp2_ksl_update_key(&rob->gapksl, &g->range, &r); + g->range = r; + } + rv = rob_write_data(rob, m.begin, data + (m.begin - offset), + (size_t)ngtcp2_range_len(&m)); + if (rv != 0) { + return rv; + } + ngtcp2_ksl_it_next(&it); + } + return 0; +} + +void ngtcp2_rob_remove_prefix(ngtcp2_rob *rob, uint64_t offset) { + ngtcp2_rob_gap *g; + ngtcp2_rob_data *d; + ngtcp2_ksl_it it; + + it = ngtcp2_ksl_begin(&rob->gapksl); + + for (; !ngtcp2_ksl_it_end(&it);) { + g = ngtcp2_ksl_it_get(&it); + if (offset <= g->range.begin) { + break; + } + if (offset < g->range.end) { + ngtcp2_range r = {offset, g->range.end}; + ngtcp2_ksl_update_key(&rob->gapksl, &g->range, &r); + g->range.begin = offset; + break; + } + ngtcp2_ksl_remove_hint(&rob->gapksl, &it, &it, &g->range); + ngtcp2_rob_gap_del(g, rob->mem); + } + + it = ngtcp2_ksl_begin(&rob->dataksl); + + for (; !ngtcp2_ksl_it_end(&it);) { + d = ngtcp2_ksl_it_get(&it); + if (offset < d->range.begin + rob->chunk) { + return; + } + ngtcp2_ksl_remove_hint(&rob->dataksl, &it, &it, &d->range); + ngtcp2_rob_data_del(d, rob->mem); + } +} + +size_t ngtcp2_rob_data_at(ngtcp2_rob *rob, const uint8_t **pdest, + uint64_t offset) { + ngtcp2_rob_gap *g; + ngtcp2_rob_data *d; + ngtcp2_ksl_it it; + + it = ngtcp2_ksl_begin(&rob->gapksl); + if (ngtcp2_ksl_it_end(&it)) { + return 0; + } + + g = ngtcp2_ksl_it_get(&it); + + if (g->range.begin <= offset) { + return 0; + } + + it = ngtcp2_ksl_begin(&rob->dataksl); + d = ngtcp2_ksl_it_get(&it); + + assert(d); + assert(d->range.begin <= offset); + assert(offset < d->range.begin + rob->chunk); + + *pdest = d->begin + (offset - d->range.begin); + + return (size_t)(ngtcp2_min(g->range.begin, d->range.begin + rob->chunk) - + offset); +} + +void ngtcp2_rob_pop(ngtcp2_rob *rob, uint64_t offset, size_t len) { + ngtcp2_ksl_it it; + ngtcp2_rob_data *d; + + it = ngtcp2_ksl_begin(&rob->dataksl); + d = ngtcp2_ksl_it_get(&it); + + assert(d); + + if (offset + len < d->range.begin + rob->chunk) { + return; + } + + ngtcp2_ksl_remove_hint(&rob->dataksl, NULL, &it, &d->range); + ngtcp2_rob_data_del(d, rob->mem); +} + +uint64_t ngtcp2_rob_first_gap_offset(ngtcp2_rob *rob) { + ngtcp2_ksl_it it = ngtcp2_ksl_begin(&rob->gapksl); + ngtcp2_rob_gap *g; + + if (ngtcp2_ksl_it_end(&it)) { + return UINT64_MAX; + } + + g = ngtcp2_ksl_it_get(&it); + + return g->range.begin; +} + +int ngtcp2_rob_data_buffered(ngtcp2_rob *rob) { + return ngtcp2_ksl_len(&rob->dataksl) != 0; +} diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_rob.h b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_rob.h new file mode 100644 index 0000000..6518d56 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_rob.h @@ -0,0 +1,191 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 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_ROB_H +#define NGTCP2_ROB_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_mem.h" +#include "ngtcp2_range.h" +#include "ngtcp2_ksl.h" + +/* + * ngtcp2_rob_gap represents the gap, which is the range of stream + * data that is not received yet. + */ +typedef struct ngtcp2_rob_gap { + /* range is the range of this gap. */ + ngtcp2_range range; +} ngtcp2_rob_gap; + +/* + * ngtcp2_rob_gap_new allocates new ngtcp2_rob_gap object, and assigns + * its pointer to |*pg|. The caller should call ngtcp2_rob_gap_del to + * delete it when it is no longer used. The range of the gap is + * [begin, end). |mem| is custom memory allocator to allocate memory. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +int ngtcp2_rob_gap_new(ngtcp2_rob_gap **pg, uint64_t begin, uint64_t end, + const ngtcp2_mem *mem); + +/* + * ngtcp2_rob_gap_del deallocates |g|. It deallocates the memory + * pointed by |g| it self. |mem| is custom memory allocator to + * deallocate memory. + */ +void ngtcp2_rob_gap_del(ngtcp2_rob_gap *g, const ngtcp2_mem *mem); + +/* + * ngtcp2_rob_data holds the buffered stream data. + */ +typedef struct ngtcp2_rob_data { + /* range is the range of this gap. */ + ngtcp2_range range; + /* begin points to the buffer. */ + uint8_t *begin; + /* end points to the one beyond of the last byte of the buffer */ + uint8_t *end; +} ngtcp2_rob_data; + +/* + * ngtcp2_rob_data_new allocates new ngtcp2_rob_data object, and + * assigns its pointer to |*pd|. The caller should call + * ngtcp2_rob_data_del to delete it when it is no longer used. + * |offset| is the stream offset of the first byte of this data. + * |chunk| is the size of the buffer. |offset| must be multiple of + * |chunk|. |mem| is custom memory allocator to allocate memory. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +int ngtcp2_rob_data_new(ngtcp2_rob_data **pd, uint64_t offset, size_t chunk, + const ngtcp2_mem *mem); + +/* + * ngtcp2_rob_data_del deallocates |d|. It deallocates the memory + * pointed by |d| itself. |mem| is custom memory allocator to + * deallocate memory. + */ +void ngtcp2_rob_data_del(ngtcp2_rob_data *d, const ngtcp2_mem *mem); + +/* + * ngtcp2_rob is the reorder buffer which reassembles stream data + * received in out of order. + */ +typedef struct ngtcp2_rob { + /* gapksl maintains the range of offset which is not received + yet. Initially, its range is [0, UINT64_MAX). */ + ngtcp2_ksl gapksl; + /* dataksl maintains the list of buffers which store received data + ordered by stream offset. */ + ngtcp2_ksl dataksl; + /* mem is custom memory allocator */ + const ngtcp2_mem *mem; + /* chunk is the size of each buffer in data field */ + size_t chunk; +} ngtcp2_rob; + +/* + * ngtcp2_rob_init initializes |rob|. |chunk| is the size of buffer + * per chunk. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +int ngtcp2_rob_init(ngtcp2_rob *rob, size_t chunk, const ngtcp2_mem *mem); + +/* + * ngtcp2_rob_free frees resources allocated for |rob|. + */ +void ngtcp2_rob_free(ngtcp2_rob *rob); + +/* + * ngtcp2_rob_push adds new data of length |datalen| at the stream + * offset |offset|. + * + * This function returns 0 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_rob_remove_prefix removes gap up to |offset|, exclusive. It + * also removes data buffer if it is completely included in |offset|. + */ +void ngtcp2_rob_remove_prefix(ngtcp2_rob *rob, uint64_t offset); + +/* + * ngtcp2_rob_data_at stores the pointer to the buffer of stream + * offset |offset| to |*pdest| if it is available, and returns the + * valid length of available data. If no data is available, it + * returns 0. + */ +size_t ngtcp2_rob_data_at(ngtcp2_rob *rob, const uint8_t **pdest, + uint64_t offset); + +/* + * ngtcp2_rob_pop clears data at stream offset |offset| of length + * |len|. + * + * |offset| must be the offset given in ngtcp2_rob_data_at. |len| + * must be the return value of ngtcp2_rob_data_at when |offset| is + * passed. + * + * Caller should call this function from offset 0 in non-decreasing + * order. + */ +void ngtcp2_rob_pop(ngtcp2_rob *rob, uint64_t offset, size_t len); + +/* + * ngtcp2_rob_first_gap_offset returns the offset to the first gap. + * If there is no gap, it returns UINT64_MAX. + */ +uint64_t ngtcp2_rob_first_gap_offset(ngtcp2_rob *rob); + +/* + * ngtcp2_rob_data_buffered returns nonzero if any data is buffered. + */ +int ngtcp2_rob_data_buffered(ngtcp2_rob *rob); + +#endif /* NGTCP2_ROB_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_rst.c b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_rst.c new file mode 100644 index 0000000..b8587e3 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_rst.c @@ -0,0 +1,136 @@ +/* + * 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. + */ +#include "ngtcp2_rst.h" + +#include <assert.h> + +#include "ngtcp2_rtb.h" +#include "ngtcp2_cc.h" +#include "ngtcp2_macro.h" +#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 = 0; + rs->tx_in_flight = 0; + rs->lost = 0; + rs->prior_lost = 0; + rs->send_elapsed = 0; + rs->ack_elapsed = 0; + rs->is_app_limited = 0; +} + +void ngtcp2_rst_init(ngtcp2_rst *rst) { + ngtcp2_rs_init(&rst->rs); + ngtcp2_window_filter_init(&rst->wf, 12); + rst->delivered = 0; + rst->delivered_ts = 0; + rst->first_sent_ts = 0; + rst->app_limited = 0; + rst->next_round_delivered = 0; + rst->round_count = 0; + rst->is_cwnd_limited = 0; + rst->lost = 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) { + 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.lost = rst->lost; +} + +void ngtcp2_rst_on_ack_recv(ngtcp2_rst *rst, ngtcp2_conn_stat *cstat, + uint64_t pkt_delivered) { + ngtcp2_rs *rs = &rst->rs; + uint64_t rate; + + if (rst->app_limited && rst->delivered > rst->app_limited) { + rst->app_limited = 0; + } + + if (pkt_delivered >= rst->next_round_delivered) { + rst->next_round_delivered = pkt_delivered; + ++rst->round_count; + } + + if (rs->prior_ts == 0) { + return; + } + + rs->interval = ngtcp2_max(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; + } + + if (!rs->interval) { + return; + } + + rate = rs->delivered * NGTCP2_SECONDS / rs->interval; + + if (rate > ngtcp2_window_filter_get_best(&rst->wf) || !rst->app_limited) { + ngtcp2_window_filter_update(&rst->wf, rate, rst->round_count); + cstat->delivery_rate_sec = ngtcp2_window_filter_get_best(&rst->wf); + } +} + +void ngtcp2_rst_update_rate_sample(ngtcp2_rst *rst, const ngtcp2_rtb_entry *ent, + ngtcp2_tstamp ts) { + ngtcp2_rs *rs = &rst->rs; + + rst->delivered += ent->pktlen; + rst->delivered_ts = ts; + + if (ent->rst.delivered > rs->prior_delivered) { + 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; + rst->first_sent_ts = ent->ts; + } +} + +void ngtcp2_rst_update_app_limited(ngtcp2_rst *rst, ngtcp2_conn_stat *cstat) { + (void)rst; + (void)cstat; + /* TODO Not implemented */ +} diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_rst.h b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_rst.h new file mode 100644 index 0000000..c9e1e16 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_rst.h @@ -0,0 +1,86 @@ +/* + * 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. + */ +#ifndef NGTCP2_RST_H +#define NGTCP2_RST_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_window_filter.h" + +typedef struct ngtcp2_rtb_entry ngtcp2_rtb_entry; +typedef struct ngtcp2_conn_stat ngtcp2_conn_stat; + +/** + * @struct + * + * ngtcp2_rs contains connection state for delivery rate estimation. + */ +typedef struct ngtcp2_rs { + ngtcp2_duration interval; + uint64_t delivered; + uint64_t prior_delivered; + ngtcp2_tstamp prior_ts; + uint64_t tx_in_flight; + uint64_t lost; + uint64_t prior_lost; + ngtcp2_duration send_elapsed; + ngtcp2_duration ack_elapsed; + int is_app_limited; +} ngtcp2_rs; + +void ngtcp2_rs_init(ngtcp2_rs *rs); + +/* + * ngtcp2_rst implements delivery rate estimation described in + * https://tools.ietf.org/html/draft-cheng-iccrg-delivery-rate-estimation-00 + */ +typedef struct ngtcp2_rst { + ngtcp2_rs rs; + ngtcp2_window_filter wf; + uint64_t delivered; + ngtcp2_tstamp delivered_ts; + ngtcp2_tstamp first_sent_ts; + uint64_t app_limited; + uint64_t next_round_delivered; + uint64_t round_count; + uint64_t lost; + int is_cwnd_limited; +} ngtcp2_rst; + +void ngtcp2_rst_init(ngtcp2_rst *rst); + +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, + uint64_t pkt_delivered); +void ngtcp2_rst_update_rate_sample(ngtcp2_rst *rst, const ngtcp2_rtb_entry *ent, + ngtcp2_tstamp ts); +void ngtcp2_rst_update_app_limited(ngtcp2_rst *rst, ngtcp2_conn_stat *cstat); + +#endif /* NGTCP2_RST_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_rtb.c b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_rtb.c new file mode 100644 index 0000000..6308261 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_rtb.c @@ -0,0 +1,1466 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 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_rtb.h" + +#include <assert.h> +#include <string.h> + +#include "ngtcp2_macro.h" +#include "ngtcp2_conn.h" +#include "ngtcp2_log.h" +#include "ngtcp2_vec.h" +#include "ngtcp2_cc.h" +#include "ngtcp2_rcvry.h" +#include "ngtcp2_rst.h" +#include "ngtcp2_unreachable.h" +#include "ngtcp2_tstamp.h" +#include "ngtcp2_frame_chain.h" + +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->next = NULL; +} + +int ngtcp2_rtb_entry_objalloc_new(ngtcp2_rtb_entry **pent, + const ngtcp2_pkt_hd *hd, + ngtcp2_frame_chain *frc, ngtcp2_tstamp ts, + size_t pktlen, uint16_t flags, + ngtcp2_objalloc *objalloc) { + *pent = ngtcp2_objalloc_rtb_entry_get(objalloc); + if (*pent == NULL) { + return NGTCP2_ERR_NOMEM; + } + + rtb_entry_init(*pent, hd, frc, ts, pktlen, flags); + + return 0; +} + +void ngtcp2_rtb_entry_objalloc_del(ngtcp2_rtb_entry *ent, + ngtcp2_objalloc *objalloc, + ngtcp2_objalloc *frc_objalloc, + const ngtcp2_mem *mem) { + ngtcp2_frame_chain_list_objalloc_del(ent->frc, frc_objalloc, mem); + + ent->frc = NULL; + + ngtcp2_objalloc_rtb_entry_release(objalloc, ent); +} + +static int greater(const ngtcp2_ksl_key *lhs, const ngtcp2_ksl_key *rhs) { + return *(int64_t *)lhs > *(int64_t *)rhs; +} + +void ngtcp2_rtb_init(ngtcp2_rtb *rtb, ngtcp2_pktns_id pktns_id, + ngtcp2_strm *crypto, ngtcp2_rst *rst, ngtcp2_cc *cc, + int64_t cc_pkt_num, ngtcp2_log *log, ngtcp2_qlog *qlog, + ngtcp2_objalloc *rtb_entry_objalloc, + ngtcp2_objalloc *frc_objalloc, const ngtcp2_mem *mem) { + rtb->rtb_entry_objalloc = rtb_entry_objalloc; + rtb->frc_objalloc = frc_objalloc; + ngtcp2_ksl_init(&rtb->ents, greater, sizeof(int64_t), mem); + rtb->crypto = crypto; + rtb->rst = rst; + rtb->cc = cc; + rtb->log = log; + rtb->qlog = qlog; + rtb->mem = mem; + rtb->largest_acked_tx_pkt_num = -1; + rtb->num_ack_eliciting = 0; + rtb->num_retransmittable = 0; + rtb->num_pto_eliciting = 0; + rtb->probe_pkt_left = 0; + rtb->pktns_id = pktns_id; + rtb->cc_pkt_num = cc_pkt_num; + rtb->cc_bytes_in_flight = 0; + rtb->persistent_congestion_start_ts = UINT64_MAX; + rtb->num_lost_pkts = 0; + rtb->num_lost_pmtud_pkts = 0; +} + +void ngtcp2_rtb_free(ngtcp2_rtb *rtb) { + ngtcp2_ksl_it it; + + if (rtb == NULL) { + return; + } + + it = ngtcp2_ksl_begin(&rtb->ents); + + for (; !ngtcp2_ksl_it_end(&it); ngtcp2_ksl_it_next(&it)) { + ngtcp2_rtb_entry_objalloc_del(ngtcp2_ksl_it_get(&it), + rtb->rtb_entry_objalloc, rtb->frc_objalloc, + rtb->mem); + } + + ngtcp2_ksl_free(&rtb->ents); +} + +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_update_app_limited(rtb->rst, cstat); + + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) { + ++rtb->num_ack_eliciting; + } + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE) { + ++rtb->num_retransmittable; + } + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING) { + ++rtb->num_pto_eliciting; + } +} + +static size_t rtb_on_remove(ngtcp2_rtb *rtb, ngtcp2_rtb_entry *ent, + ngtcp2_conn_stat *cstat) { + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED) { + assert(rtb->num_lost_pkts); + --rtb->num_lost_pkts; + + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE) { + assert(rtb->num_lost_pmtud_pkts); + --rtb->num_lost_pmtud_pkts; + } + + return 0; + } + + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) { + assert(rtb->num_ack_eliciting); + --rtb->num_ack_eliciting; + } + + if ((ent->flags & NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE) && + !(ent->flags & NGTCP2_RTB_ENTRY_FLAG_PTO_RECLAIMED)) { + assert(rtb->num_retransmittable); + --rtb->num_retransmittable; + } + + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING) { + assert(rtb->num_pto_eliciting); + --rtb->num_pto_eliciting; + } + + if (rtb->cc_pkt_num <= ent->hd.pkt_num) { + assert(cstat->bytes_in_flight >= ent->pktlen); + cstat->bytes_in_flight -= ent->pktlen; + + assert(rtb->cc_bytes_in_flight >= ent->pktlen); + rtb->cc_bytes_in_flight -= ent->pktlen; + + /* If PMTUD packet is lost, we do not report the lost bytes to the + caller in order to ignore loss of PMTUD packet. */ + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE) { + return 0; + } + + return ent->pktlen; + } + + return 0; +} + +/* NGTCP2_RECLAIM_FLAG_NONE indicates that no flag is set. */ +#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 + +/* + * rtb_reclaim_frame queues unacknowledged frames included in |ent| + * for retransmission. The re-queued frames are not deleted from + * |ent|. It returns the number of frames queued. |flags| is bitwise + * OR of 0 or more of NGTCP2_RECLAIM_FLAG_*. + */ +static ngtcp2_ssize rtb_reclaim_frame(ngtcp2_rtb *rtb, uint8_t flags, + ngtcp2_conn *conn, ngtcp2_pktns *pktns, + ngtcp2_rtb_entry *ent) { + ngtcp2_frame_chain *frc, *nfrc, **pfrc = &pktns->tx.frq; + ngtcp2_frame *fr; + ngtcp2_strm *strm; + ngtcp2_range gap, range; + size_t num_reclaimed = 0; + int rv; + + assert(ent->flags & NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE); + + /* TODO Reconsider the order of pfrc */ + for (frc = ent->frc; frc; frc = frc->next) { + fr = &frc->fr; + /* Check that a late ACK acknowledged this frame. */ + if (frc->binder && + (frc->binder->flags & NGTCP2_FRAME_CHAIN_BINDER_FLAG_ACK)) { + continue; + } + switch (frc->fr.type) { + case NGTCP2_FRAME_STREAM: + strm = ngtcp2_conn_find_stream(conn, fr->stream.stream_id); + if (strm == NULL) { + continue; + } + + gap = ngtcp2_strm_get_unacked_range_after(strm, fr->stream.offset); + + range.begin = fr->stream.offset; + range.end = fr->stream.offset + + ngtcp2_vec_len(fr->stream.data, fr->stream.datacnt); + range = ngtcp2_range_intersect(&range, &gap); + if (ngtcp2_range_len(&range) == 0) { + if (!fr->stream.fin) { + /* 0 length STREAM frame with offset == 0 must be + retransmitted if no non-empty data is sent to this stream + and no data in this stream is acknowledged. */ + if (fr->stream.offset != 0 || fr->stream.datacnt != 0 || + strm->tx.offset || (strm->flags & NGTCP2_STRM_FLAG_ANY_ACKED)) { + continue; + } + } else if (strm->flags & NGTCP2_STRM_FLAG_FIN_ACKED) { + continue; + } + } + + if ((flags & NGTCP2_RECLAIM_FLAG_ON_LOSS) && + ent->hd.pkt_num != strm->tx.last_lost_pkt_num) { + strm->tx.last_lost_pkt_num = ent->hd.pkt_num; + ++strm->tx.loss_count; + } + + rv = ngtcp2_frame_chain_stream_datacnt_objalloc_new( + &nfrc, fr->stream.datacnt, rtb->frc_objalloc, rtb->mem); + if (rv != 0) { + return rv; + } + + nfrc->fr = *fr; + ngtcp2_vec_copy(nfrc->fr.stream.data, fr->stream.data, + fr->stream.datacnt); + + rv = ngtcp2_strm_streamfrq_push(strm, nfrc); + if (rv != 0) { + ngtcp2_frame_chain_objalloc_del(nfrc, rtb->frc_objalloc, rtb->mem); + return rv; + } + if (!ngtcp2_strm_is_tx_queued(strm)) { + strm->cycle = ngtcp2_conn_tx_strmq_first_cycle(conn); + rv = ngtcp2_conn_tx_strmq_push(conn, strm); + if (rv != 0) { + return rv; + } + } + + ++num_reclaimed; + + continue; + case NGTCP2_FRAME_CRYPTO: + /* Don't resend CRYPTO frame if the whole region it contains has + been acknowledged */ + gap = ngtcp2_strm_get_unacked_range_after(rtb->crypto, fr->stream.offset); + + range.begin = fr->stream.offset; + range.end = fr->stream.offset + + ngtcp2_vec_len(fr->stream.data, fr->stream.datacnt); + range = ngtcp2_range_intersect(&range, &gap); + if (ngtcp2_range_len(&range) == 0) { + continue; + } + + rv = ngtcp2_frame_chain_stream_datacnt_objalloc_new( + &nfrc, fr->stream.datacnt, rtb->frc_objalloc, rtb->mem); + if (rv != 0) { + return rv; + } + + nfrc->fr = *fr; + ngtcp2_vec_copy(nfrc->fr.stream.data, fr->stream.data, + fr->stream.datacnt); + + rv = ngtcp2_strm_streamfrq_push(&pktns->crypto.strm, nfrc); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + ngtcp2_frame_chain_objalloc_del(nfrc, rtb->frc_objalloc, rtb->mem); + return rv; + } + + ++num_reclaimed; + + continue; + case NGTCP2_FRAME_NEW_TOKEN: + rv = ngtcp2_frame_chain_new_token_objalloc_new( + &nfrc, fr->new_token.token, fr->new_token.tokenlen, rtb->frc_objalloc, + rtb->mem); + if (rv != 0) { + return rv; + } + + rv = ngtcp2_bind_frame_chains(frc, nfrc, rtb->mem); + if (rv != 0) { + return rv; + } + + break; + case NGTCP2_FRAME_DATAGRAM: + case NGTCP2_FRAME_DATAGRAM_LEN: + continue; + default: + rv = ngtcp2_frame_chain_objalloc_new(&nfrc, rtb->frc_objalloc); + if (rv != 0) { + return rv; + } + + nfrc->fr = *fr; + + rv = ngtcp2_bind_frame_chains(frc, nfrc, rtb->mem); + if (rv != 0) { + return rv; + } + + break; + } + + ++num_reclaimed; + + nfrc->next = *pfrc; + *pfrc = nfrc; + pfrc = &nfrc->next; + } + + return (ngtcp2_ssize)num_reclaimed; +} + +/* + * conn_process_lost_datagram calls ngtcp2_lost_datagram callback for + * lost DATAGRAM frames. + */ +static int conn_process_lost_datagram(ngtcp2_conn *conn, + ngtcp2_rtb_entry *ent) { + ngtcp2_frame_chain *frc; + int rv; + + for (frc = ent->frc; frc; frc = frc->next) { + switch (frc->fr.type) { + case NGTCP2_FRAME_DATAGRAM: + case NGTCP2_FRAME_DATAGRAM_LEN: + assert(conn->callbacks.lost_datagram); + + rv = conn->callbacks.lost_datagram(conn, frc->fr.datagram.dgram_id, + conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + break; + } + } + + return 0; +} + +static int rtb_on_pkt_lost(ngtcp2_rtb *rtb, ngtcp2_ksl_it *it, + ngtcp2_rtb_entry *ent, ngtcp2_conn_stat *cstat, + ngtcp2_conn *conn, ngtcp2_pktns *pktns, + ngtcp2_tstamp ts) { + int rv; + ngtcp2_ssize reclaimed; + ngtcp2_cc *cc = rtb->cc; + ngtcp2_cc_pkt pkt; + + ngtcp2_log_pkt_lost(rtb->log, ent->hd.pkt_num, ent->hd.type, ent->hd.flags, + ent->ts); + + if (rtb->qlog) { + ngtcp2_qlog_pkt_lost(rtb->qlog, ent); + } + + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE) { + ++rtb->num_lost_pmtud_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, + rtb->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); + assert(!(ent->flags & NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED)); + assert(UINT64_MAX == ent->lost_ts); + + ent->flags |= NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED; + ent->lost_ts = ts; + + ++rtb->num_lost_pkts; + + ngtcp2_ksl_it_next(it); + + return 0; + } + + if (conn->callbacks.lost_datagram && + (ent->flags & NGTCP2_RTB_ENTRY_FLAG_DATAGRAM)) { + rv = conn_process_lost_datagram(conn, ent); + if (rv != 0) { + return rv; + } + } + + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE) { + assert(ent->frc); + assert(!(ent->flags & NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED)); + assert(UINT64_MAX == ent->lost_ts); + + reclaimed = + rtb_reclaim_frame(rtb, NGTCP2_RECLAIM_FLAG_ON_LOSS, conn, pktns, ent); + if (reclaimed < 0) { + return (int)reclaimed; + } + } + + ent->flags |= NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED; + ent->lost_ts = ts; + + ++rtb->num_lost_pkts; + + ngtcp2_ksl_it_next(it); + + return 0; +} + +int ngtcp2_rtb_add(ngtcp2_rtb *rtb, ngtcp2_rtb_entry *ent, + ngtcp2_conn_stat *cstat) { + int rv; + + rv = ngtcp2_ksl_insert(&rtb->ents, NULL, &ent->hd.pkt_num, ent); + if (rv != 0) { + return rv; + } + + rtb_on_add(rtb, ent, cstat); + + return 0; +} + +ngtcp2_ksl_it ngtcp2_rtb_head(ngtcp2_rtb *rtb) { + return ngtcp2_ksl_begin(&rtb->ents); +} + +static void rtb_remove(ngtcp2_rtb *rtb, ngtcp2_ksl_it *it, + ngtcp2_rtb_entry **pent, ngtcp2_rtb_entry *ent, + ngtcp2_conn_stat *cstat) { + int rv; + (void)rv; + + 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); + + ngtcp2_list_insert(ent, pent); +} + +static void conn_ack_crypto_data(ngtcp2_conn *conn, ngtcp2_pktns *pktns, + uint64_t datalen) { + ngtcp2_buf_chain **pbufchain, *bufchain; + size_t left; + + for (pbufchain = &pktns->crypto.tx.data; *pbufchain;) { + left = ngtcp2_buf_len(&(*pbufchain)->buf); + if (left > datalen) { + (*pbufchain)->buf.pos += datalen; + return; + } + + bufchain = *pbufchain; + *pbufchain = bufchain->next; + + ngtcp2_mem_free(conn->mem, bufchain); + + datalen -= left; + + if (datalen == 0) { + return; + } + } + + assert(datalen == 0); + + return; +} + +static int rtb_process_acked_pkt(ngtcp2_rtb *rtb, ngtcp2_rtb_entry *ent, + ngtcp2_conn *conn) { + ngtcp2_frame_chain *frc; + uint64_t prev_stream_offset, stream_offset; + ngtcp2_strm *strm; + int rv; + uint64_t datalen; + ngtcp2_strm *crypto = rtb->crypto; + ngtcp2_pktns *pktns = NULL; + + if ((ent->flags & NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE) && conn->pmtud && + conn->pmtud->tx_pkt_num <= ent->hd.pkt_num) { + ngtcp2_pmtud_probe_success(conn->pmtud, ent->pktlen); + + conn->dcid.current.max_udp_payload_size = + ngtcp2_max(conn->dcid.current.max_udp_payload_size, ent->pktlen); + + if (ngtcp2_pmtud_finished(conn->pmtud)) { + ngtcp2_conn_stop_pmtud(conn); + } + } + + for (frc = ent->frc; frc; frc = frc->next) { + if (frc->binder) { + if (frc->binder->flags & NGTCP2_FRAME_CHAIN_BINDER_FLAG_ACK) { + continue; + } + + frc->binder->flags |= NGTCP2_FRAME_CHAIN_BINDER_FLAG_ACK; + } + + switch (frc->fr.type) { + case NGTCP2_FRAME_STREAM: + strm = ngtcp2_conn_find_stream(conn, frc->fr.stream.stream_id); + if (strm == NULL) { + break; + } + + strm->flags |= NGTCP2_STRM_FLAG_ANY_ACKED; + + if (frc->fr.stream.fin) { + strm->flags |= NGTCP2_STRM_FLAG_FIN_ACKED; + } + + prev_stream_offset = ngtcp2_strm_get_acked_offset(strm); + rv = ngtcp2_strm_ack_data( + strm, frc->fr.stream.offset, + ngtcp2_vec_len(frc->fr.stream.data, frc->fr.stream.datacnt)); + if (rv != 0) { + return rv; + } + + if (conn->callbacks.acked_stream_data_offset) { + stream_offset = ngtcp2_strm_get_acked_offset(strm); + datalen = stream_offset - prev_stream_offset; + if (datalen == 0 && !frc->fr.stream.fin) { + break; + } + + rv = conn->callbacks.acked_stream_data_offset( + conn, strm->stream_id, prev_stream_offset, datalen, conn->user_data, + strm->stream_user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + } + + rv = ngtcp2_conn_close_stream_if_shut_rdwr(conn, strm); + if (rv != 0) { + return rv; + } + break; + case NGTCP2_FRAME_CRYPTO: + prev_stream_offset = ngtcp2_strm_get_acked_offset(crypto); + rv = ngtcp2_strm_ack_data( + crypto, frc->fr.stream.offset, + ngtcp2_vec_len(frc->fr.stream.data, frc->fr.stream.datacnt)); + if (rv != 0) { + return rv; + } + + stream_offset = ngtcp2_strm_get_acked_offset(crypto); + datalen = stream_offset - prev_stream_offset; + if (datalen == 0) { + break; + } + + switch (rtb->pktns_id) { + case NGTCP2_PKTNS_ID_INITIAL: + pktns = conn->in_pktns; + break; + case NGTCP2_PKTNS_ID_HANDSHAKE: + pktns = conn->hs_pktns; + break; + case NGTCP2_PKTNS_ID_APPLICATION: + pktns = &conn->pktns; + break; + default: + ngtcp2_unreachable(); + } + + conn_ack_crypto_data(conn, pktns, datalen); + + break; + case NGTCP2_FRAME_RESET_STREAM: + strm = ngtcp2_conn_find_stream(conn, frc->fr.reset_stream.stream_id); + if (strm == NULL) { + break; + } + strm->flags |= NGTCP2_STRM_FLAG_RESET_STREAM_ACKED; + rv = ngtcp2_conn_close_stream_if_shut_rdwr(conn, strm); + if (rv != 0) { + return rv; + } + break; + case NGTCP2_FRAME_RETIRE_CONNECTION_ID: + ngtcp2_conn_untrack_retired_dcid_seq(conn, + frc->fr.retire_connection_id.seq); + break; + case NGTCP2_FRAME_NEW_CONNECTION_ID: + assert(conn->scid.num_in_flight); + + --conn->scid.num_in_flight; + + break; + case NGTCP2_FRAME_DATAGRAM: + case NGTCP2_FRAME_DATAGRAM_LEN: + if (!conn->callbacks.ack_datagram) { + break; + } + + rv = conn->callbacks.ack_datagram(conn, frc->fr.datagram.dgram_id, + conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + break; + } + } + return 0; +} + +static void rtb_on_pkt_acked(ngtcp2_rtb *rtb, ngtcp2_rtb_entry *ent, + ngtcp2_conn_stat *cstat, ngtcp2_tstamp ts) { + ngtcp2_cc *cc = rtb->cc; + ngtcp2_cc_pkt pkt; + + ngtcp2_rst_update_rate_sample(rtb->rst, ent, ts); + + if (cc->on_pkt_acked) { + cc->on_pkt_acked(cc, cstat, + ngtcp2_cc_pkt_init(&pkt, ent->hd.pkt_num, ent->pktlen, + rtb->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_PROBE) && + (ent->flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING)) { + cstat->pto_count = 0; + } +} + +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) { + if (conn->tx.ecn.state == NGTCP2_ECN_STATE_FAILED) { + return; + } + + if ((ecn_acked && fr->type == NGTCP2_FRAME_ACK) || + (fr->type == NGTCP2_FRAME_ACK_ECN && + (pktns->rx.ecn.ack.ect0 > fr->ecn.ect0 || + pktns->rx.ecn.ack.ect1 > fr->ecn.ect1 || + pktns->rx.ecn.ack.ce > fr->ecn.ce || + (fr->ecn.ect0 - pktns->rx.ecn.ack.ect0) + + (fr->ecn.ce - pktns->rx.ecn.ack.ce) < + ecn_acked || + fr->ecn.ect0 > pktns->tx.ecn.ect0 || fr->ecn.ect1))) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, + "path is not ECN capable"); + conn->tx.ecn.state = NGTCP2_ECN_STATE_FAILED; + return; + } + + if (conn->tx.ecn.state != NGTCP2_ECN_STATE_CAPABLE && ecn_acked) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, "path is ECN capable"); + conn->tx.ecn.state = NGTCP2_ECN_STATE_CAPABLE; + } + + if (fr->type == NGTCP2_FRAME_ACK_ECN) { + if (cc->congestion_event && largest_pkt_sent_ts != UINT64_MAX && + fr->ecn.ce > pktns->rx.ecn.ack.ce) { + cc->congestion_event(cc, cstat, largest_pkt_sent_ts, ts); + } + + pktns->rx.ecn.ack.ect0 = fr->ecn.ect0; + pktns->rx.ecn.ack.ect1 = fr->ecn.ect1; + pktns->rx.ecn.ack.ce = fr->ecn.ce; + } +} + +static int rtb_detect_lost_pkt(ngtcp2_rtb *rtb, uint64_t *ppkt_lost, + ngtcp2_conn *conn, ngtcp2_pktns *pktns, + ngtcp2_conn_stat *cstat, ngtcp2_tstamp ts); + +ngtcp2_ssize ngtcp2_rtb_recv_ack(ngtcp2_rtb *rtb, const ngtcp2_ack *fr, + ngtcp2_conn_stat *cstat, ngtcp2_conn *conn, + ngtcp2_pktns *pktns, ngtcp2_tstamp pkt_ts, + ngtcp2_tstamp ts) { + ngtcp2_rtb_entry *ent; + int64_t largest_ack = fr->largest_ack, min_ack; + size_t i; + int rv; + ngtcp2_ksl_it it; + ngtcp2_ssize 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}; + size_t num_lost_pkts = rtb->num_lost_pkts - rtb->num_lost_pmtud_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->crypto.key_update.confirmed_ts = ts; + + ngtcp2_log_info(rtb->log, NGTCP2_LOG_EVENT_CRY, "key update confirmed"); + } + + if (rtb->largest_acked_tx_pkt_num < largest_ack) { + rtb->largest_acked_tx_pkt_num = largest_ack; + verify_ecn = 1; + } + + /* 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); + } + return 0; + } + + min_ack = largest_ack - (int64_t)fr->first_ack_range; + + for (; !ngtcp2_ksl_it_end(&it);) { + pkt_num = *(int64_t *)ngtcp2_ksl_it_key(&it); + + assert(pkt_num <= largest_ack); + + if (pkt_num < min_ack) { + break; + } + + ent = ngtcp2_ksl_it_get(&it); + + if (largest_ack == pkt_num) { + largest_pkt_sent_ts = ent->ts; + } + + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) { + ack_eliciting_pkt_acked = 1; + } + + rtb_remove(rtb, &it, &acked_ent, ent, cstat); + ++num_acked; + } + + for (i = 0; i < fr->rangecnt;) { + largest_ack = min_ack - (int64_t)fr->ranges[i].gap - 2; + min_ack = largest_ack - (int64_t)fr->ranges[i].len; + + it = ngtcp2_ksl_lower_bound(&rtb->ents, &largest_ack); + if (ngtcp2_ksl_it_end(&it)) { + break; + } + + for (; !ngtcp2_ksl_it_end(&it);) { + pkt_num = *(int64_t *)ngtcp2_ksl_it_key(&it); + if (pkt_num < min_ack) { + break; + } + ent = ngtcp2_ksl_it_get(&it); + + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) { + ack_eliciting_pkt_acked = 1; + } + + rtb_remove(rtb, &it, &acked_ent, ent, cstat); + ++num_acked; + } + + ++i; + } + + if (largest_pkt_sent_ts != UINT64_MAX && ack_eliciting_pkt_acked) { + cc_ack.rtt = pkt_ts - largest_pkt_sent_ts; + + 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); + } + } + + if (conn) { + for (ent = acked_ent; ent; ent = acked_ent) { + if (ent->hd.pkt_num >= pktns->tx.ecn.start_pkt_num && + (ent->flags & NGTCP2_RTB_ENTRY_FLAG_ECN)) { + ++ecn_acked; + } + + rv = rtb_process_acked_pkt(rtb, ent, conn); + if (rv != 0) { + goto fail; + } + + if (ent->hd.pkt_num >= rtb->cc_pkt_num) { + assert(cc_ack.pkt_delivered <= ent->rst.delivered); + + cc_ack.bytes_delivered += ent->pktlen; + cc_ack.pkt_delivered = ent->rst.delivered; + } + + rtb_on_pkt_acked(rtb, ent, cstat, 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, ts); + acked_ent = ent->next; + ngtcp2_rtb_entry_objalloc_del(ent, rtb->rtb_entry_objalloc, + rtb->frc_objalloc, rtb->mem); + } + } + + if (rtb->cc->on_spurious_congestion && num_lost_pkts && + rtb->num_lost_pkts - rtb->num_lost_pmtud_pkts == 0) { + rtb->cc->on_spurious_congestion(cc, cstat, ts); + } + + ngtcp2_rst_on_ack_recv(rtb->rst, cstat, cc_ack.pkt_delivered); + + if (conn && num_acked > 0) { + rv = rtb_detect_lost_pkt(rtb, &cc_ack.bytes_lost, conn, pktns, cstat, ts); + if (rv != 0) { + return rv; + } + } + + rtb->rst->lost += cc_ack.bytes_lost; + + cc_ack.largest_pkt_sent_ts = largest_pkt_sent_ts; + if (cc->on_ack_recv) { + cc->on_ack_recv(cc, cstat, &cc_ack, ts); + } + + return num_acked; + +fail: + for (ent = acked_ent; ent; ent = acked_ent) { + acked_ent = ent->next; + ngtcp2_rtb_entry_objalloc_del(ent, rtb->rtb_entry_objalloc, + rtb->frc_objalloc, rtb->mem); + } + + return rv; +} + +static int rtb_pkt_lost(ngtcp2_rtb *rtb, ngtcp2_conn_stat *cstat, + const ngtcp2_rtb_entry *ent, ngtcp2_duration loss_delay, + size_t pkt_thres, ngtcp2_tstamp ts) { + ngtcp2_tstamp loss_time; + + if (ngtcp2_tstamp_elapsed(ent->ts, loss_delay, ts) || + rtb->largest_acked_tx_pkt_num >= ent->hd.pkt_num + (int64_t)pkt_thres) { + return 1; + } + + loss_time = cstat->loss_time[rtb->pktns_id]; + + if (loss_time == UINT64_MAX) { + loss_time = ent->ts + loss_delay; + } else { + loss_time = ngtcp2_min(loss_time, ent->ts + loss_delay); + } + + cstat->loss_time[rtb->pktns_id] = loss_time; + + return 0; +} + +/* + * rtb_compute_pkt_loss_delay computes loss delay. + */ +static ngtcp2_duration compute_pkt_loss_delay(const ngtcp2_conn_stat *cstat) { + /* 9/8 is kTimeThreshold */ + ngtcp2_duration loss_delay = + ngtcp2_max(cstat->latest_rtt, cstat->smoothed_rtt) * 9 / 8; + return ngtcp2_max(loss_delay, NGTCP2_GRANULARITY); +} + +/* + * conn_all_ecn_pkt_lost returns nonzero if all ECN QUIC packets are + * lost during validation period. + */ +static int conn_all_ecn_pkt_lost(ngtcp2_conn *conn) { + ngtcp2_pktns *in_pktns = conn->in_pktns; + ngtcp2_pktns *hs_pktns = conn->hs_pktns; + ngtcp2_pktns *pktns = &conn->pktns; + + return (!in_pktns || in_pktns->tx.ecn.validation_pkt_sent == + in_pktns->tx.ecn.validation_pkt_lost) && + (!hs_pktns || hs_pktns->tx.ecn.validation_pkt_sent == + hs_pktns->tx.ecn.validation_pkt_lost) && + 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, + ngtcp2_conn *conn, ngtcp2_pktns *pktns, + ngtcp2_conn_stat *cstat, ngtcp2_tstamp ts) { + ngtcp2_rtb_entry *ent; + ngtcp2_duration loss_delay; + ngtcp2_ksl_it it; + ngtcp2_tstamp latest_ts, oldest_ts; + int64_t last_lost_pkt_num; + ngtcp2_duration loss_window, congestion_period; + ngtcp2_cc *cc = rtb->cc; + int rv; + uint64_t pkt_thres = + rtb->cc_bytes_in_flight / cstat->max_tx_udp_payload_size / 2; + size_t ecn_pkt_lost = 0; + ngtcp2_tstamp start_ts; + ngtcp2_duration pto = ngtcp2_conn_compute_pto(conn, pktns); + uint64_t bytes_lost = 0; + ngtcp2_duration max_ack_delay; + + pkt_thres = ngtcp2_max(pkt_thres, NGTCP2_PKT_THRESHOLD); + pkt_thres = ngtcp2_min(pkt_thres, 256); + cstat->loss_time[rtb->pktns_id] = UINT64_MAX; + loss_delay = compute_pkt_loss_delay(cstat); + + it = ngtcp2_ksl_lower_bound(&rtb->ents, &rtb->largest_acked_tx_pkt_num); + for (; !ngtcp2_ksl_it_end(&it); ngtcp2_ksl_it_next(&it)) { + ent = ngtcp2_ksl_it_get(&it); + + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED) { + break; + } + + if (rtb_pkt_lost(rtb, cstat, ent, loss_delay, (size_t)pkt_thres, ts)) { + /* All entries from ent are considered to be lost. */ + latest_ts = oldest_ts = ent->ts; + /* +1 to pick this packet for persistent congestion in the + following loop. */ + last_lost_pkt_num = ent->hd.pkt_num + 1; + max_ack_delay = conn->remote.transport_params + ? conn->remote.transport_params->max_ack_delay + : 0; + + congestion_period = + (cstat->smoothed_rtt + + ngtcp2_max(4 * cstat->rttvar, NGTCP2_GRANULARITY) + max_ack_delay) * + NGTCP2_PERSISTENT_CONGESTION_THRESHOLD; + + start_ts = ngtcp2_max(rtb->persistent_congestion_start_ts, + cstat->first_rtt_sample_ts); + + for (; !ngtcp2_ksl_it_end(&it);) { + ent = ngtcp2_ksl_it_get(&it); + + if (last_lost_pkt_num == ent->hd.pkt_num + 1 && ent->ts >= start_ts) { + last_lost_pkt_num = ent->hd.pkt_num; + oldest_ts = ent->ts; + } else { + last_lost_pkt_num = -1; + } + + if ((ent->flags & NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED)) { + if (rtb->pktns_id != NGTCP2_PKTNS_ID_APPLICATION || + last_lost_pkt_num == -1 || + latest_ts - oldest_ts >= congestion_period) { + break; + } + ngtcp2_ksl_it_next(&it); + continue; + } + + if (ent->hd.pkt_num >= pktns->tx.ecn.start_pkt_num && + (ent->flags & NGTCP2_RTB_ENTRY_FLAG_ECN)) { + ++ecn_pkt_lost; + } + + bytes_lost += rtb_on_remove(rtb, ent, cstat); + rv = rtb_on_pkt_lost(rtb, &it, ent, cstat, conn, pktns, ts); + if (rv != 0) { + return rv; + } + } + + /* If only PMTUD packets are lost, do not trigger congestion + event. */ + if (bytes_lost == 0) { + break; + } + + switch (conn->tx.ecn.state) { + case NGTCP2_ECN_STATE_TESTING: + if (conn->tx.ecn.validation_start_ts == UINT64_MAX) { + break; + } + if (ts - conn->tx.ecn.validation_start_ts < 3 * pto) { + pktns->tx.ecn.validation_pkt_lost += ecn_pkt_lost; + assert(pktns->tx.ecn.validation_pkt_sent >= + pktns->tx.ecn.validation_pkt_lost); + break; + } + conn->tx.ecn.state = NGTCP2_ECN_STATE_UNKNOWN; + /* fall through */ + case NGTCP2_ECN_STATE_UNKNOWN: + pktns->tx.ecn.validation_pkt_lost += ecn_pkt_lost; + assert(pktns->tx.ecn.validation_pkt_sent >= + pktns->tx.ecn.validation_pkt_lost); + if (conn_all_ecn_pkt_lost(conn)) { + conn->tx.ecn.state = NGTCP2_ECN_STATE_FAILED; + } + break; + default: + break; + } + + if (cc->congestion_event) { + cc->congestion_event(cc, cstat, latest_ts, ts); + } + + loss_window = latest_ts - oldest_ts; + /* Persistent congestion situation is only evaluated for app + * packet number space and for the packets sent after handshake + * is confirmed. During handshake, there is not much packets + * sent and also people seem to do lots of effort not to trigger + * persistent congestion there, then it is a lot easier to just + * not enable it during handshake. + */ + if (rtb->pktns_id == NGTCP2_PKTNS_ID_APPLICATION && loss_window > 0) { + if (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); + + /* Reset min_rtt, srtt, and rttvar here. Next new RTT + sample will be used to recalculate these values. */ + cstat->min_rtt = UINT64_MAX; + cstat->smoothed_rtt = conn->local.settings.initial_rtt; + cstat->rttvar = conn->local.settings.initial_rtt / 2; + cstat->first_rtt_sample_ts = UINT64_MAX; + + if (cc->on_persistent_congestion) { + cc->on_persistent_congestion(cc, cstat, ts); + } + } + } + + break; + } + } + + 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); +} + +void ngtcp2_rtb_remove_excessive_lost_pkt(ngtcp2_rtb *rtb, size_t n) { + ngtcp2_ksl_it it = ngtcp2_ksl_end(&rtb->ents); + ngtcp2_rtb_entry *ent; + int rv; + (void)rv; + + for (; rtb->num_lost_pkts > n;) { + assert(ngtcp2_ksl_it_end(&it)); + ngtcp2_ksl_it_prev(&it); + ent = ngtcp2_ksl_it_get(&it); + + 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); + + --rtb->num_lost_pkts; + + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE) { + --rtb->num_lost_pmtud_pkts; + } + + rv = ngtcp2_ksl_remove_hint(&rtb->ents, &it, &it, &ent->hd.pkt_num); + assert(0 == rv); + ngtcp2_rtb_entry_objalloc_del(ent, rtb->rtb_entry_objalloc, + rtb->frc_objalloc, rtb->mem); + } +} + +void ngtcp2_rtb_remove_expired_lost_pkt(ngtcp2_rtb *rtb, ngtcp2_duration pto, + ngtcp2_tstamp ts) { + ngtcp2_ksl_it it; + ngtcp2_rtb_entry *ent; + int rv; + (void)rv; + + if (ngtcp2_ksl_len(&rtb->ents) == 0) { + return; + } + + it = ngtcp2_ksl_end(&rtb->ents); + + for (;;) { + assert(ngtcp2_ksl_it_end(&it)); + + ngtcp2_ksl_it_prev(&it); + ent = ngtcp2_ksl_it_get(&it); + + if (!(ent->flags & NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED) || + ts - ent->lost_ts < pto) { + return; + } + + ngtcp2_log_info(rtb->log, NGTCP2_LOG_EVENT_LDC, + "removing stale lost pkn=%" PRId64, ent->hd.pkt_num); + + --rtb->num_lost_pkts; + + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE) { + --rtb->num_lost_pmtud_pkts; + } + + rv = ngtcp2_ksl_remove_hint(&rtb->ents, &it, &it, &ent->hd.pkt_num); + assert(0 == rv); + ngtcp2_rtb_entry_objalloc_del(ent, rtb->rtb_entry_objalloc, + rtb->frc_objalloc, rtb->mem); + + if (ngtcp2_ksl_len(&rtb->ents) == 0) { + return; + } + } +} + +ngtcp2_tstamp ngtcp2_rtb_lost_pkt_ts(ngtcp2_rtb *rtb) { + ngtcp2_ksl_it it; + ngtcp2_rtb_entry *ent; + + if (ngtcp2_ksl_len(&rtb->ents) == 0) { + return UINT64_MAX; + } + + it = ngtcp2_ksl_end(&rtb->ents); + ngtcp2_ksl_it_prev(&it); + ent = ngtcp2_ksl_it_get(&it); + + if (!(ent->flags & NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED)) { + return UINT64_MAX; + } + + return ent->lost_ts; +} + +static int rtb_on_pkt_lost_resched_move(ngtcp2_rtb *rtb, ngtcp2_conn *conn, + ngtcp2_pktns *pktns, + ngtcp2_rtb_entry *ent) { + ngtcp2_frame_chain **pfrc, *frc; + ngtcp2_stream *sfr; + ngtcp2_strm *strm; + int rv; + + ngtcp2_log_pkt_lost(rtb->log, ent->hd.pkt_num, ent->hd.type, ent->hd.flags, + ent->ts); + + if (rtb->qlog) { + ngtcp2_qlog_pkt_lost(rtb->qlog, ent); + } + + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_PROBE) { + ngtcp2_log_info(rtb->log, NGTCP2_LOG_EVENT_LDC, + "pkn=%" PRId64 + " is a probe packet, no retransmission is necessary", + ent->hd.pkt_num); + return 0; + } + + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE) { + ngtcp2_log_info(rtb->log, NGTCP2_LOG_EVENT_LDC, + "pkn=%" PRId64 + " is a PMTUD probe packet, no retransmission is necessary", + ent->hd.pkt_num); + return 0; + } + + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED) { + --rtb->num_lost_pkts; + + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE) { + --rtb->num_lost_pmtud_pkts; + } + + ngtcp2_log_info(rtb->log, NGTCP2_LOG_EVENT_LDC, + "pkn=%" PRId64 + " was declared lost and has already been retransmitted", + ent->hd.pkt_num); + return 0; + } + + 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); + return 0; + } + + if (!(ent->flags & NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE) && + (!(ent->flags & NGTCP2_RTB_ENTRY_FLAG_DATAGRAM) || + !conn->callbacks.lost_datagram)) { + /* PADDING only (or PADDING + ACK ) packets will have NULL + ent->frc. */ + return 0; + } + + pfrc = &ent->frc; + + for (; *pfrc;) { + switch ((*pfrc)->fr.type) { + case NGTCP2_FRAME_STREAM: + frc = *pfrc; + + *pfrc = frc->next; + frc->next = NULL; + sfr = &frc->fr.stream; + + strm = ngtcp2_conn_find_stream(conn, sfr->stream_id); + if (!strm) { + ngtcp2_frame_chain_objalloc_del(frc, rtb->frc_objalloc, rtb->mem); + break; + } + rv = ngtcp2_strm_streamfrq_push(strm, frc); + if (rv != 0) { + ngtcp2_frame_chain_objalloc_del(frc, rtb->frc_objalloc, rtb->mem); + return rv; + } + if (!ngtcp2_strm_is_tx_queued(strm)) { + strm->cycle = ngtcp2_conn_tx_strmq_first_cycle(conn); + rv = ngtcp2_conn_tx_strmq_push(conn, strm); + if (rv != 0) { + return rv; + } + } + break; + case NGTCP2_FRAME_CRYPTO: + frc = *pfrc; + + *pfrc = frc->next; + frc->next = NULL; + + rv = ngtcp2_strm_streamfrq_push(&pktns->crypto.strm, frc); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + ngtcp2_frame_chain_objalloc_del(frc, rtb->frc_objalloc, rtb->mem); + return rv; + } + break; + case NGTCP2_FRAME_DATAGRAM: + case NGTCP2_FRAME_DATAGRAM_LEN: + frc = *pfrc; + + if (conn->callbacks.lost_datagram) { + rv = conn->callbacks.lost_datagram(conn, frc->fr.datagram.dgram_id, + conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + } + + *pfrc = (*pfrc)->next; + + ngtcp2_frame_chain_objalloc_del(frc, rtb->frc_objalloc, rtb->mem); + break; + default: + pfrc = &(*pfrc)->next; + } + } + + *pfrc = pktns->tx.frq; + pktns->tx.frq = ent->frc; + ent->frc = NULL; + + return 0; +} + +int ngtcp2_rtb_remove_all(ngtcp2_rtb *rtb, ngtcp2_conn *conn, + ngtcp2_pktns *pktns, ngtcp2_conn_stat *cstat) { + ngtcp2_rtb_entry *ent; + ngtcp2_ksl_it it; + int rv; + + it = ngtcp2_ksl_begin(&rtb->ents); + + for (; !ngtcp2_ksl_it_end(&it);) { + ent = ngtcp2_ksl_it_get(&it); + + rtb_on_remove(rtb, ent, cstat); + rv = ngtcp2_ksl_remove_hint(&rtb->ents, &it, &it, &ent->hd.pkt_num); + assert(0 == rv); + + rv = rtb_on_pkt_lost_resched_move(rtb, conn, pktns, ent); + ngtcp2_rtb_entry_objalloc_del(ent, rtb->rtb_entry_objalloc, + rtb->frc_objalloc, rtb->mem); + if (rv != 0) { + return rv; + } + } + + return 0; +} + +void ngtcp2_rtb_remove_early_data(ngtcp2_rtb *rtb, ngtcp2_conn_stat *cstat) { + ngtcp2_rtb_entry *ent; + ngtcp2_ksl_it it; + int rv; + (void)rv; + + it = ngtcp2_ksl_begin(&rtb->ents); + + for (; !ngtcp2_ksl_it_end(&it);) { + ent = ngtcp2_ksl_it_get(&it); + + if (ent->hd.type != NGTCP2_PKT_0RTT) { + ngtcp2_ksl_it_next(&it); + continue; + } + + rtb_on_remove(rtb, ent, cstat); + rv = ngtcp2_ksl_remove_hint(&rtb->ents, &it, &it, &ent->hd.pkt_num); + assert(0 == rv); + + ngtcp2_rtb_entry_objalloc_del(ent, rtb->rtb_entry_objalloc, + rtb->frc_objalloc, rtb->mem); + } +} + +int ngtcp2_rtb_empty(ngtcp2_rtb *rtb) { + return ngtcp2_ksl_len(&rtb->ents) == 0; +} + +void ngtcp2_rtb_reset_cc_state(ngtcp2_rtb *rtb, int64_t cc_pkt_num) { + rtb->cc_pkt_num = cc_pkt_num; + rtb->cc_bytes_in_flight = 0; +} + +ngtcp2_ssize ngtcp2_rtb_reclaim_on_pto(ngtcp2_rtb *rtb, ngtcp2_conn *conn, + ngtcp2_pktns *pktns, size_t num_pkts) { + ngtcp2_ksl_it it; + ngtcp2_rtb_entry *ent; + ngtcp2_ssize reclaimed; + size_t atmost = num_pkts; + + it = ngtcp2_ksl_end(&rtb->ents); + for (; !ngtcp2_ksl_it_begin(&it) && num_pkts >= 1;) { + ngtcp2_ksl_it_prev(&it); + ent = ngtcp2_ksl_it_get(&it); + + if ((ent->flags & (NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED | + NGTCP2_RTB_ENTRY_FLAG_PTO_RECLAIMED)) || + !(ent->flags & NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE)) { + continue; + } + + assert(ent->frc); + + reclaimed = + rtb_reclaim_frame(rtb, NGTCP2_RECLAIM_FLAG_NONE, conn, pktns, ent); + if (reclaimed < 0) { + return reclaimed; + } + + /* Mark reclaimed even if reclaimed == 0 so that we can skip it in + the next run. */ + ent->flags |= NGTCP2_RTB_ENTRY_FLAG_PTO_RECLAIMED; + + assert(rtb->num_retransmittable); + --rtb->num_retransmittable; + + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING) { + ent->flags &= (uint16_t)~NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING; + assert(rtb->num_pto_eliciting); + --rtb->num_pto_eliciting; + } + + if (reclaimed) { + --num_pkts; + } + } + + return (ngtcp2_ssize)(atmost - num_pkts); +} diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_rtb.h b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_rtb.h new file mode 100644 index 0000000..a1ff208 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_rtb.h @@ -0,0 +1,320 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 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_RTB_H +#define NGTCP2_RTB_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_pkt.h" +#include "ngtcp2_ksl.h" +#include "ngtcp2_pq.h" +#include "ngtcp2_objalloc.h" +#include "ngtcp2_pktns_id.h" + +typedef struct ngtcp2_conn ngtcp2_conn; +typedef struct ngtcp2_pktns ngtcp2_pktns; +typedef struct ngtcp2_log ngtcp2_log; +typedef struct ngtcp2_qlog ngtcp2_qlog; +typedef struct ngtcp2_strm ngtcp2_strm; +typedef struct ngtcp2_rst ngtcp2_rst; +typedef struct ngtcp2_cc ngtcp2_cc; +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 +/* NGTCP2_RTB_ENTRY_FLAG_PROBE indicates that the entry includes a + probe packet. */ +#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 +/* NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING indicates that the entry + elicits acknowledgement. */ +#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 +/* 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 +/* 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 +/* NGTCP2_RTB_ENTRY_FLAG_DATAGRAM indicates that the entry includes + DATAGRAM frame. */ +#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 +/* 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 + +typedef struct ngtcp2_rtb_entry ngtcp2_rtb_entry; + +/* + * ngtcp2_rtb_entry is an object stored in ngtcp2_rtb. It corresponds + * to the one packet which is waiting for its ACK. + */ +struct ngtcp2_rtb_entry { + union { + struct { + ngtcp2_rtb_entry *next; + + struct { + int64_t pkt_num; + uint8_t type; + uint8_t flags; + } hd; + ngtcp2_frame_chain *frc; + /* ts is the time point when a packet included in this entry is sent + to a peer. */ + ngtcp2_tstamp ts; + /* lost_ts is the time when this entry is marked lost. */ + ngtcp2_tstamp lost_ts; + /* pktlen is the length of QUIC packet */ + size_t pktlen; + struct { + uint64_t delivered; + ngtcp2_tstamp delivered_ts; + ngtcp2_tstamp first_sent_ts; + uint64_t tx_in_flight; + uint64_t lost; + int is_app_limited; + } rst; + /* flags is bitwise-OR of zero or more of + NGTCP2_RTB_ENTRY_FLAG_*. */ + uint16_t flags; + }; + + ngtcp2_opl_entry oplent; + }; +}; + +ngtcp2_objalloc_decl(rtb_entry, ngtcp2_rtb_entry, oplent); + +/* + * ngtcp2_rtb_entry_new allocates ngtcp2_rtb_entry object, and assigns + * its pointer to |*pent|. + */ +int ngtcp2_rtb_entry_objalloc_new(ngtcp2_rtb_entry **pent, + const ngtcp2_pkt_hd *hd, + ngtcp2_frame_chain *frc, ngtcp2_tstamp ts, + size_t pktlen, uint16_t flags, + ngtcp2_objalloc *objalloc); + +/* + * ngtcp2_rtb_entry_objalloc_del adds |ent| to |objalloc| for reuse. + * ngtcp2_frame_chain linked from ent->frc are also added to + * |frc_objalloc| depending on their frame type and size. + */ +void ngtcp2_rtb_entry_objalloc_del(ngtcp2_rtb_entry *ent, + ngtcp2_objalloc *objalloc, + ngtcp2_objalloc *frc_objalloc, + const ngtcp2_mem *mem); + +/* + * ngtcp2_rtb tracks sent packets, and its ACK timeout for + * retransmission. + */ +typedef struct ngtcp2_rtb { + ngtcp2_objalloc *frc_objalloc; + ngtcp2_objalloc *rtb_entry_objalloc; + /* ents includes ngtcp2_rtb_entry sorted by decreasing order of + packet number. */ + ngtcp2_ksl ents; + /* crypto is CRYPTO stream. */ + ngtcp2_strm *crypto; + ngtcp2_rst *rst; + ngtcp2_cc *cc; + ngtcp2_log *log; + ngtcp2_qlog *qlog; + const ngtcp2_mem *mem; + /* largest_acked_tx_pkt_num is the largest packet number + acknowledged by the peer. */ + int64_t largest_acked_tx_pkt_num; + /* num_ack_eliciting is the number of ACK eliciting entries. */ + size_t num_ack_eliciting; + /* num_retransmittable is the number of packets which contain frames + that must be retransmitted on loss. */ + size_t num_retransmittable; + /* num_pto_eliciting is the number of packets that elicit PTO probe + packets. */ + size_t num_pto_eliciting; + /* probe_pkt_left is the number of probe packet to send */ + size_t probe_pkt_left; + /* pktns_id is the identifier of packet number space. */ + ngtcp2_pktns_id pktns_id; + /* cc_pkt_num is the smallest packet number that is contributed to + ngtcp2_conn_stat.bytes_in_flight. */ + int64_t cc_pkt_num; + /* cc_bytes_in_flight is the number of in-flight bytes that is + contributed to ngtcp2_conn_stat.bytes_in_flight. It only + includes the bytes after congestion state is reset. */ + uint64_t cc_bytes_in_flight; + /* persistent_congestion_start_ts is the time when persistent + congestion evaluation is started. It happens roughly after + handshake is confirmed. */ + ngtcp2_tstamp persistent_congestion_start_ts; + /* num_lost_pkts is the number entries in ents which has + NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED flag set. */ + size_t num_lost_pkts; + /* num_lost_pmtud_pkts is the number of entries in ents which have + both NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED and + NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE flags set. */ + size_t num_lost_pmtud_pkts; +} ngtcp2_rtb; + +/* + * ngtcp2_rtb_init initializes |rtb|. + */ +void ngtcp2_rtb_init(ngtcp2_rtb *rtb, ngtcp2_pktns_id pktns_id, + ngtcp2_strm *crypto, ngtcp2_rst *rst, ngtcp2_cc *cc, + int64_t cc_pkt_num, ngtcp2_log *log, ngtcp2_qlog *qlog, + ngtcp2_objalloc *rtb_entry_objalloc, + ngtcp2_objalloc *frc_objalloc, const ngtcp2_mem *mem); + +/* + * ngtcp2_rtb_free deallocates resources allocated for |rtb|. + */ +void ngtcp2_rtb_free(ngtcp2_rtb *rtb); + +/* + * ngtcp2_rtb_add adds |ent| to |rtb|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory + */ +int ngtcp2_rtb_add(ngtcp2_rtb *rtb, ngtcp2_rtb_entry *ent, + ngtcp2_conn_stat *cstat); + +/* + * ngtcp2_rtb_head returns the iterator which points to the entry + * which has the largest packet number. If there is no entry, + * returned value satisfies ngtcp2_ksl_it_end(&it) != 0. + */ +ngtcp2_ksl_it ngtcp2_rtb_head(ngtcp2_rtb *rtb); + +/* + * ngtcp2_rtb_recv_ack removes acked ngtcp2_rtb_entry from |rtb|. + * |pkt_num| is a packet number which includes |fr|. |pkt_ts| is the + * timestamp when packet is received. |ts| should be the current + * time. Usually they are the same, but for buffered packets, + * |pkt_ts| would be earlier than |ts|. + * + * This function returns the number of newly acknowledged packets if + * it succeeds, or one of the following negative error codes: + * + * NGTCP2_ERR_CALLBACK_FAILURE + * User callback failed + * NGTCP2_ERR_NOMEM + * Out of memory + */ +ngtcp2_ssize ngtcp2_rtb_recv_ack(ngtcp2_rtb *rtb, const ngtcp2_ack *fr, + ngtcp2_conn_stat *cstat, ngtcp2_conn *conn, + ngtcp2_pktns *pktns, ngtcp2_tstamp pkt_ts, + ngtcp2_tstamp ts); + +/* + * ngtcp2_rtb_detect_lost_pkt detects lost packets and prepends the + * frames contained them to |*pfrc|. Even when this function fails, + * some frames might be prepended to |*pfrc| and the caller should + * handle them. + */ +int ngtcp2_rtb_detect_lost_pkt(ngtcp2_rtb *rtb, ngtcp2_conn *conn, + ngtcp2_pktns *pktns, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); + +/* + * ngtcp2_rtb_remove_expired_lost_pkt removes expired lost packet. + */ +void ngtcp2_rtb_remove_expired_lost_pkt(ngtcp2_rtb *rtb, ngtcp2_duration pto, + ngtcp2_tstamp ts); + +/* + * ngtcp2_rtb_lost_pkt_ts returns the earliest time when the still + * retained packet was lost. It returns UINT64_MAX if no such packet + * exists. + */ +ngtcp2_tstamp ngtcp2_rtb_lost_pkt_ts(ngtcp2_rtb *rtb); + +/* + * ngtcp2_rtb_remove_all removes all packets from |rtb| and prepends + * all frames to |*pfrc|. Even when this function fails, some frames + * might be prepended to |*pfrc| and the caller should handle them. + */ +int ngtcp2_rtb_remove_all(ngtcp2_rtb *rtb, ngtcp2_conn *conn, + ngtcp2_pktns *pktns, ngtcp2_conn_stat *cstat); + +/* + * ngtcp2_rtb_remove_early_data removes all entries for 0RTT packets. + */ +void ngtcp2_rtb_remove_early_data(ngtcp2_rtb *rtb, ngtcp2_conn_stat *cstat); + +/* + * ngtcp2_rtb_empty returns nonzero if |rtb| have no entry. + */ +int ngtcp2_rtb_empty(ngtcp2_rtb *rtb); + +/* + * ngtcp2_rtb_reset_cc_state resets congestion state in |rtb|. + * |cc_pkt_num| is the next outbound packet number which is sent under + * new congestion state. + */ +void ngtcp2_rtb_reset_cc_state(ngtcp2_rtb *rtb, int64_t cc_pkt_num); + +/* + * ngtcp2_rtb_remove_expired_lost_pkt ensures that the number of lost + * packets at most |n|. + */ +void ngtcp2_rtb_remove_excessive_lost_pkt(ngtcp2_rtb *rtb, size_t n); + +/* + * ngtcp2_rtb_reclaim_on_pto reclaims up to |num_pkts| packets which + * are in-flight and not marked lost to send them in PTO probe. The + * reclaimed frames are chained to |*pfrc|. + * + * This function returns the number of packets reclaimed if it + * succeeds, or one of the following negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory + */ +ngtcp2_ssize ngtcp2_rtb_reclaim_on_pto(ngtcp2_rtb *rtb, ngtcp2_conn *conn, + ngtcp2_pktns *pktns, size_t num_pkts); + +#endif /* NGTCP2_RTB_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_str.c b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_str.c new file mode 100644 index 0000000..a61636d --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_str.c @@ -0,0 +1,233 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 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_str.h" + +#include <string.h> + +#include "ngtcp2_macro.h" + +void *ngtcp2_cpymem(void *dest, const void *src, size_t n) { + memcpy(dest, src, n); + return (uint8_t *)dest + n; +} + +uint8_t *ngtcp2_setmem(uint8_t *dest, uint8_t b, size_t n) { + memset(dest, b, n); + return dest + n; +} + +const void *ngtcp2_get_bytes(void *dest, const void *src, size_t n) { + memcpy(dest, src, n); + return (uint8_t *)src + n; +} + +#define LOWER_XDIGITS "0123456789abcdef" + +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]; + } + + *p = '\0'; + + return dest; +} + +char *ngtcp2_encode_printable_ascii(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) { + *p++ = (char)c; + } else { + *p++ = '.'; + } + } + + *p = '\0'; + + 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) { + size_t i; + uint8_t *p = dest; + + p = write_uint(p, addr[0]); + + for (i = 1; i < 4; ++i) { + *p++ = '.'; + p = write_uint(p, addr[i]); + } + + *p = '\0'; + + return dest; +} + +/* + * write_hex_zsup writes the content of buffer pointed by |data| of + * 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) { + size_t i; + uint8_t *p = dest; + uint8_t d; + + for (i = 0; i < len; ++i) { + d = data[i]; + if (d >> 4) { + break; + } + + d &= 0xf; + + if (d) { + *p++ = (uint8_t)LOWER_XDIGITS[d]; + ++i; + break; + } + } + + if (p == dest && i == len) { + *p++ = '0'; + return p; + } + + for (; i < len; ++i) { + d = data[i]; + *p++ = (uint8_t)LOWER_XDIGITS[d >> 4]; + *p++ = (uint8_t)LOWER_XDIGITS[d & 0xf]; + } + + return p; +} + +uint8_t *ngtcp2_encode_ipv6(uint8_t *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; + + for (i = 0; i < 16; i += sizeof(uint16_t)) { + /* Copy in network byte order. */ + memcpy(&blks[i / sizeof(uint16_t)], addr + i, sizeof(uint16_t)); + } + + for (i = 0; i < 8;) { + if (blks[i]) { + ++i; + continue; + } + + zlen = 1; + zoff = i; + + ++i; + for (; i < 8 && blks[i] == 0; ++i, ++zlen) + ; + if (zlen > max_zlen) { + max_zlen = zlen; + max_zoff = zoff; + } + } + + /* Do not suppress a single '0' block */ + if (max_zlen == 1) { + max_zoff = 8; + } + + if (max_zoff != 0) { + p = write_hex_zsup(p, (const uint8_t *)blks, sizeof(uint16_t)); + + for (i = 1; i < max_zoff; ++i) { + *p++ = ':'; + p = write_hex_zsup(p, (const uint8_t *)(blks + i), sizeof(uint16_t)); + } + } + + if (max_zoff != 8) { + *p++ = ':'; + + if (max_zoff + max_zlen == 8) { + *p++ = ':'; + } else { + for (i = max_zoff + max_zlen; i < 8; ++i) { + *p++ = ':'; + p = write_hex_zsup(p, (const uint8_t *)(blks + i), sizeof(uint16_t)); + } + } + } + + *p = '\0'; + + return dest; +} + +int ngtcp2_cmemeq(const uint8_t *a, const uint8_t *b, size_t n) { + size_t i; + int rv = 0; + + for (i = 0; i < n; ++i) { + rv |= a[i] ^ b[i]; + } + + return rv == 0; +} diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_str.h b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_str.h new file mode 100644 index 0000000..deb75e3 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_str.h @@ -0,0 +1,94 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 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_STR_H +#define NGTCP2_STR_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +void *ngtcp2_cpymem(void *dest, const void *src, size_t n); + +/* + * ngtcp2_setmem writes a string of length |n| consisting only |b| to + * the buffer pointed by |dest|. It returns dest + n; + */ +uint8_t *ngtcp2_setmem(uint8_t *dest, uint8_t b, size_t n); + +/* + * ngtcp2_get_bytes copies |n| bytes from |src| to |dest|, and returns + * |src| + |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|. + */ +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 + * |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); + +/* + * ngtcp2_encode_ipv6 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 + * NULL byte. The function produces the canonical form of IPv6 text + * representation described in + * https://tools.ietf.org/html/rfc5952#section-4. The function + * returns |dest|. + */ +uint8_t *ngtcp2_encode_ipv6(uint8_t *dest, const uint8_t *addr); + +/* + * ngtcp2_encode_printable_ascii encodes |data| of length |len| in + * |dest| in the following manner: printable ascii characters are + * copied as is. The other characters are converted to ".". It + * 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); + +/* + * ngtcp2_cmemeq returns nonzero if the first |n| bytes of the buffers + * pointed by |a| and |b| are equal. The comparison is done in a + * constant time manner. + */ +int ngtcp2_cmemeq(const uint8_t *a, const uint8_t *b, size_t n); + +#endif /* NGTCP2_STR_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_strm.c b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_strm.c new file mode 100644 index 0000000..6bbeb8f --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_strm.c @@ -0,0 +1,698 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 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_strm.h" + +#include <string.h> +#include <assert.h> + +#include "ngtcp2_rtb.h" +#include "ngtcp2_pkt.h" +#include "ngtcp2_vec.h" +#include "ngtcp2_frame_chain.h" + +static int offset_less(const ngtcp2_ksl_key *lhs, const ngtcp2_ksl_key *rhs) { + return *(int64_t *)lhs < *(int64_t *)rhs; +} + +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->frc_objalloc = frc_objalloc; + strm->cycle = 0; + 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->stream_id = stream_id; + strm->flags = flags; + strm->stream_user_data = stream_user_data; + strm->rx.window = strm->rx.max_offset = strm->rx.unsent_max_offset = + max_rx_offset; + strm->pe.index = NGTCP2_PQ_BAD_INDEX; + strm->mem = mem; + strm->app_error_code = 0; +} + +void ngtcp2_strm_free(ngtcp2_strm *strm) { + ngtcp2_ksl_it it; + + if (strm == NULL) { + return; + } + + if (strm->tx.streamfrq) { + for (it = ngtcp2_ksl_begin(strm->tx.streamfrq); !ngtcp2_ksl_it_end(&it); + ngtcp2_ksl_it_next(&it)) { + ngtcp2_frame_chain_objalloc_del(ngtcp2_ksl_it_get(&it), + strm->frc_objalloc, strm->mem); + } + + ngtcp2_ksl_free(strm->tx.streamfrq); + ngtcp2_mem_free(strm->mem, strm->tx.streamfrq); + } + + if (strm->rx.rob) { + ngtcp2_rob_free(strm->rx.rob); + ngtcp2_mem_free(strm->mem, strm->rx.rob); + } + + if (strm->tx.acked_offset) { + ngtcp2_gaptr_free(strm->tx.acked_offset); + ngtcp2_mem_free(strm->mem, strm->tx.acked_offset); + } +} + +static int strm_rob_init(ngtcp2_strm *strm) { + int rv; + ngtcp2_rob *rob = ngtcp2_mem_malloc(strm->mem, sizeof(*rob)); + + if (rob == NULL) { + return NGTCP2_ERR_NOMEM; + } + + rv = ngtcp2_rob_init(rob, 8 * 1024, strm->mem); + if (rv != 0) { + ngtcp2_mem_free(strm->mem, rob); + return rv; + } + + strm->rx.rob = rob; + + return 0; +} + +uint64_t ngtcp2_strm_rx_offset(ngtcp2_strm *strm) { + if (strm->rx.rob == NULL) { + return strm->rx.cont_offset; + } + return ngtcp2_rob_first_gap_offset(strm->rx.rob); +} + +/* strm_rob_heavily_fragmented returns nonzero if the number of gaps + in |rob| exceeds the limit. */ +static int strm_rob_heavily_fragmented(ngtcp2_rob *rob) { + return ngtcp2_ksl_len(&rob->gapksl) >= 5000; +} + +int ngtcp2_strm_recv_reordering(ngtcp2_strm *strm, const uint8_t *data, + size_t datalen, uint64_t offset) { + int rv; + + if (strm->rx.rob == NULL) { + rv = strm_rob_init(strm); + if (rv != 0) { + return rv; + } + + if (strm->rx.cont_offset) { + ngtcp2_rob_remove_prefix(strm->rx.rob, strm->rx.cont_offset); + } + } + + if (strm_rob_heavily_fragmented(strm->rx.rob)) { + return NGTCP2_ERR_INTERNAL; + } + + return ngtcp2_rob_push(strm->rx.rob, offset, data, datalen); +} + +void ngtcp2_strm_update_rx_offset(ngtcp2_strm *strm, uint64_t offset) { + if (strm->rx.rob == NULL) { + strm->rx.cont_offset = offset; + return; + } + + ngtcp2_rob_remove_prefix(strm->rx.rob, offset); +} + +void ngtcp2_strm_shutdown(ngtcp2_strm *strm, uint32_t flags) { + strm->flags |= flags & NGTCP2_STRM_FLAG_SHUT_RDWR; +} + +static int strm_streamfrq_init(ngtcp2_strm *strm) { + ngtcp2_ksl *streamfrq = ngtcp2_mem_malloc(strm->mem, sizeof(*streamfrq)); + if (streamfrq == NULL) { + return NGTCP2_ERR_NOMEM; + } + + ngtcp2_ksl_init(streamfrq, offset_less, sizeof(uint64_t), strm->mem); + + strm->tx.streamfrq = streamfrq; + + return 0; +} + +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->next == NULL); + + if (strm->tx.streamfrq == NULL) { + rv = strm_streamfrq_init(strm); + if (rv != 0) { + return rv; + } + } + + return ngtcp2_ksl_insert(strm->tx.streamfrq, NULL, &frc->fr.stream.offset, + frc); +} + +static int strm_streamfrq_unacked_pop(ngtcp2_strm *strm, + ngtcp2_frame_chain **pfrc) { + ngtcp2_frame_chain *frc, *nfrc; + ngtcp2_stream *fr, *nfr; + uint64_t offset, end_offset; + size_t idx, end_idx; + uint64_t base_offset, end_base_offset; + ngtcp2_range gap; + ngtcp2_vec *v; + int rv; + ngtcp2_ksl_it it; + + *pfrc = NULL; + + assert(strm->tx.streamfrq); + assert(ngtcp2_ksl_len(strm->tx.streamfrq)); + + for (it = ngtcp2_ksl_begin(strm->tx.streamfrq); !ngtcp2_ksl_it_end(&it);) { + frc = ngtcp2_ksl_it_get(&it); + fr = &frc->fr.stream; + + ngtcp2_ksl_remove_hint(strm->tx.streamfrq, &it, &it, &fr->offset); + + idx = 0; + offset = fr->offset; + base_offset = 0; + + gap = ngtcp2_strm_get_unacked_range_after(strm, offset); + if (gap.begin < offset) { + gap.begin = offset; + } + + for (; idx < fr->datacnt && offset < gap.begin; ++idx) { + v = &fr->data[idx]; + if (offset + v->len > gap.begin) { + base_offset = gap.begin - offset; + break; + } + + offset += v->len; + } + + if (idx == fr->datacnt) { + if (fr->fin) { + if (strm->flags & NGTCP2_STRM_FLAG_FIN_ACKED) { + ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem); + assert(ngtcp2_ksl_len(strm->tx.streamfrq) == 0); + return 0; + } + + fr->offset += ngtcp2_vec_len(fr->data, fr->datacnt); + fr->datacnt = 0; + + *pfrc = frc; + + return 0; + } + + if (fr->offset == 0 && fr->datacnt == 0 && strm->tx.offset == 0 && + !(strm->flags & NGTCP2_STRM_FLAG_ANY_ACKED)) { + *pfrc = frc; + + return 0; + } + + ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem); + continue; + } + + assert(gap.begin == offset + base_offset); + + end_idx = idx; + end_offset = offset; + end_base_offset = 0; + + for (; end_idx < fr->datacnt; ++end_idx) { + v = &fr->data[end_idx]; + if (end_offset + v->len > gap.end) { + end_base_offset = gap.end - end_offset; + break; + } + + end_offset += v->len; + } + + if (fr->offset == offset && base_offset == 0 && fr->datacnt == end_idx) { + *pfrc = frc; + return 0; + } + + if (fr->datacnt == end_idx) { + memmove(fr->data, fr->data + idx, sizeof(fr->data[0]) * (end_idx - idx)); + + assert(fr->data[0].len > base_offset); + + fr->offset = offset + base_offset; + fr->datacnt = end_idx - idx; + fr->data[0].base += base_offset; + fr->data[0].len -= (size_t)base_offset; + + *pfrc = frc; + return 0; + } + + rv = ngtcp2_frame_chain_stream_datacnt_objalloc_new( + &nfrc, fr->datacnt - end_idx, 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; + memcpy(nfr->data, fr->data + end_idx, + sizeof(nfr->data[0]) * (fr->datacnt - end_idx)); + + assert(nfr->data[0].len > end_base_offset); + + nfr->type = fr->type; + nfr->flags = 0; + nfr->fin = fr->fin; + 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; + + rv = ngtcp2_ksl_insert(strm->tx.streamfrq, NULL, &nfr->offset, nfrc); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + ngtcp2_frame_chain_objalloc_del(nfrc, strm->frc_objalloc, strm->mem); + ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem); + return rv; + } + + if (end_base_offset) { + ++end_idx; + } + + memmove(fr->data, fr->data + idx, sizeof(fr->data[0]) * (end_idx - idx)); + + assert(fr->data[0].len > base_offset); + + fr->fin = 0; + fr->offset = offset + base_offset; + fr->datacnt = end_idx - idx; + if (end_base_offset) { + assert(fr->data[fr->datacnt - 1].len > end_base_offset); + 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; + + *pfrc = frc; + return 0; + } + + return 0; +} + +int ngtcp2_strm_streamfrq_pop(ngtcp2_strm *strm, ngtcp2_frame_chain **pfrc, + size_t left) { + ngtcp2_stream *fr, *nfr; + 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; + uint64_t unacked_offset; + + if (strm->tx.streamfrq == NULL || ngtcp2_ksl_len(strm->tx.streamfrq) == 0) { + *pfrc = NULL; + return 0; + } + + rv = strm_streamfrq_unacked_pop(strm, &frc); + if (rv != 0) { + return rv; + } + if (frc == NULL) { + *pfrc = NULL; + return 0; + } + + fr = &frc->fr.stream; + datalen = ngtcp2_vec_len(fr->data, fr->datacnt); + + /* datalen could be zero if 0 length STREAM has been sent */ + if (left == 0 && datalen) { + rv = ngtcp2_ksl_insert(strm->tx.streamfrq, NULL, &fr->offset, frc); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem); + return rv; + } + *pfrc = NULL; + return 0; + } + + if (datalen > left) { + ngtcp2_vec_copy(a, fr->data, fr->datacnt); + acnt = fr->datacnt; + + bcnt = 0; + ngtcp2_vec_split(a, &acnt, b, &bcnt, left, NGTCP2_MAX_STREAM_DATACNT); + + assert(acnt > 0); + assert(bcnt > 0); + + rv = ngtcp2_frame_chain_stream_datacnt_objalloc_new( + &nfrc, bcnt, 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->type = fr->type; + nfr->flags = 0; + 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); + + rv = ngtcp2_ksl_insert(strm->tx.streamfrq, NULL, &nfr->offset, nfrc); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + ngtcp2_frame_chain_objalloc_del(nfrc, strm->frc_objalloc, strm->mem); + ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem); + 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); + + *pfrc = nfrc; + + return 0; + } + + left -= (size_t)datalen; + + ngtcp2_vec_copy(a, fr->data, fr->datacnt); + acnt = fr->datacnt; + + for (; left && ngtcp2_ksl_len(strm->tx.streamfrq);) { + unacked_offset = ngtcp2_strm_streamfrq_unacked_offset(strm); + if (unacked_offset != fr->offset + datalen) { + assert(fr->offset + datalen < unacked_offset); + break; + } + + rv = strm_streamfrq_unacked_pop(strm, &nfrc); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem); + return rv; + } + if (nfrc == NULL) { + break; + } + + nfr = &nfrc->fr.stream; + + if (nfr->fin && nfr->datacnt == 0) { + fr->fin = 1; + ngtcp2_frame_chain_objalloc_del(nfrc, strm->frc_objalloc, strm->mem); + break; + } + + nmerged = ngtcp2_vec_merge(a, &acnt, nfr->data, &nfr->datacnt, left, + NGTCP2_MAX_STREAM_DATACNT); + if (nmerged == 0) { + rv = ngtcp2_ksl_insert(strm->tx.streamfrq, NULL, &nfr->offset, nfrc); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + ngtcp2_frame_chain_objalloc_del(nfrc, strm->frc_objalloc, strm->mem); + ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem); + return rv; + } + break; + } + + datalen += nmerged; + left -= nmerged; + + if (nfr->datacnt == 0) { + fr->fin = nfr->fin; + ngtcp2_frame_chain_objalloc_del(nfrc, strm->frc_objalloc, strm->mem); + continue; + } + + 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); + 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]; + } + + *pfrc = frc; + return 0; + } + + assert(acnt > fr->datacnt); + + rv = ngtcp2_frame_chain_stream_datacnt_objalloc_new( + &nfrc, acnt, 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); + + ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem); + + *pfrc = nfrc; + + return 0; +} + +uint64_t ngtcp2_strm_streamfrq_unacked_offset(ngtcp2_strm *strm) { + ngtcp2_frame_chain *frc; + ngtcp2_stream *fr; + ngtcp2_range gap; + ngtcp2_ksl_it it; + uint64_t datalen; + + assert(strm->tx.streamfrq); + assert(ngtcp2_ksl_len(strm->tx.streamfrq)); + + for (it = ngtcp2_ksl_begin(strm->tx.streamfrq); !ngtcp2_ksl_it_end(&it); + ngtcp2_ksl_it_next(&it)) { + frc = ngtcp2_ksl_it_get(&it); + fr = &frc->fr.stream; + + gap = ngtcp2_strm_get_unacked_range_after(strm, fr->offset); + + datalen = ngtcp2_vec_len(fr->data, fr->datacnt); + + if (gap.begin <= fr->offset) { + return fr->offset; + } + if (gap.begin < fr->offset + datalen) { + return gap.begin; + } + if (fr->offset + datalen == gap.begin && fr->fin && + !(strm->flags & NGTCP2_STRM_FLAG_FIN_ACKED)) { + return fr->offset + datalen; + } + } + + return (uint64_t)-1; +} + +ngtcp2_frame_chain *ngtcp2_strm_streamfrq_top(ngtcp2_strm *strm) { + ngtcp2_ksl_it it; + + assert(strm->tx.streamfrq); + assert(ngtcp2_ksl_len(strm->tx.streamfrq)); + + it = ngtcp2_ksl_begin(strm->tx.streamfrq); + return ngtcp2_ksl_it_get(&it); +} + +int ngtcp2_strm_streamfrq_empty(ngtcp2_strm *strm) { + return strm->tx.streamfrq == NULL || ngtcp2_ksl_len(strm->tx.streamfrq) == 0; +} + +void ngtcp2_strm_streamfrq_clear(ngtcp2_strm *strm) { + ngtcp2_frame_chain *frc; + ngtcp2_ksl_it it; + + if (strm->tx.streamfrq == NULL) { + return; + } + + for (it = ngtcp2_ksl_begin(strm->tx.streamfrq); !ngtcp2_ksl_it_end(&it); + ngtcp2_ksl_it_next(&it)) { + frc = ngtcp2_ksl_it_get(&it); + ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem); + } + ngtcp2_ksl_clear(strm->tx.streamfrq); +} + +int ngtcp2_strm_is_tx_queued(ngtcp2_strm *strm) { + return strm->pe.index != NGTCP2_PQ_BAD_INDEX; +} + +int ngtcp2_strm_is_all_tx_data_acked(ngtcp2_strm *strm) { + if (strm->tx.acked_offset == NULL) { + return strm->tx.cont_acked_offset == strm->tx.offset; + } + + return ngtcp2_gaptr_first_gap_offset(strm->tx.acked_offset) == + strm->tx.offset; +} + +int ngtcp2_strm_is_all_tx_data_fin_acked(ngtcp2_strm *strm) { + return (strm->flags & NGTCP2_STRM_FLAG_FIN_ACKED) && + ngtcp2_strm_is_all_tx_data_acked(strm); +} + +ngtcp2_range ngtcp2_strm_get_unacked_range_after(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_gaptr_get_first_gap_after(strm->tx.acked_offset, offset); +} + +uint64_t ngtcp2_strm_get_acked_offset(ngtcp2_strm *strm) { + if (strm->tx.acked_offset == NULL) { + return strm->tx.cont_acked_offset; + } + + return ngtcp2_gaptr_first_gap_offset(strm->tx.acked_offset); +} + +static int strm_acked_offset_init(ngtcp2_strm *strm) { + ngtcp2_gaptr *acked_offset = + ngtcp2_mem_malloc(strm->mem, sizeof(*acked_offset)); + + if (acked_offset == NULL) { + return NGTCP2_ERR_NOMEM; + } + + ngtcp2_gaptr_init(acked_offset, strm->mem); + + strm->tx.acked_offset = acked_offset; + + return 0; +} + +int ngtcp2_strm_ack_data(ngtcp2_strm *strm, uint64_t offset, uint64_t len) { + int rv; + + if (strm->tx.acked_offset == NULL) { + if (strm->tx.cont_acked_offset == offset) { + strm->tx.cont_acked_offset += len; + return 0; + } + + rv = strm_acked_offset_init(strm); + if (rv != 0) { + return rv; + } + + rv = + ngtcp2_gaptr_push(strm->tx.acked_offset, 0, strm->tx.cont_acked_offset); + if (rv != 0) { + return rv; + } + } + + return ngtcp2_gaptr_push(strm->tx.acked_offset, offset, len); +} + +void ngtcp2_strm_set_app_error_code(ngtcp2_strm *strm, + uint64_t app_error_code) { + if (strm->flags & NGTCP2_STRM_FLAG_APP_ERROR_CODE_SET) { + return; + } + + assert(0 == strm->app_error_code); + + strm->flags |= NGTCP2_STRM_FLAG_APP_ERROR_CODE_SET; + strm->app_error_code = app_error_code; +} diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_strm.h b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_strm.h new file mode 100644 index 0000000..223e38f --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_strm.h @@ -0,0 +1,323 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 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_STRM_H +#define NGTCP2_STRM_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_rob.h" +#include "ngtcp2_map.h" +#include "ngtcp2_gaptr.h" +#include "ngtcp2_ksl.h" +#include "ngtcp2_pq.h" + +typedef struct ngtcp2_frame_chain ngtcp2_frame_chain; + +/* NGTCP2_STRM_FLAG_NONE indicates that no flag is set. */ +#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 +/* 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_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 +/* 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 +/* NGTCP2_STRM_FLAG_STOP_SENDING indicates that STOP_SENDING is sent + from the local endpoint. */ +#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 +/* 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 +/* 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 +/* 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 +/* NGTCP2_STRM_FLAG_SEND_STOP_SENDING is set when STOP_SENDING frame + should be sent. */ +#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 +/* 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 + +typedef struct ngtcp2_strm ngtcp2_strm; + +struct ngtcp2_strm { + union { + struct { + ngtcp2_pq_entry pe; + uint64_t cycle; + ngtcp2_objalloc *frc_objalloc; + + struct { + /* acked_offset tracks acknowledged outgoing data. */ + ngtcp2_gaptr *acked_offset; + /* cont_acked_offset is the offset that all data up to this offset + is acknowledged by a remote endpoint. It is used until the + remote endpoint acknowledges data in out-of-order. After that, + acked_offset is used instead. */ + uint64_t cont_acked_offset; + /* streamfrq contains STREAM or CRYPTO frame for + retransmission. The flow control credits have been paid + when they are transmitted first time. There are no + restriction regarding flow control for retransmission. */ + ngtcp2_ksl *streamfrq; + /* offset is the next offset of outgoing data. In other words, it + is the number of bytes sent in this stream without + duplication. */ + uint64_t offset; + /* max_tx_offset is the maximum offset that local endpoint can + send for this stream. */ + uint64_t max_offset; + /* last_blocked_offset is the largest offset where the + transmission of stream data is blocked. */ + uint64_t last_blocked_offset; + /* last_max_stream_data_ts is the timestamp when last + MAX_STREAM_DATA frame is sent. */ + ngtcp2_tstamp last_max_stream_data_ts; + /* loss_count is the number of packets that contain STREAM + frame for this stream and are declared to be lost. It may + include the spurious losses. It does not include a packet + whose contents have been reclaimed for PTO and which is + later declared to be lost. Those data are not blocked by + the flow control and will be sent immediately if no other + restrictions are applied. */ + size_t loss_count; + /* last_lost_pkt_num is the packet number of the packet that + is counted to loss_count. It is used to avoid to count + multiple STREAM frames in one lost packet. */ + int64_t last_lost_pkt_num; + /* stop_sending_app_error_code is the application specific + error code that is sent along with STOP_SENDING. */ + uint64_t stop_sending_app_error_code; + /* reset_stream_app_error_code is the application specific + error code that is sent along with RESET_STREAM. */ + uint64_t reset_stream_app_error_code; + } tx; + + struct { + /* rob is the reorder buffer for incoming stream data. The data + received in out of order is buffered and sorted by its offset + in this object. */ + ngtcp2_rob *rob; + /* cont_offset is the largest offset of consecutive data. It is + used until the endpoint receives out-of-order data. After + that, rob is used to track the offset and data. */ + uint64_t cont_offset; + /* last_offset is the largest offset of stream data received for + this stream. */ + uint64_t last_offset; + /* max_offset is the maximum offset that remote endpoint can send + to this stream. */ + uint64_t max_offset; + /* unsent_max_offset is the maximum offset that remote endpoint + can send to this stream, and it is not notified to the remote + endpoint. unsent_max_offset >= max_offset must be hold. */ + uint64_t unsent_max_offset; + /* window is the stream-level flow control window size. */ + uint64_t window; + } rx; + + 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; + }; + + ngtcp2_opl_entry oplent; + }; +}; + +/* + * ngtcp2_strm_init initializes |strm|. + */ +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); + +/* + * ngtcp2_strm_free deallocates memory allocated for |strm|. This + * function does not free the memory pointed by |strm| itself. + */ +void ngtcp2_strm_free(ngtcp2_strm *strm); + +/* + * ngtcp2_strm_rx_offset returns the minimum offset of stream data + * which is not received yet. + */ +uint64_t ngtcp2_strm_rx_offset(ngtcp2_strm *strm); + +/* + * ngtcp2_strm_recv_reordering handles reordered data. + * + * It returns 0 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_strm_update_rx_offset tells that data up to offset bytes are + * received in order. + */ +void ngtcp2_strm_update_rx_offset(ngtcp2_strm *strm, uint64_t offset); + +/* + * ngtcp2_strm_shutdown shutdowns |strm|. |flags| should be + * NGTCP2_STRM_FLAG_SHUT_RD, and/or NGTCP2_STRM_FLAG_SHUT_WR. + */ +void ngtcp2_strm_shutdown(ngtcp2_strm *strm, uint32_t flags); + +/* + * ngtcp2_strm_streamfrq_push pushes |frc| to streamfrq for + * retransmission. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory + */ +int ngtcp2_strm_streamfrq_push(ngtcp2_strm *strm, ngtcp2_frame_chain *frc); + +/* + * ngtcp2_strm_streamfrq_pop pops the first ngtcp2_frame_chain and + * assigns it to |*pfrc|. This function splits into or merges several + * ngtcp2_frame_chain objects so that the returned ngtcp2_frame_chain + * has at most |left| data length. If there is no frames to send, + * this function returns 0 and |*pfrc| is NULL. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory + */ +int ngtcp2_strm_streamfrq_pop(ngtcp2_strm *strm, ngtcp2_frame_chain **pfrc, + size_t left); + +/* + * ngtcp2_strm_streamfrq_unacked_offset returns the smallest offset of + * unacknowledged stream data held in strm->tx.streamfrq. + */ +uint64_t ngtcp2_strm_streamfrq_unacked_offset(ngtcp2_strm *strm); + +/* + * ngtcp2_strm_streamfrq_top returns the first ngtcp2_frame_chain. + * The queue must not be empty. + */ +ngtcp2_frame_chain *ngtcp2_strm_streamfrq_top(ngtcp2_strm *strm); + +/* + * ngtcp2_strm_streamfrq_empty returns nonzero if streamfrq is empty. + */ +int ngtcp2_strm_streamfrq_empty(ngtcp2_strm *strm); + +/* + * ngtcp2_strm_streamfrq_clear removes all frames from streamfrq. + */ +void ngtcp2_strm_streamfrq_clear(ngtcp2_strm *strm); + +/* + * ngtcp2_strm_is_tx_queued returns nonzero if |strm| is queued. + */ +int ngtcp2_strm_is_tx_queued(ngtcp2_strm *strm); + +/* + * ngtcp2_strm_is_all_tx_data_acked returns nonzero if all outgoing + * data for |strm| which have sent so far have been acknowledged. + */ +int ngtcp2_strm_is_all_tx_data_acked(ngtcp2_strm *strm); + +/* + * ngtcp2_strm_is_all_tx_data_fin_acked behaves like + * ngtcp2_strm_is_all_tx_data_acked, but it also requires that STREAM + * frame with fin bit set is acknowledged. + */ +int ngtcp2_strm_is_all_tx_data_fin_acked(ngtcp2_strm *strm); + +/* + * ngtcp2_strm_get_unacked_range_after returns the range that is not + * acknowledged yet and intersects or comes after |offset|. + */ +ngtcp2_range ngtcp2_strm_get_unacked_range_after(ngtcp2_strm *strm, + uint64_t offset); + +/* + * ngtcp2_strm_get_acked_offset returns offset, that is the data up to + * this offset have been acknowledged by a remote endpoint. It + * returns 0 if no data is acknowledged. + */ +uint64_t ngtcp2_strm_get_acked_offset(ngtcp2_strm *strm); + +/* + * ngtcp2_strm_ack_data tells |strm| that the data [offset, + * offset+len) is acknowledged by a remote endpoint. + */ +int ngtcp2_strm_ack_data(ngtcp2_strm *strm, uint64_t offset, uint64_t len); + +/* + * ngtcp2_strm_set_app_error_code sets |app_error_code| to |strm| and + * set NGTCP2_STRM_FLAG_APP_ERROR_CODE_SET flag. If the flag is + * already set, this function does nothing. + */ +void ngtcp2_strm_set_app_error_code(ngtcp2_strm *strm, uint64_t app_error_code); + +#endif /* NGTCP2_STRM_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_tstamp.h b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_tstamp.h new file mode 100644 index 0000000..9a210a3 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_tstamp.h @@ -0,0 +1,68 @@ +/* + * ngtcp2 + * + * Copyright (c) 2023 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_TSTAMP_H +#define NGTCP2_TSTAMP_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +/* + * ngtcp2_tstamp_elapsed returns nonzero if at least |d| has passed + * since |base|. |ts| expresses a current time, and must not be + * UINT64_MAX. + * + * If |base| is UINT64_MAX, this function returns 0 because UINT64_MAX + * is an invalid timestamp. Otherwise, if |base| + |d| >= UINT64_MAX, + * this function returns 0. + * + * !ngtcp2_tstamp_elapsed() == ngtcp2_tstamp_not_elapsed() does not + * hold when |base| is UINT64_MAX. If you need nonzero if |base| is + * UINT64_MAX, use !ngtcp2_tstamp_elapsed. Otherwise, use + * ngtcp2_tstamp_not_elapsed. + */ +static inline int ngtcp2_tstamp_elapsed(ngtcp2_tstamp base, ngtcp2_duration d, + ngtcp2_tstamp ts) { + return base != UINT64_MAX && base < UINT64_MAX - d && base + d <= ts; +} + +/* + * ngtcp2_tstamp_not_elapsed returns nonzero if |d| has not passed + * since |base|. |ts| expresses a current time, and must not be + * UINT64_MAX. + * + * If |base| is UINT64_MAX, this function returns 0 because UINT64_MAX + * is an invalid timestamp. Otherwise, if |base| + |d| >= UINT64_MAX, + * this function returns nonzero. + */ +static inline int ngtcp2_tstamp_not_elapsed(ngtcp2_tstamp base, + ngtcp2_duration d, + ngtcp2_tstamp ts) { + return base != UINT64_MAX && (base >= UINT64_MAX - d || base + d > ts); +} + +#endif /* NGTCP2_TSTAMP_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_unreachable.c b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_unreachable.c new file mode 100644 index 0000000..7c7d9ae --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_unreachable.c @@ -0,0 +1,71 @@ +/* + * ngtcp2 + * + * Copyright (c) 2022 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_unreachable.h" + +#include <stdio.h> +#include <errno.h> +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif /* HAVE_UNISTD_H */ +#include <stdlib.h> +#ifdef WIN32 +# include <io.h> +#endif /* WIN32 */ + +void ngtcp2_unreachable_fail(const char *file, int line, const char *func) { + char *buf; + size_t buflen; + int rv; + +#define NGTCP2_UNREACHABLE_TEMPLATE "%s:%d %s: Unreachable.\n" + + rv = snprintf(NULL, 0, NGTCP2_UNREACHABLE_TEMPLATE, file, line, func); + if (rv < 0) { + abort(); + } + + /* here we explicitly use system malloc */ + buflen = (size_t)rv + 1; + buf = malloc(buflen); + if (buf == NULL) { + abort(); + } + + rv = snprintf(buf, buflen, NGTCP2_UNREACHABLE_TEMPLATE, file, line, func); + if (rv < 0) { + abort(); + } + +#ifndef WIN32 + while (write(STDERR_FILENO, buf, (size_t)rv) == -1 && errno == EINTR) + ; +#else /* WIN32 */ + _write(_fileno(stderr), buf, (unsigned int)rv); +#endif /* WIN32 */ + + free(buf); + + abort(); +} diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_unreachable.h b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_unreachable.h new file mode 100644 index 0000000..a5276fd --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_unreachable.h @@ -0,0 +1,52 @@ +/* + * ngtcp2 + * + * Copyright (c) 2022 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_UNREACHABLE_H +#define NGTCP2_UNREACHABLE_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#ifdef __FILE_NAME__ +# define NGTCP2_FILE_NAME __FILE_NAME__ +#else /* !__FILE_NAME__ */ +# define NGTCP2_FILE_NAME "(file)" +#endif /* !__FILE_NAME__ */ + +#define ngtcp2_unreachable() \ + ngtcp2_unreachable_fail(NGTCP2_FILE_NAME, __LINE__, __func__) + +#ifdef _MSC_VER +__declspec(noreturn) +#endif /* _MSC_VER */ + void ngtcp2_unreachable_fail(const char *file, int line, const char *func) +#ifndef _MSC_VER + __attribute__((noreturn)) +#endif /* !_MSC_VER */ + ; + +#endif /* NGTCP2_UNREACHABLE_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_vec.c b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_vec.c new file mode 100644 index 0000000..dbc7b66 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_vec.c @@ -0,0 +1,240 @@ +/* + * ngtcp2 + * + * Copyright (c) 2018 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_vec.h" + +#include <string.h> +#include <assert.h> + +#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; + return vec; +} + +int ngtcp2_vec_new(ngtcp2_vec **pvec, const uint8_t *data, size_t datalen, + const ngtcp2_mem *mem) { + size_t len; + uint8_t *p; + + len = sizeof(ngtcp2_vec) + datalen; + + *pvec = ngtcp2_mem_malloc(mem, len); + if (*pvec == NULL) { + return NGTCP2_ERR_NOMEM; + } + + p = (uint8_t *)(*pvec) + sizeof(ngtcp2_vec); + (*pvec)->base = p; + (*pvec)->len = datalen; + if (datalen) { + /* p = */ ngtcp2_cpymem(p, data, datalen); + } + + return 0; +} + +void ngtcp2_vec_del(ngtcp2_vec *vec, const ngtcp2_mem *mem) { + ngtcp2_mem_free(mem, vec); +} + +uint64_t ngtcp2_vec_len(const ngtcp2_vec *vec, size_t n) { + size_t i; + size_t res = 0; + + for (i = 0; i < n; ++i) { + res += vec[i].len; + } + + return res; +} + +int64_t ngtcp2_vec_len_varint(const ngtcp2_vec *vec, size_t n) { + uint64_t res = 0; + size_t len; + size_t i; + + for (i = 0; i < n; ++i) { + len = vec[i].len; + if (len > NGTCP2_MAX_VARINT - res) { + return -1; + } + + res += len; + } + + return (int64_t)res; +} + +ngtcp2_ssize ngtcp2_vec_split(ngtcp2_vec *src, size_t *psrccnt, ngtcp2_vec *dst, + size_t *pdstcnt, size_t left, size_t maxcnt) { + size_t i; + size_t srccnt = *psrccnt; + size_t nmove; + size_t extra = 0; + + for (i = 0; i < srccnt; ++i) { + if (left >= src[i].len) { + left -= src[i].len; + continue; + } + + if (*pdstcnt && src[srccnt - 1].base + src[srccnt - 1].len == dst[0].base) { + if (*pdstcnt + srccnt - i - 1 > maxcnt) { + return -1; + } + + dst[0].len += src[srccnt - 1].len; + dst[0].base = src[srccnt - 1].base; + extra = src[srccnt - 1].len; + --srccnt; + } else if (*pdstcnt + srccnt - i > maxcnt) { + return -1; + } + + if (left == 0) { + *psrccnt = i; + } else { + *psrccnt = i + 1; + } + + nmove = srccnt - i; + if (nmove) { + memmove(dst + nmove, dst, sizeof(ngtcp2_vec) * (*pdstcnt)); + *pdstcnt += nmove; + memcpy(dst, src + i, sizeof(ngtcp2_vec) * nmove); + } + + dst[0].len -= left; + dst[0].base += left; + src[i].len = left; + + if (nmove == 0) { + extra -= left; + } + + return (ngtcp2_ssize)(ngtcp2_vec_len(dst, nmove) + extra); + } + + return 0; +} + +size_t ngtcp2_vec_merge(ngtcp2_vec *dst, size_t *pdstcnt, ngtcp2_vec *src, + size_t *psrccnt, size_t left, size_t maxcnt) { + size_t orig_left = left; + size_t i = 0; + ngtcp2_vec *a, *b; + + assert(maxcnt); + + if (*pdstcnt == 0) { + if (*psrccnt == 0) { + return 0; + } + + a = &dst[0]; + b = &src[0]; + + if (left < b->len) { + a->len = left; + a->base = b->base; + + b->len -= left; + b->base += left; + + return left; + } + + *a = *b; + ++*pdstcnt; + left -= b->len; + i = 1; + } + + for (; left && i < *psrccnt; ++i) { + a = &dst[*pdstcnt - 1]; + b = &src[i]; + + if (left < b->len) { + if (a->base + a->len == b->base) { + a->len += left; + } else if (*pdstcnt == maxcnt) { + break; + } else { + dst[*pdstcnt].len = left; + dst[*pdstcnt].base = b->base; + ++*pdstcnt; + } + + b->len -= left; + b->base += left; + left = 0; + + break; + } + + if (a->base + a->len == b->base) { + a->len += b->len; + } else if (*pdstcnt == maxcnt) { + break; + } else { + dst[(*pdstcnt)++] = *b; + } + left -= b->len; + } + + memmove(src, src + i, sizeof(ngtcp2_vec) * (*psrccnt - i)); + *psrccnt -= i; + + return orig_left - left; +} + +size_t ngtcp2_vec_copy_at_most(ngtcp2_vec *dst, size_t dstcnt, + const ngtcp2_vec *src, size_t srccnt, + size_t left) { + size_t i, j; + + for (i = 0, j = 0; left > 0 && i < srccnt && j < dstcnt;) { + if (src[i].len == 0) { + ++i; + continue; + } + dst[j] = src[i]; + if (dst[j].len > left) { + dst[j].len = left; + return j + 1; + } + left -= dst[j].len; + ++i; + ++j; + } + + return j; +} + +void ngtcp2_vec_copy(ngtcp2_vec *dst, const ngtcp2_vec *src, size_t cnt) { + memcpy(dst, src, sizeof(ngtcp2_vec) * cnt); +} diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_vec.h b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_vec.h new file mode 100644 index 0000000..a39c439 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_vec.h @@ -0,0 +1,120 @@ +/* + * ngtcp2 + * + * Copyright (c) 2018 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_VEC_H +#define NGTCP2_VEC_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#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|. + */ +ngtcp2_vec *ngtcp2_vec_init(ngtcp2_vec *vec, const uint8_t *base, size_t len); + +/* + * ngtcp2_vec_new allocates and initializes |*pvec| with given |data| + * of length |datalen|. This function allocates memory for |*pvec| + * and the given data with a single allocation, and the contents + * pointed by |data| is copied into the allocated memory space. To + * free the allocated memory, call ngtcp2_vec_del. + */ +int ngtcp2_vec_new(ngtcp2_vec **pvec, const uint8_t *data, size_t datalen, + const ngtcp2_mem *mem); + +/* + * ngtcp2_vec_del frees the memory allocated by |vec| which is + * allocated and initialized by ngtcp2_vec_new. + */ +void ngtcp2_vec_del(ngtcp2_vec *vec, const ngtcp2_mem *mem); + +/* + * ngtcp2_vec_len returns the sum of length in |vec| of |n| elements. + */ +uint64_t ngtcp2_vec_len(const ngtcp2_vec *vec, size_t n); + +/* + * ngtcp2_vec_len_varint is similar to ngtcp2_vec_len, but it returns + * -1 if the sum of the length exceeds NGTCP2_MAX_VARINT. + */ +int64_t ngtcp2_vec_len_varint(const ngtcp2_vec *vec, size_t n); + +/* + * ngtcp2_vec_split splits |src| to |dst| so that the sum of the + * length in |src| does not exceed |left| bytes. The |maxcnt| is the + * maximum number of elements which |dst| array can contain. The + * caller must set |*psrccnt| to the number of elements of |src|. + * Similarly, the caller must set |*pdstcnt| to the number of elements + * of |dst|. The split does not necessarily occur at the boundary of + * ngtcp2_vec object. After split has done, this function updates + * |*psrccnt| and |*pdstcnt|. This function returns the number of + * bytes moved from |src| to |dst|. If split cannot be made because + * doing so exceeds |maxcnt|, this function returns -1. + */ +ngtcp2_ssize ngtcp2_vec_split(ngtcp2_vec *src, size_t *psrccnt, ngtcp2_vec *dst, + size_t *pdstcnt, size_t left, size_t maxcnt); + +/* + * ngtcp2_vec_merge merges |src| into |dst| by moving at most |left| + * bytes from |src|. The |maxcnt| is the maximum number of elements + * which |dst| array can contain. The caller must set |*pdstcnt| to + * the number of elements of |dst|. Similarly, the caller must set + * |*psrccnt| to the number of elements of |src|. After merge has + * done, this function updates |*psrccnt| and |*pdstcnt|. This + * function returns the number of bytes moved from |src| to |dst|. + */ +size_t ngtcp2_vec_merge(ngtcp2_vec *dst, size_t *pdstcnt, ngtcp2_vec *src, + size_t *psrccnt, size_t left, size_t maxcnt); + +/* + * ngtcp2_vec_copy_at_most copies |src| of length |srccnt| to |dst| of + * length |dstcnt|. The total number of bytes which the copied + * ngtcp2_vec refers to is at most |left|. The empty elements in + * |src| are ignored. This function returns the number of elements + * copied. + */ +size_t ngtcp2_vec_copy_at_most(ngtcp2_vec *dst, size_t dstcnt, + const ngtcp2_vec *src, size_t srccnt, + size_t left); + +/* + * ngtcp2_vec_copy copies |src| of length |cnt| to |dst|. |dst| must + * have sufficient capacity. + */ +void ngtcp2_vec_copy(ngtcp2_vec *dst, const ngtcp2_vec *src, size_t cnt); + +#endif /* NGTCP2_VEC_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_version.c b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_version.c new file mode 100644 index 0000000..b31162c --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_version.c @@ -0,0 +1,39 @@ +/* + * 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. + */ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +static ngtcp2_info version = {NGTCP2_VERSION_AGE, NGTCP2_VERSION_NUM, + NGTCP2_VERSION}; + +const ngtcp2_info *ngtcp2_version(int least_version) { + if (least_version > NGTCP2_VERSION_NUM) { + return NULL; + } + return &version; +} diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_window_filter.c b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_window_filter.c new file mode 100644 index 0000000..39f3d40 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_window_filter.c @@ -0,0 +1,116 @@ +/* + * ngtcp2 + * + * Copyright (c) 2021 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. + */ + +/* + * Translated to C from the original C++ code + * https://quiche.googlesource.com/quiche/+/5be974e29f7e71a196e726d6e2272676d33ab77d/quic/core/congestion_control/windowed_filter.h + * with the following license: + * + * // Copyright (c) 2016 The Chromium Authors. All rights reserved. + * // Use of this source code is governed by a BSD-style license that can be + * // found in the LICENSE file. + */ +#include "ngtcp2_window_filter.h" + +#include <string.h> + +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)); +} + +void ngtcp2_window_filter_update(ngtcp2_window_filter *wf, uint64_t new_sample, + uint64_t new_time) { + /* Reset all estimates if they have not yet been initialized, if new + sample is a new best, or if the newest recorded estimate is too + old. */ + if (wf->estimates[0].sample == UINT64_MAX || + new_sample > wf->estimates[0].sample || + new_time - wf->estimates[2].time > wf->window_length) { + ngtcp2_window_filter_reset(wf, new_sample, new_time); + return; + } + + if (new_sample > wf->estimates[1].sample) { + wf->estimates[1].sample = new_sample; + wf->estimates[1].time = new_time; + wf->estimates[2] = wf->estimates[1]; + } else if (new_sample > wf->estimates[2].sample) { + wf->estimates[2].sample = new_sample; + wf->estimates[2].time = new_time; + } + + /* Expire and update estimates as necessary. */ + if (new_time - wf->estimates[0].time > wf->window_length) { + /* The best estimate hasn't been updated for an entire window, so + promote second and third best estimates. */ + wf->estimates[0] = wf->estimates[1]; + wf->estimates[1] = wf->estimates[2]; + wf->estimates[2].sample = new_sample; + wf->estimates[2].time = new_time; + + /* Need to iterate one more time. Check if the new best estimate + is outside the window as well, since it may also have been + recorded a long time ago. Don't need to iterate once more + since we cover that case at the beginning of the method. */ + if (new_time - wf->estimates[0].time > wf->window_length) { + wf->estimates[0] = wf->estimates[1]; + wf->estimates[1] = wf->estimates[2]; + } + return; + } + + if (wf->estimates[1].sample == wf->estimates[0].sample && + new_time - wf->estimates[1].time > wf->window_length >> 2) { + /* A quarter of the window has passed without a better sample, so + the second-best estimate is taken from the second quarter of + the window. */ + wf->estimates[2].sample = new_sample; + wf->estimates[2].time = new_time; + wf->estimates[1] = wf->estimates[2]; + return; + } + + if (wf->estimates[2].sample == wf->estimates[1].sample && + new_time - wf->estimates[2].time > wf->window_length >> 1) { + /* We've passed a half of the window without a better estimate, so + take a third-best estimate from the second half of the + window. */ + wf->estimates[2].sample = new_sample; + wf->estimates[2].time = new_time; + } +} + +void ngtcp2_window_filter_reset(ngtcp2_window_filter *wf, uint64_t new_sample, + uint64_t new_time) { + wf->estimates[0].sample = new_sample; + wf->estimates[0].time = new_time; + wf->estimates[1] = wf->estimates[2] = wf->estimates[0]; +} + +uint64_t ngtcp2_window_filter_get_best(ngtcp2_window_filter *wf) { + return wf->estimates[0].sample; +} diff --git a/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_window_filter.h b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_window_filter.h new file mode 100644 index 0000000..50415f1 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_window_filter.h @@ -0,0 +1,65 @@ +/* + * ngtcp2 + * + * Copyright (c) 2021 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. + */ + +/* + * Translated to C from the original C++ code + * https://quiche.googlesource.com/quiche/+/5be974e29f7e71a196e726d6e2272676d33ab77d/quic/core/congestion_control/windowed_filter.h + * with the following license: + * + * // Copyright (c) 2016 The Chromium Authors. All rights reserved. + * // Use of this source code is governed by a BSD-style license that can be + * // found in the LICENSE file. + */ +#ifndef NGTCP2_WINDOW_FILTER_H +#define NGTCP2_WINDOW_FILTER_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +typedef struct ngtcp2_window_filter_sample { + uint64_t sample; + uint64_t time; +} ngtcp2_window_filter_sample; + +typedef struct ngtcp2_window_filter { + uint64_t window_length; + ngtcp2_window_filter_sample estimates[3]; +} ngtcp2_window_filter; + +void ngtcp2_window_filter_init(ngtcp2_window_filter *wf, + uint64_t window_length); + +void ngtcp2_window_filter_update(ngtcp2_window_filter *wf, uint64_t new_sample, + uint64_t new_time); + +void ngtcp2_window_filter_reset(ngtcp2_window_filter *wf, uint64_t new_sample, + uint64_t new_time); + +uint64_t ngtcp2_window_filter_get_best(ngtcp2_window_filter *wf); + +#endif /* NGTCP2_WINDOW_FILTER_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/ngtcp2.h b/src/contrib/libngtcp2/ngtcp2/ngtcp2.h new file mode 100644 index 0000000..72c8142 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/ngtcp2.h @@ -0,0 +1,5868 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * Copyright (c) 2017 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_H +#define NGTCP2_H + +/* Define WIN32 when build target is Win32 API (borrowed from + libcurl) */ +#if (defined(_WIN32) || defined(__WIN32__)) && !defined(WIN32) +# define WIN32 +#endif + +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable : 4324) +#endif + +#include <stdlib.h> +#if defined(_MSC_VER) && (_MSC_VER < 1800) +/* MSVC < 2013 does not have inttypes.h because it is not C99 + compliant. See compiler macros and version number in + https://sourceforge.net/p/predef/wiki/Compilers/ */ +# include <stdint.h> +#else /* !defined(_MSC_VER) || (_MSC_VER >= 1800) */ +# include <inttypes.h> +#endif /* !defined(_MSC_VER) || (_MSC_VER >= 1800) */ +#include <sys/types.h> +#include <stdarg.h> +#include <stddef.h> + +#ifndef NGTCP2_USE_GENERIC_SOCKADDR +# ifdef WIN32 +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif /* WIN32_LEAN_AND_MEAN */ +# include <ws2tcpip.h> +# else /* !WIN32 */ +# include <sys/socket.h> +# include <netinet/in.h> +# endif /* !WIN32 */ +#endif /* NGTCP2_USE_GENERIC_SOCKADDR */ + +#include <ngtcp2/version.h> + +#ifdef NGTCP2_STATICLIB +# define NGTCP2_EXTERN +#elif defined(WIN32) +# ifdef BUILDING_NGTCP2 +# define NGTCP2_EXTERN __declspec(dllexport) +# else /* !BUILDING_NGTCP2 */ +# define NGTCP2_EXTERN __declspec(dllimport) +# endif /* !BUILDING_NGTCP2 */ +#else /* !defined(WIN32) */ +# ifdef BUILDING_NGTCP2 +# define NGTCP2_EXTERN __attribute__((visibility("default"))) +# else /* !BUILDING_NGTCP2 */ +# define NGTCP2_EXTERN +# endif /* !BUILDING_NGTCP2 */ +#endif /* !defined(WIN32) */ + +#ifdef _MSC_VER +# define NGTCP2_ALIGN(N) __declspec(align(N)) +#else /* !_MSC_VER */ +# define NGTCP2_ALIGN(N) __attribute__((aligned(N))) +#endif /* !_MSC_VER */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @typedef + * + * :type:`ngtcp2_ssize` is signed counterpart of size_t. + */ +typedef ptrdiff_t ngtcp2_ssize; + +/** + * @functypedef + * + * :type:`ngtcp2_malloc` is a custom memory allocator to replace + * :manpage:`malloc(3)`. The |user_data| is + * :member:`ngtcp2_mem.user_data`. + */ +typedef void *(*ngtcp2_malloc)(size_t size, void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_free` is a custom memory allocator to replace + * :manpage:`free(3)`. The |user_data| is + * :member:`ngtcp2_mem.user_data`. + */ +typedef void (*ngtcp2_free)(void *ptr, void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_calloc` is a custom memory allocator to replace + * :manpage:`calloc(3)`. The |user_data| is the + * :member:`ngtcp2_mem.user_data`. + */ +typedef void *(*ngtcp2_calloc)(size_t nmemb, size_t size, void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_realloc` is a custom memory allocator to replace + * :manpage:`realloc(3)`. The |user_data| is the + * :member:`ngtcp2_mem.user_data`. + */ +typedef void *(*ngtcp2_realloc)(void *ptr, size_t size, void *user_data); + +/** + * @struct + * + * :type:`ngtcp2_mem` is a custom memory allocator. The + * :member:`user_data` field is passed to each allocator function. + * This can be used, for example, to achieve per-connection memory + * pool. + * + * In the following example code, ``my_malloc``, ``my_free``, + * ``my_calloc`` and ``my_realloc`` are the replacement of the + * standard allocators :manpage:`malloc(3)`, :manpage:`free(3)`, + * :manpage:`calloc(3)` and :manpage:`realloc(3)` respectively:: + * + * void *my_malloc_cb(size_t size, void *user_data) { + * (void)user_data; + * return my_malloc(size); + * } + * + * void my_free_cb(void *ptr, void *user_data) { + * (void)user_data; + * my_free(ptr); + * } + * + * void *my_calloc_cb(size_t nmemb, size_t size, void *user_data) { + * (void)user_data; + * return my_calloc(nmemb, size); + * } + * + * void *my_realloc_cb(void *ptr, size_t size, void *user_data) { + * (void)user_data; + * return my_realloc(ptr, size); + * } + * + * void conn_new() { + * ngtcp2_mem mem = {NULL, my_malloc_cb, my_free_cb, my_calloc_cb, + * my_realloc_cb}; + * + * ... + * } + */ +typedef struct ngtcp2_mem { + /** + * :member:`user_data` is an arbitrary user supplied data. This + * is passed to each allocator function. + */ + void *user_data; + /** + * :member:`malloc` is a custom allocator function to replace + * :manpage:`malloc(3)`. + */ + ngtcp2_malloc malloc; + /** + * :member:`free` is a custom allocator function to replace + * :manpage:`free(3)`. + */ + ngtcp2_free free; + /** + * :member:`calloc` is a custom allocator function to replace + * :manpage:`calloc(3)`. + */ + ngtcp2_calloc calloc; + /** + * :member:`realloc` is a custom allocator function to replace + * :manpage:`realloc(3)`. + */ + ngtcp2_realloc realloc; +} ngtcp2_mem; + +/** + * @macrosection + * + * Time related macros + */ + +/** + * @macro + * + * :macro:`NGTCP2_SECONDS` is a count of tick which corresponds to 1 + * second. + */ +#define NGTCP2_SECONDS ((ngtcp2_duration)1000000000ULL) + +/** + * @macro + * + * :macro:`NGTCP2_MILLISECONDS` is a count of tick which corresponds + * to 1 millisecond. + */ +#define NGTCP2_MILLISECONDS ((ngtcp2_duration)1000000ULL) + +/** + * @macro + * + * :macro:`NGTCP2_MICROSECONDS` is a count of tick which corresponds + * to 1 microsecond. + */ +#define NGTCP2_MICROSECONDS ((ngtcp2_duration)1000ULL) + +/** + * @macro + * + * :macro:`NGTCP2_NANOSECONDS` is a count of tick which corresponds to + * 1 nanosecond. + */ +#define NGTCP2_NANOSECONDS ((ngtcp2_duration)1ULL) + +/** + * @macrosection + * + * QUIC protocol version macros + */ + +/** + * @macro + * + * :macro:`NGTCP2_PROTO_VER_V1` is the QUIC version 1. + */ +#define NGTCP2_PROTO_VER_V1 ((uint32_t)0x00000001u) + +/** + * @macro + * + * :macro:`NGTCP2_PROTO_VER_V2` is the QUIC version 2. See + * :rfc:`9369`. + */ +#define NGTCP2_PROTO_VER_V2 ((uint32_t)0x6b3343cfu) + +/** + * @macro + * + * :macro:`NGTCP2_PROTO_VER_MAX` is the highest QUIC version that this + * library supports. Deprecated since v1.1.0. + */ +#define NGTCP2_PROTO_VER_MAX NGTCP2_PROTO_VER_V1 + +/** + * @macro + * + * :macro:`NGTCP2_PROTO_VER_MIN` is the lowest QUIC version that this + * library supports. Deprecated since v1.1.0. + */ +#define NGTCP2_PROTO_VER_MIN NGTCP2_PROTO_VER_V1 + +/** + * @macro + * + * :macro:`NGTCP2_RESERVED_VERSION_MASK` is the bit mask of reserved + * version. + */ +#define NGTCP2_RESERVED_VERSION_MASK 0x0a0a0a0au + +/** + * @macrosection + * + * UDP datagram related macros + */ + +/** + * @macro + * + * :macro:`NGTCP2_MAX_UDP_PAYLOAD_SIZE` is the default maximum UDP + * datagram payload size that the local endpoint transmits. + */ +#define NGTCP2_MAX_UDP_PAYLOAD_SIZE 1200 + +/** + * @macro + * + * :macro:`NGTCP2_MAX_PMTUD_UDP_PAYLOAD_SIZE` is the maximum UDP + * datagram payload size that Path MTU Discovery can discover. + */ +#define NGTCP2_MAX_PMTUD_UDP_PAYLOAD_SIZE 1452 + +/** + * @macrosection + * + * QUIC specific macros + */ + +/** + * @macro + * + * :macro:`NGTCP2_MAX_VARINT` is the maximum value which can be + * encoded in variable-length integer encoding. + */ +#define NGTCP2_MAX_VARINT ((1ULL << 62) - 1) + +/** + * @macro + * + * :macro:`NGTCP2_STATELESS_RESET_TOKENLEN` is the length of Stateless + * Reset Token. + */ +#define NGTCP2_STATELESS_RESET_TOKENLEN 16 + +/** + * @macro + * + * :macro:`NGTCP2_MIN_STATELESS_RESET_RANDLEN` is the minimum length + * of random bytes (Unpredictable Bits) in Stateless Reset packet. + */ +#define NGTCP2_MIN_STATELESS_RESET_RANDLEN 5 + +/** + * @macro + * + * :macro:`NGTCP2_PATH_CHALLENGE_DATALEN` is the length of + * PATH_CHALLENGE data. + */ +#define NGTCP2_PATH_CHALLENGE_DATALEN 8 + +/** + * @macro + * + * :macro:`NGTCP2_RETRY_KEY_V1` is an encryption key to create + * 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" + +/** + * @macro + * + * :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" + +/** + * @macro + * + * :macro:`NGTCP2_RETRY_KEY_V2` is an encryption key to create + * integrity tag of Retry packet. It is used for QUIC v2. See + * :rfc:`9369`. + */ +#define NGTCP2_RETRY_KEY_V2 \ + "\x8f\xb4\xb0\x1b\x56\xac\x48\xe2\x60\xfb\xcb\xce\xad\x7c\xcc\x92" + +/** + * @macro + * + * :macro:`NGTCP2_RETRY_NONCE_V2` is nonce used when generating + * 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" + +/** + * @macro + * + * :macro:`NGTCP2_HP_MASKLEN` is the length of header protection mask. + */ +#define NGTCP2_HP_MASKLEN 5 + +/** + * @macro + * + * :macro:`NGTCP2_HP_SAMPLELEN` is the number bytes sampled when + * encrypting a packet header. + */ +#define NGTCP2_HP_SAMPLELEN 16 + +/** + * @macro + * + * :macro:`NGTCP2_DEFAULT_INITIAL_RTT` is a default initial RTT. + */ +#define NGTCP2_DEFAULT_INITIAL_RTT (333 * NGTCP2_MILLISECONDS) + +/** + * @macro + * + * :macro:`NGTCP2_MAX_CIDLEN` is the maximum length of Connection ID. + */ +#define NGTCP2_MAX_CIDLEN 20 + +/** + * @macro + * + * :macro:`NGTCP2_MIN_CIDLEN` is the minimum length of Connection ID. + */ +#define NGTCP2_MIN_CIDLEN 1 + +/** + * @macro + * + * :macro:`NGTCP2_MIN_INITIAL_DCIDLEN` is the minimum length of + * Destination Connection ID in Client Initial packet if it does not + * bear token from Retry packet. + */ +#define NGTCP2_MIN_INITIAL_DCIDLEN 8 + +/** + * @macrosection + * + * ECN related macros + */ + +/** + * @macro + * + * :macro:`NGTCP2_ECN_NOT_ECT` indicates no ECN marking. + */ +#define NGTCP2_ECN_NOT_ECT 0x0 + +/** + * @macro + * + * :macro:`NGTCP2_ECN_ECT_1` is ECT(1) codepoint. + */ +#define NGTCP2_ECN_ECT_1 0x1 + +/** + * @macro + * + * :macro:`NGTCP2_ECN_ECT_0` is ECT(0) codepoint. + */ +#define NGTCP2_ECN_ECT_0 0x2 + +/** + * @macro + * + * :macro:`NGTCP2_ECN_CE` is CE codepoint. + */ +#define NGTCP2_ECN_CE 0x3 + +/** + * @macro + * + * :macro:`NGTCP2_ECN_MASK` is a bit mask to get ECN marking. + */ +#define NGTCP2_ECN_MASK 0x3 + +#define NGTCP2_PKT_INFO_V1 1 +#define NGTCP2_PKT_INFO_VERSION NGTCP2_PKT_INFO_V1 + +/** + * @struct + * + * :type:`ngtcp2_pkt_info` is a packet metadata. + */ +typedef struct NGTCP2_ALIGN(8) ngtcp2_pkt_info { + /** + * :member:`ecn` is ECN marking, and when it is passed to + * `ngtcp2_conn_read_pkt()`, it should be either + * :macro:`NGTCP2_ECN_NOT_ECT`, :macro:`NGTCP2_ECN_ECT_1`, + * :macro:`NGTCP2_ECN_ECT_0`, or :macro:`NGTCP2_ECN_CE`. + */ + uint8_t ecn; +} ngtcp2_pkt_info; + +/** + * @macrosection + * + * ngtcp2 library error codes + */ + +/** + * @macro + * + * :macro:`NGTCP2_ERR_INVALID_ARGUMENT` indicates that a passed + * argument is invalid. + */ +#define NGTCP2_ERR_INVALID_ARGUMENT -201 +/** + * @macro + * + * :macro:`NGTCP2_ERR_NOBUF` indicates that a provided buffer does not + * have enough space to store data. + */ +#define NGTCP2_ERR_NOBUF -202 +/** + * @macro + * + * :macro:`NGTCP2_ERR_PROTO` indicates a general protocol error. + */ +#define NGTCP2_ERR_PROTO -203 +/** + * @macro + * + * :macro:`NGTCP2_ERR_INVALID_STATE` indicates that a requested + * operation is not allowed at the current connection state. + */ +#define NGTCP2_ERR_INVALID_STATE -204 +/** + * @macro + * + * :macro:`NGTCP2_ERR_ACK_FRAME` indicates that an invalid ACK frame + * is received. + */ +#define NGTCP2_ERR_ACK_FRAME -205 +/** + * @macro + * + * :macro:`NGTCP2_ERR_STREAM_ID_BLOCKED` indicates that there is no + * spare stream ID available. + */ +#define NGTCP2_ERR_STREAM_ID_BLOCKED -206 +/** + * @macro + * + * :macro:`NGTCP2_ERR_STREAM_IN_USE` indicates that a stream ID is + * already in use. + */ +#define NGTCP2_ERR_STREAM_IN_USE -207 +/** + * @macro + * + * :macro:`NGTCP2_ERR_STREAM_DATA_BLOCKED` indicates that stream data + * cannot be sent because of flow control. + */ +#define NGTCP2_ERR_STREAM_DATA_BLOCKED -208 +/** + * @macro + * + * :macro:`NGTCP2_ERR_FLOW_CONTROL` indicates flow control error. + */ +#define NGTCP2_ERR_FLOW_CONTROL -209 +/** + * @macro + * + * :macro:`NGTCP2_ERR_CONNECTION_ID_LIMIT` indicates that the number + * of received Connection ID exceeds acceptable limit. + */ +#define NGTCP2_ERR_CONNECTION_ID_LIMIT -210 +/** + * @macro + * + * :macro:`NGTCP2_ERR_STREAM_LIMIT` indicates that a remote endpoint + * opens more streams that is permitted. + */ +#define NGTCP2_ERR_STREAM_LIMIT -211 +/** + * @macro + * + * :macro:`NGTCP2_ERR_FINAL_SIZE` indicates that inconsistent final + * size of a stream. + */ +#define NGTCP2_ERR_FINAL_SIZE -212 +/** + * @macro + * + * :macro:`NGTCP2_ERR_CRYPTO` indicates crypto (TLS) related error. + */ +#define NGTCP2_ERR_CRYPTO -213 +/** + * @macro + * + * :macro:`NGTCP2_ERR_PKT_NUM_EXHAUSTED` indicates that packet number + * is exhausted. + */ +#define NGTCP2_ERR_PKT_NUM_EXHAUSTED -214 +/** + * @macro + * + * :macro:`NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM` indicates that a + * required transport parameter is missing. + */ +#define NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM -215 +/** + * @macro + * + * :macro:`NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM` indicates that a + * transport parameter is malformed. + */ +#define NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM -216 +/** + * @macro + * + * :macro:`NGTCP2_ERR_FRAME_ENCODING` indicates there is an error in + * frame encoding. + */ +#define NGTCP2_ERR_FRAME_ENCODING -217 +/** + * @macro + * + * :macro:`NGTCP2_ERR_DECRYPT` indicates a decryption failure. + */ +#define NGTCP2_ERR_DECRYPT -218 +/** + * @macro + * + * :macro:`NGTCP2_ERR_STREAM_SHUT_WR` indicates no more data can be + * sent to a stream. + */ +#define NGTCP2_ERR_STREAM_SHUT_WR -219 +/** + * @macro + * + * :macro:`NGTCP2_ERR_STREAM_NOT_FOUND` indicates that a stream was + * not found. + */ +#define NGTCP2_ERR_STREAM_NOT_FOUND -220 +/** + * @macro + * + * :macro:`NGTCP2_ERR_STREAM_STATE` indicates that a requested + * operation is not allowed at the current stream state. + */ +#define NGTCP2_ERR_STREAM_STATE -221 +/** + * @macro + * + * :macro:`NGTCP2_ERR_RECV_VERSION_NEGOTIATION` indicates that Version + * Negotiation packet was received. + */ +#define NGTCP2_ERR_RECV_VERSION_NEGOTIATION -222 +/** + * @macro + * + * :macro:`NGTCP2_ERR_CLOSING` indicates that connection is in closing + * state. + */ +#define NGTCP2_ERR_CLOSING -223 +/** + * @macro + * + * :macro:`NGTCP2_ERR_DRAINING` indicates that connection is in + * draining state. + */ +#define NGTCP2_ERR_DRAINING -224 +/** + * @macro + * + * :macro:`NGTCP2_ERR_TRANSPORT_PARAM` indicates a general transport + * parameter error. + */ +#define NGTCP2_ERR_TRANSPORT_PARAM -225 +/** + * @macro + * + * :macro:`NGTCP2_ERR_DISCARD_PKT` indicates a packet was discarded. + */ +#define NGTCP2_ERR_DISCARD_PKT -226 +/** + * @macro + * + * :macro:`NGTCP2_ERR_CONN_ID_BLOCKED` indicates that there is no + * spare Connection ID available. + */ +#define NGTCP2_ERR_CONN_ID_BLOCKED -227 +/** + * @macro + * + * :macro:`NGTCP2_ERR_INTERNAL` indicates an internal error. + */ +#define NGTCP2_ERR_INTERNAL -228 +/** + * @macro + * + * :macro:`NGTCP2_ERR_CRYPTO_BUFFER_EXCEEDED` indicates that a crypto + * buffer exceeded. + */ +#define NGTCP2_ERR_CRYPTO_BUFFER_EXCEEDED -229 +/** + * @macro + * + * :macro:`NGTCP2_ERR_WRITE_MORE` indicates + * :macro:`NGTCP2_WRITE_STREAM_FLAG_MORE` is used and a function call + * succeeded. + */ +#define NGTCP2_ERR_WRITE_MORE -230 +/** + * @macro + * + * :macro:`NGTCP2_ERR_RETRY` indicates that server should send Retry + * packet. + */ +#define NGTCP2_ERR_RETRY -231 +/** + * @macro + * + * :macro:`NGTCP2_ERR_DROP_CONN` indicates that an endpoint should + * drop connection immediately. + */ +#define NGTCP2_ERR_DROP_CONN -232 +/** + * @macro + * + * :macro:`NGTCP2_ERR_AEAD_LIMIT_REACHED` indicates AEAD encryption + * limit is reached and key update is not available. An endpoint + * should drop connection immediately. + */ +#define NGTCP2_ERR_AEAD_LIMIT_REACHED -233 +/** + * @macro + * + * :macro:`NGTCP2_ERR_NO_VIABLE_PATH` indicates that path validation + * could not probe that a path is capable of sending UDP datagram + * payload of size at least 1200 bytes. + */ +#define NGTCP2_ERR_NO_VIABLE_PATH -234 +/** + * @macro + * + * :macro:`NGTCP2_ERR_VERSION_NEGOTIATION` indicates that server + * should send Version Negotiation packet. + */ +#define NGTCP2_ERR_VERSION_NEGOTIATION -235 +/** + * @macro + * + * :macro:`NGTCP2_ERR_HANDSHAKE_TIMEOUT` indicates that QUIC + * connection is not established before the specified deadline. + */ +#define NGTCP2_ERR_HANDSHAKE_TIMEOUT -236 +/** + * @macro + * + * :macro:`NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE` indicates the + * version negotiation failed. + */ +#define NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE -237 +/** + * @macro + * + * :macro:`NGTCP2_ERR_IDLE_CLOSE` indicates the connection should be + * closed silently because of idle timeout. + */ +#define NGTCP2_ERR_IDLE_CLOSE -238 +/** + * @macro + * + * :macro:`NGTCP2_ERR_FATAL` indicates that error codes less than this + * value is fatal error. When this error is returned, an endpoint + * should close connection immediately. + */ +#define NGTCP2_ERR_FATAL -500 +/** + * @macro + * + * :macro:`NGTCP2_ERR_NOMEM` indicates out of memory. + */ +#define NGTCP2_ERR_NOMEM -501 +/** + * @macro + * + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` indicates that user defined + * callback function failed. + */ +#define NGTCP2_ERR_CALLBACK_FAILURE -502 + +/** + * @macrosection + * + * QUIC packet header flags + */ + +/** + * @macro + * + * :macro:`NGTCP2_PKT_FLAG_NONE` indicates no flag set. + */ +#define NGTCP2_PKT_FLAG_NONE 0x00u + +/** + * @macro + * + * :macro:`NGTCP2_PKT_FLAG_LONG_FORM` indicates the Long header packet + * header. + */ +#define NGTCP2_PKT_FLAG_LONG_FORM 0x01u + +/** + * @macro + * + * :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 + +/** + * @macro + * + * :macro:`NGTCP2_PKT_FLAG_KEY_PHASE` indicates Key Phase bit set. + */ +#define NGTCP2_PKT_FLAG_KEY_PHASE 0x04u + +/** + * @enum + * + * :type:`ngtcp2_pkt_type` defines QUIC version-independent QUIC + * packet types. + */ +typedef enum ngtcp2_pkt_type { + /** + * :enum:`NGTCP2_PKT_VERSION_NEGOTIATION` is defined by libngtcp2 + * for convenience. + */ + NGTCP2_PKT_VERSION_NEGOTIATION = 0x80, + /** + * :enum:`NGTCP2_PKT_STATELESS_RESET` is defined by libngtcp2 for + * convenience. + */ + NGTCP2_PKT_STATELESS_RESET = 0x81, + /** + * :enum:`NGTCP2_PKT_INITIAL` indicates Initial packet. + */ + NGTCP2_PKT_INITIAL = 0x10, + /** + * :enum:`NGTCP2_PKT_0RTT` indicates 0-RTT packet. + */ + NGTCP2_PKT_0RTT = 0x11, + /** + * :enum:`NGTCP2_PKT_HANDSHAKE` indicates Handshake packet. + */ + NGTCP2_PKT_HANDSHAKE = 0x12, + /** + * :enum:`NGTCP2_PKT_RETRY` indicates Retry packet. + */ + NGTCP2_PKT_RETRY = 0x13, + /** + * :enum:`NGTCP2_PKT_1RTT` is defined by libngtcp2 for convenience. + */ + NGTCP2_PKT_1RTT = 0x40 +} ngtcp2_pkt_type; + +/** + * @macrosection + * + * QUIC transport error code + */ + +/** + * @macro + * + * :macro:`NGTCP2_NO_ERROR` is QUIC transport error code ``NO_ERROR``. + */ +#define NGTCP2_NO_ERROR 0x0u + +/** + * @macro + * + * :macro:`NGTCP2_INTERNAL_ERROR` is QUIC transport error code + * ``INTERNAL_ERROR``. + */ +#define NGTCP2_INTERNAL_ERROR 0x1u + +/** + * @macro + * + * :macro:`NGTCP2_CONNECTION_REFUSED` is QUIC transport error code + * ``CONNECTION_REFUSED``. + */ +#define NGTCP2_CONNECTION_REFUSED 0x2u + +/** + * @macro + * + * :macro:`NGTCP2_FLOW_CONTROL_ERROR` is QUIC transport error code + * ``FLOW_CONTROL_ERROR``. + */ +#define NGTCP2_FLOW_CONTROL_ERROR 0x3u + +/** + * @macro + * + * :macro:`NGTCP2_STREAM_LIMIT_ERROR` is QUIC transport error code + * ``STREAM_LIMIT_ERROR``. + */ +#define NGTCP2_STREAM_LIMIT_ERROR 0x4u + +/** + * @macro + * + * :macro:`NGTCP2_STREAM_STATE_ERROR` is QUIC transport error code + * ``STREAM_STATE_ERROR``. + */ +#define NGTCP2_STREAM_STATE_ERROR 0x5u + +/** + * @macro + * + * :macro:`NGTCP2_FINAL_SIZE_ERROR` is QUIC transport error code + * ``FINAL_SIZE_ERROR``. + */ +#define NGTCP2_FINAL_SIZE_ERROR 0x6u + +/** + * @macro + * + * :macro:`NGTCP2_FRAME_ENCODING_ERROR` is QUIC transport error code + * ``FRAME_ENCODING_ERROR``. + */ +#define NGTCP2_FRAME_ENCODING_ERROR 0x7u + +/** + * @macro + * + * :macro:`NGTCP2_TRANSPORT_PARAMETER_ERROR` is QUIC transport error + * code ``TRANSPORT_PARAMETER_ERROR``. + */ +#define NGTCP2_TRANSPORT_PARAMETER_ERROR 0x8u + +/** + * @macro + * + * :macro:`NGTCP2_CONNECTION_ID_LIMIT_ERROR` is QUIC transport error + * code ``CONNECTION_ID_LIMIT_ERROR``. + */ +#define NGTCP2_CONNECTION_ID_LIMIT_ERROR 0x9u + +/** + * @macro + * + * :macro:`NGTCP2_PROTOCOL_VIOLATION` is QUIC transport error code + * ``PROTOCOL_VIOLATION``. + */ +#define NGTCP2_PROTOCOL_VIOLATION 0xau + +/** + * @macro + * + * :macro:`NGTCP2_INVALID_TOKEN` is QUIC transport error code + * ``INVALID_TOKEN``. + */ +#define NGTCP2_INVALID_TOKEN 0xbu + +/** + * @macro + * + * :macro:`NGTCP2_APPLICATION_ERROR` is QUIC transport error code + * ``APPLICATION_ERROR``. + */ +#define NGTCP2_APPLICATION_ERROR 0xcu + +/** + * @macro + * + * :macro:`NGTCP2_CRYPTO_BUFFER_EXCEEDED` is QUIC transport error code + * ``CRYPTO_BUFFER_EXCEEDED``. + */ +#define NGTCP2_CRYPTO_BUFFER_EXCEEDED 0xdu + +/** + * @macro + * + * :macro:`NGTCP2_KEY_UPDATE_ERROR` is QUIC transport error code + * ``KEY_UPDATE_ERROR``. + */ +#define NGTCP2_KEY_UPDATE_ERROR 0xeu + +/** + * @macro + * + * :macro:`NGTCP2_AEAD_LIMIT_REACHED` is QUIC transport error code + * ``AEAD_LIMIT_REACHED``. + */ +#define NGTCP2_AEAD_LIMIT_REACHED 0xfu + +/** + * @macro + * + * :macro:`NGTCP2_NO_VIABLE_PATH` is QUIC transport error code + * ``NO_VIABLE_PATH``. + */ +#define NGTCP2_NO_VIABLE_PATH 0x10u + +/** + * @macro + * + * :macro:`NGTCP2_CRYPTO_ERROR` is QUIC transport error code + * ``CRYPTO_ERROR``. + */ +#define NGTCP2_CRYPTO_ERROR 0x100u + +/** + * @macro + * + * :macro:`NGTCP2_VERSION_NEGOTIATION_ERROR` is QUIC transport error + * code ``VERSION_NEGOTIATION_ERROR``. See :rfc:`9368`. + */ +#define NGTCP2_VERSION_NEGOTIATION_ERROR 0x11 + +/** + * @enum + * + * :type:`ngtcp2_path_validation_result` defines path validation + * result code. + */ +typedef enum ngtcp2_path_validation_result { + /** + * :enum:`NGTCP2_PATH_VALIDATION_RESULT_SUCCESS` indicates + * successful validation. + */ + NGTCP2_PATH_VALIDATION_RESULT_SUCCESS, + /** + * :enum:`NGTCP2_PATH_VALIDATION_RESULT_FAILURE` indicates + * validation failure. + */ + NGTCP2_PATH_VALIDATION_RESULT_FAILURE, + /** + * :enum:`NGTCP2_PATH_VALIDATION_RESULT_ABORTED` indicates that path + * validation was aborted. + */ + NGTCP2_PATH_VALIDATION_RESULT_ABORTED +} ngtcp2_path_validation_result; + +/** + * @typedef + * + * :type:`ngtcp2_tstamp` is a timestamp with nanosecond resolution. + * ``UINT64_MAX`` is an invalid value, and it is often used to + * indicate that no value is set. + */ +typedef uint64_t ngtcp2_tstamp; + +/** + * @typedef + * + * :type:`ngtcp2_duration` is a period of time in nanosecond + * resolution. ``UINT64_MAX`` is an invalid value, and it is often + * used to indicate that no value is set. + */ +typedef uint64_t ngtcp2_duration; + +/** + * @struct + * + * :type:`ngtcp2_cid` holds a Connection ID. + */ +typedef struct ngtcp2_cid { + /** + * :member:`datalen` is the length of Connection ID. + */ + size_t datalen; + /** + * :member:`data` is the buffer to store Connection ID. + */ + uint8_t data[NGTCP2_MAX_CIDLEN]; +} ngtcp2_cid; + +/** + * @struct + * + * :type:`ngtcp2_vec` is struct iovec compatible structure to + * reference arbitrary array of bytes. + */ +typedef struct ngtcp2_vec { + /** + * :member:`base` points to the data. + */ + uint8_t *base; + /** + * :member:`len` is the number of bytes which the buffer pointed by + * base contains. + */ + size_t len; +} ngtcp2_vec; + +/** + * @function + * + * `ngtcp2_cid_init` initializes Connection ID |cid| with the byte + * string pointed by |data| and its length is |datalen|. |datalen| + * must be at most :macro:`NGTCP2_MAX_CIDLEN`. + */ +NGTCP2_EXTERN void ngtcp2_cid_init(ngtcp2_cid *cid, const uint8_t *data, + size_t datalen); + +/** + * @function + * + * `ngtcp2_cid_eq` returns nonzero if |a| and |b| share the same + * Connection ID. + */ +NGTCP2_EXTERN int ngtcp2_cid_eq(const ngtcp2_cid *a, const ngtcp2_cid *b); + +/** + * @struct + * + * :type:`ngtcp2_pkt_hd` represents QUIC packet header. + */ +typedef struct ngtcp2_pkt_hd { + /** + * :member:`dcid` is Destination Connection ID. + */ + ngtcp2_cid dcid; + /** + * :member:`scid` is Source Connection ID. + */ + ngtcp2_cid scid; + /** + * :member:`pkt_num` is a packet number. + */ + int64_t pkt_num; + /** + * :member:`token` contains token. Only Initial packet may contain + * token. NULL if no token is present. + */ + const uint8_t *token; + /** + * :member:`tokenlen` is the length of :member:`token`. 0 if no + * token is present. + */ + size_t tokenlen; + /** + * :member:`pkt_numlen` is the number of bytes spent to encode + * :member:`pkt_num`. + */ + size_t pkt_numlen; + /** + * :member:`len` is the sum of :member:`pkt_numlen` and the length + * of QUIC packet payload. + */ + size_t len; + /** + * :member:`version` is QUIC version. + */ + uint32_t version; + /** + * :member:`type` is a type of QUIC packet. This field does not + * have a QUIC packet type defined for a specific QUIC version. + * Instead, it contains version independent packet type defined by + * this library. See :type:`ngtcp2_pkt_type`. + */ + uint8_t type; + /** + * :member:`flags` is zero or more of :macro:`NGTCP2_PKT_FLAG_* + * <NGTCP2_PKT_FLAG_NONE>`. + */ + uint8_t flags; +} ngtcp2_pkt_hd; + +/** + * @struct + * + * :type:`ngtcp2_pkt_stateless_reset` represents Stateless Reset. + */ +typedef struct ngtcp2_pkt_stateless_reset { + /** + * :member:`stateless_reset_token` contains stateless reset token. + */ + uint8_t stateless_reset_token[NGTCP2_STATELESS_RESET_TOKENLEN]; + /** + * :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_reset; + +/** + * @macrosection + * + * QUIC transport parameters related macros + */ + +/** + * @macro + * + * :macro:`NGTCP2_DEFAULT_MAX_RECV_UDP_PAYLOAD_SIZE` is the default + * value of max_udp_payload_size transport parameter. + */ +#define NGTCP2_DEFAULT_MAX_RECV_UDP_PAYLOAD_SIZE 65527 + +/** + * @macro + * + * :macro:`NGTCP2_DEFAULT_ACK_DELAY_EXPONENT` is a default value of + * scaling factor of ACK Delay field in ACK frame. + */ +#define NGTCP2_DEFAULT_ACK_DELAY_EXPONENT 3 + +/** + * @macro + * + * :macro:`NGTCP2_DEFAULT_MAX_ACK_DELAY` is a default value of the + * maximum amount of time in nanoseconds by which endpoint delays + * sending acknowledgement. + */ +#define NGTCP2_DEFAULT_MAX_ACK_DELAY (25 * NGTCP2_MILLISECONDS) + +/** + * @macro + * + * :macro:`NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT` is the default + * value of active_connection_id_limit transport parameter value if + * omitted. + */ +#define NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT 2 + +/** + * @macro + * + * :macro:`NGTCP2_TLSEXT_QUIC_TRANSPORT_PARAMETERS_V1` is TLS + * extension type of quic_transport_parameters. + */ +#define NGTCP2_TLSEXT_QUIC_TRANSPORT_PARAMETERS_V1 0x39u + +#ifdef NGTCP2_USE_GENERIC_SOCKADDR +# ifndef NGTCP2_AF_INET +# error NGTCP2_AF_INET must be defined +# endif /* !NGTCP2_AF_INET */ + +# ifndef NGTCP2_AF_INET6 +# error NGTCP2_AF_INET6 must be defined +# endif /* !NGTCP2_AF_INET6 */ + +typedef unsigned short int ngtcp2_sa_family; +typedef uint16_t ngtcp2_in_port; + +typedef struct ngtcp2_sockaddr { + ngtcp2_sa_family sa_family; + uint8_t sa_data[14]; +} ngtcp2_sockaddr; + +typedef struct ngtcp2_in_addr { + uint32_t s_addr; +} ngtcp2_in_addr; + +typedef struct ngtcp2_sockaddr_in { + ngtcp2_sa_family sin_family; + ngtcp2_in_port sin_port; + ngtcp2_in_addr sin_addr; + uint8_t sin_zero[8]; +} ngtcp2_sockaddr_in; + +typedef struct ngtcp2_in6_addr { + uint8_t in6_addr[16]; +} ngtcp2_in6_addr; + +typedef struct ngtcp2_sockaddr_in6 { + ngtcp2_sa_family sin6_family; + ngtcp2_in_port sin6_port; + uint32_t sin6_flowinfo; + ngtcp2_in6_addr sin6_addr; + uint32_t sin6_scope_id; +} ngtcp2_sockaddr_in6; + +typedef uint32_t ngtcp2_socklen; +#else /* !NGTCP2_USE_GENERIC_SOCKADDR */ +# define NGTCP2_AF_INET AF_INET +# define NGTCP2_AF_INET6 AF_INET6 + +/** + * @typedef + * + * :type:`ngtcp2_sockaddr` is typedefed to struct sockaddr. If + * :macro:`NGTCP2_USE_GENERIC_SOCKADDR` is defined, it is typedefed to + * the generic struct sockaddr defined in ngtcp2.h. + */ +typedef struct sockaddr ngtcp2_sockaddr; +/** + * @typedef + * + * :type:`ngtcp2_sockaddr_in` is typedefed to struct sockaddr_in. If + * :macro:`NGTCP2_USE_GENERIC_SOCKADDR` is defined, it is typedefed to + * the generic struct sockaddr_in defined in ngtcp2.h. + */ +typedef struct sockaddr_in ngtcp2_sockaddr_in; +/** + * @typedef + * + * :type:`ngtcp2_sockaddr_in6` is typedefed to struct sockaddr_in6. + * If :macro:`NGTCP2_USE_GENERIC_SOCKADDR` is defined, it is typedefed + * to the generic struct sockaddr_in6 defined in ngtcp2.h. + */ +typedef struct sockaddr_in6 ngtcp2_sockaddr_in6; +/** + * @typedef + * + * :type:`ngtcp2_socklen` is typedefed to socklen_t. If + * :macro:`NGTCP2_USE_GENERIC_SOCKADDR` is defined, it is typedefed to + * uint32_t. + */ +typedef socklen_t ngtcp2_socklen; +#endif /* !NGTCP2_USE_GENERIC_SOCKADDR */ + +/** + * @struct + * + * :type:`ngtcp2_sockaddr_union` conveniently includes all supported + * address types. + */ +typedef union ngtcp2_sockaddr_union { + ngtcp2_sockaddr sa; + ngtcp2_sockaddr_in in; + ngtcp2_sockaddr_in6 in6; +} ngtcp2_sockaddr_union; + +/** + * @struct + * + * :type:`ngtcp2_preferred_addr` represents preferred address + * structure. + */ +typedef struct ngtcp2_preferred_addr { + /** + * :member:`cid` is a Connection ID. + */ + ngtcp2_cid cid; + /** + * :member:`ipv4` contains IPv4 address and port. + */ + ngtcp2_sockaddr_in ipv4; + /** + * :member:`ipv6` contains IPv6 address and port. + */ + ngtcp2_sockaddr_in6 ipv6; + /** + * :member:`ipv4_present` indicates that :member:`ipv4` contains + * IPv4 address and port. + */ + uint8_t ipv4_present; + /** + * :member:`ipv6_present` indicates that :member:`ipv6` contains + * IPv6 address and port. + */ + uint8_t ipv6_present; + /** + * :member:`stateless_reset_token` contains stateless reset token. + */ + uint8_t stateless_reset_token[NGTCP2_STATELESS_RESET_TOKENLEN]; +} ngtcp2_preferred_addr; + +/** + * @struct + * + * :type:`ngtcp2_version_info` represents version_information + * structure. See :rfc:`9368`. + */ +typedef struct ngtcp2_version_info { + /** + * :member:`chosen_version` is the version chosen by the sender. + */ + uint32_t chosen_version; + /** + * :member:`available_versions` points the wire image of + * available_versions field. The each version is therefore in + * network byte order. + */ + const uint8_t *available_versions; + /** + * :member:`available_versionslen` is the number of bytes pointed by + * :member:`available_versions`, not the number of versions + * included. + */ + size_t available_versionslen; +} ngtcp2_version_info; + +#define NGTCP2_TRANSPORT_PARAMS_V1 1 +#define NGTCP2_TRANSPORT_PARAMS_VERSION NGTCP2_TRANSPORT_PARAMS_V1 + +/** + * @struct + * + * :type:`ngtcp2_transport_params` represents QUIC transport + * parameters. + */ +typedef struct ngtcp2_transport_params { + /** + * :member:`preferred_addr` contains preferred address if + * :member:`preferred_addr_present` is nonzero. + */ + ngtcp2_preferred_addr preferred_addr; + /** + * :member:`original_dcid` is the Destination Connection ID field + * from the first Initial packet from client. Server must specify + * this field and set :member:`original_dcid_present` to nonzero. + * It is expected that application knows the original Destination + * Connection ID even if it sends Retry packet, for example, by + * including it in retry token. Otherwise, application should not + * specify this field. + */ + ngtcp2_cid original_dcid; + /** + * :member:`initial_scid` is the Source Connection ID field from the + * first Initial packet the local endpoint sends. Application + * should not specify this field. If :member:`initial_scid_present` + * is set to nonzero, it indicates this field is set. + */ + ngtcp2_cid initial_scid; + /** + * :member:`retry_scid` is the Source Connection ID field from Retry + * packet. Only server uses this field. If server application + * received Initial packet with retry token from client, and server + * successfully verified its token, server application must set + * Destination Connection ID field from the Initial packet to this + * field, and set :member:`retry_scid_present` to nonzero. Server + * application must verify that the Destination Connection ID from + * Initial packet was sent in Retry packet by, for example, + * including the Connection ID in a token, or including it in AAD + * when encrypting a token. + */ + ngtcp2_cid retry_scid; + /** + * :member:`initial_max_stream_data_bidi_local` is the size of flow + * control window of locally initiated stream. This is the number + * of bytes that the remote endpoint can send, and the local + * endpoint must ensure that it has enough buffer to receive them. + */ + uint64_t initial_max_stream_data_bidi_local; + /** + * :member:`initial_max_stream_data_bidi_remote` is the size of flow + * control window of remotely initiated stream. This is the number + * of bytes that the remote endpoint can send, and the local + * endpoint must ensure that it has enough buffer to receive them. + */ + uint64_t initial_max_stream_data_bidi_remote; + /** + * :member:`initial_max_stream_data_uni` is the size of flow control + * window of remotely initiated unidirectional stream. This is the + * number of bytes that the remote endpoint can send, and the local + * endpoint must ensure that it has enough buffer to receive them. + */ + uint64_t initial_max_stream_data_uni; + /** + * :member:`initial_max_data` is the connection level flow control + * window. + */ + uint64_t initial_max_data; + /** + * :member:`initial_max_streams_bidi` is the number of concurrent + * streams that the remote endpoint can create. + */ + uint64_t initial_max_streams_bidi; + /** + * :member:`initial_max_streams_uni` is the number of concurrent + * unidirectional streams that the remote endpoint can create. + */ + uint64_t initial_max_streams_uni; + /** + * :member:`max_idle_timeout` is a duration during which sender + * allows quiescent. 0 means no idle timeout. It must not be + * UINT64_MAX. + */ + ngtcp2_duration max_idle_timeout; + /** + * :member:`max_udp_payload_size` is the maximum UDP payload size + * that the local endpoint can receive. + */ + uint64_t max_udp_payload_size; + /** + * :member:`active_connection_id_limit` is the maximum number of + * Connection ID that sender can store. + */ + uint64_t active_connection_id_limit; + /** + * :member:`ack_delay_exponent` is the exponent used in ACK Delay + * field in ACK frame. + */ + uint64_t ack_delay_exponent; + /** + * :member:`max_ack_delay` is the maximum acknowledgement delay by + * which the local endpoint will delay sending acknowledgements. It + * must be strictly less than (1 << 14) milliseconds. + * Sub-millisecond part is dropped when sending it in a QUIC + * transport parameter. + */ + ngtcp2_duration max_ack_delay; + /** + * :member:`max_datagram_frame_size` is the maximum size of DATAGRAM + * frame that the local endpoint willingly receives. Specifying 0 + * disables DATAGRAM support. See :rfc:`9221`. + */ + uint64_t max_datagram_frame_size; + /** + * :member:`stateless_reset_token_present` is nonzero if + * :member:`stateless_reset_token` field is set. + */ + uint8_t stateless_reset_token_present; + /** + * :member:`disable_active_migration` is nonzero if the local + * endpoint does not support active connection migration. + */ + uint8_t disable_active_migration; + /** + * :member:`original_dcid_present` is nonzero if + * :member:`original_dcid` field is set. + */ + uint8_t original_dcid_present; + /** + * :member:`initial_scid_present` is nonzero if + * :member:`initial_scid` field is set. + */ + uint8_t initial_scid_present; + /** + * :member:`retry_scid_present` is nonzero if :member:`retry_scid` + * field is set. + */ + uint8_t retry_scid_present; + /** + * :member:`preferred_addr_present` is nonzero if + * :member:`preferred_address` is set. + */ + uint8_t preferred_addr_present; + /** + * :member:`stateless_reset_token` contains stateless reset token. + */ + uint8_t stateless_reset_token[NGTCP2_STATELESS_RESET_TOKENLEN]; + /** + * :member:`grease_quic_bit` is nonzero if sender supports "Greasing + * the QUIC Bit" extension. See :rfc:`9287`. + */ + uint8_t grease_quic_bit; + /** + * :member:`version_info` contains version_information field if + * :member:`version_info_present` is nonzero. Application should + * not specify this field. + */ + ngtcp2_version_info version_info; + /** + * :member:`version_info_present` is nonzero if + * :member:`version_info` is set. Application should not specify + * this field. + */ + uint8_t version_info_present; +} ngtcp2_transport_params; + +#define NGTCP2_CONN_INFO_V1 1 +#define NGTCP2_CONN_INFO_VERSION NGTCP2_CONN_INFO_V1 + +/** + * @struct + * + * :type:`ngtcp2_conn_info` holds various connection statistics. + */ +typedef struct ngtcp2_conn_info { + /** + * :member:`latest_rtt` is the latest RTT sample which is not + * adjusted by acknowledgement delay. + */ + ngtcp2_duration latest_rtt; + /** + * :member:`min_rtt` is the minimum RTT seen so far. It is not + * adjusted by acknowledgement delay. + */ + ngtcp2_duration min_rtt; + /** + * :member:`smoothed_rtt` is the smoothed RTT. + */ + ngtcp2_duration smoothed_rtt; + /** + * :member:`rttvar` is a mean deviation of observed RTT. + */ + ngtcp2_duration rttvar; + /** + * :member:`cwnd` is the size of congestion window. + */ + uint64_t cwnd; + /** + * :member:`ssthresh` is slow start threshold. + */ + uint64_t ssthresh; + /** + * :member:`bytes_in_flight` is the number in bytes of all sent + * packets which have not been acknowledged. + */ + uint64_t bytes_in_flight; +} ngtcp2_conn_info; + +/** + * @enum + * + * :type:`ngtcp2_cc_algo` defines congestion control algorithms. + */ +typedef enum ngtcp2_cc_algo { + /** + * :enum:`NGTCP2_CC_ALGO_RENO` represents Reno. + */ + NGTCP2_CC_ALGO_RENO = 0x00, + /** + * :enum:`NGTCP2_CC_ALGO_CUBIC` represents Cubic. + */ + NGTCP2_CC_ALGO_CUBIC = 0x01, + /** + * :enum:`NGTCP2_CC_ALGO_BBR` represents BBR v2. + */ + NGTCP2_CC_ALGO_BBR = 0x02 +} ngtcp2_cc_algo; + +/** + * @functypedef + * + * :type:`ngtcp2_printf` is a callback function for logging. + * |user_data| is the same object passed to `ngtcp2_conn_client_new` + * or `ngtcp2_conn_server_new`. + */ +typedef void (*ngtcp2_printf)(void *user_data, const char *format, ...); + +/** + * @macrosection + * + * QLog related macros + */ + +/** + * @macro + * + * :macro:`NGTCP2_QLOG_WRITE_FLAG_NONE` indicates no flag set. + */ +#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 + +/** + * @struct + * + * :type:`ngtcp2_rand_ctx` is a wrapper around native random number + * generator. It is opaque to the ngtcp2 library. This might be + * useful if application needs to specify random number generator per + * thread or per connection. + */ +typedef struct ngtcp2_rand_ctx { + /** + * :member:`native_handle` is a pointer to an underlying random + * number generator. + */ + void *native_handle; +} ngtcp2_rand_ctx; + +/** + * @functypedef + * + * :type:`ngtcp2_qlog_write` is a callback function which is called to + * write qlog |data| of length |datalen| bytes. |flags| is bitwise OR + * of zero or more of :macro:`NGTCP2_QLOG_WRITE_FLAG_* + * <NGTCP2_QLOG_WRITE_FLAG_NONE>`. If + * :macro:`NGTCP2_QLOG_WRITE_FLAG_FIN` is set, |datalen| may be 0. + */ +typedef void (*ngtcp2_qlog_write)(void *user_data, uint32_t flags, + const void *data, size_t datalen); + +/** + * @enum + * + * :type:`ngtcp2_token_type` defines the type of token. + */ +typedef enum ngtcp2_token_type { + /** + * :enum:`NGTCP2_TOKEN_TYPE_UNKNOWN` indicates that the type of + * token is unknown. + */ + NGTCP2_TOKEN_TYPE_UNKNOWN, + /** + * :enum:`NGTCP2_TOKEN_TYPE_RETRY` indicates that a token comes from + * Retry packet. + */ + NGTCP2_TOKEN_TYPE_RETRY, + /** + * :enum:`NGTCP2_TOKEN_TYPE_NEW_TOKEN` indicates that a token comes + * from NEW_TOKEN frame. + */ + NGTCP2_TOKEN_TYPE_NEW_TOKEN +} ngtcp2_token_type; + +#define NGTCP2_SETTINGS_V1 1 +#define NGTCP2_SETTINGS_VERSION NGTCP2_SETTINGS_V1 + +/** + * @struct + * + * :type:`ngtcp2_settings` defines QUIC connection settings. + */ +typedef struct ngtcp2_settings { + /** + * :member:`qlog_write` is a callback function to write qlog. + * Setting ``NULL`` disables qlog. + */ + ngtcp2_qlog_write qlog_write; + /** + * :member:`cc_algo` specifies congestion control algorithm. + */ + ngtcp2_cc_algo cc_algo; + /** + * :member:`initial_ts` is an initial timestamp given to the + * library. + */ + ngtcp2_tstamp initial_ts; + /** + * :member:`initial_rtt` is an initial RTT. + */ + ngtcp2_duration initial_rtt; + /** + * :member:`log_printf` is a function that the library uses to write + * logs. ``NULL`` means no logging output. It is nothing to do + * with qlog. + */ + ngtcp2_printf log_printf; + /** + * :member:`max_tx_udp_payload_size` is the maximum size of UDP + * datagram payload that the local endpoint transmits. It is used + * by congestion controller to compute congestion window. + */ + size_t max_tx_udp_payload_size; + /** + * :member:`token` is a token from Retry packet or NEW_TOKEN frame. + * + * Server sets this field if it received the token in Client Initial + * packet and successfully validated. It should also set + * :member:`token_type` field. + * + * Client sets this field if it intends to send token in its Initial + * packet. + * + * `ngtcp2_conn_server_new` and `ngtcp2_conn_client_new` make a copy + * of token. + * + * Set NULL if there is no token. + */ + const uint8_t *token; + /** + * :member:`tokenlen` is the length of :member:`token`. Set 0 if + * there is no token. + */ + size_t tokenlen; + /** + * :member:`token_type` is the type of token. Server application + * should set this field. + */ + ngtcp2_token_type token_type; + /** + * :member:`rand_ctx` is an optional random number generator to be + * passed to :type:`ngtcp2_rand` callback. + */ + ngtcp2_rand_ctx rand_ctx; + /** + * :member:`max_window` is the maximum connection-level flow control + * window if connection-level window auto-tuning is enabled. The + * connection-level window auto tuning is enabled if nonzero value + * is specified in this field. The initial value of window size is + * :member:`ngtcp2_transport_params.initial_max_data`. The window + * size is scaled up to the value specified in this field. + */ + uint64_t max_window; + /** + * :member:`max_stream_window` is the maximum stream-level flow + * control window if stream-level window auto-tuning is enabled. + * The stream-level window auto-tuning is enabled if nonzero value + * is specified in this field. The initial value of window size is + * :member:`ngtcp2_transport_params.initial_max_stream_data_bidi_remote`, + * :member:`ngtcp2_transport_params.initial_max_stream_data_bidi_local`, + * or :member:`ngtcp2_transport_params.initial_max_stream_data_uni`, + * depending on the type of stream. The window size is scaled up to + * the value specified in this field. + */ + 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. + */ + size_t ack_thresh; + /** + * :member:`no_tx_udp_payload_size_shaping`, if set to nonzero, + * instructs the library not to limit the UDP payload size to + * :macro:`NGTCP2_MAX_UDP_PAYLOAD_SIZE` (which can be extended by + * Path MTU Discovery), and instead use the minimum size among the + * given buffer size, :member:`max_tx_udp_payload_size`, and the + * received max_udp_payload_size QUIC transport parameter. + */ + uint8_t no_tx_udp_payload_size_shaping; + /** + * :member:`handshake_timeout` is the period of time before giving + * up QUIC connection establishment. If QUIC handshake is not + * complete within this period, `ngtcp2_conn_handle_expiry` returns + * :macro:`NGTCP2_ERR_HANDSHAKE_TIMEOUT` error. The deadline is + * :member:`initial_ts` + :member:`handshake_timeout`. If this + * field is set to ``UINT64_MAX``, no handshake timeout is set. + */ + ngtcp2_duration handshake_timeout; + /** + * :member:`preferred_versions` is the array of versions that are + * preferred by the local endpoint. All versions set in this array + * must be supported by the library, and compatible to QUIC v1. The + * reserved versions are not allowed. They are sorted in the order + * of preference. + * + * On compatible version negotiation, server will negotiate one of + * those versions contained in this array if there is some overlap + * between these versions and the versions offered by the client. + * If there is no overlap, but the client chosen version is + * supported by the library, the server chooses the client chosen + * version as the negotiated version. This version set corresponds + * to Offered Versions described in :rfc:`9368`, and it should be + * included in Version Negotiation packet. + * + * Client uses this field and :member:`original_version` to prevent + * version downgrade attack if it reacted upon Version Negotiation + * packet. If this field is specified, client must include + * |client_chosen_version| passed to `ngtcp2_conn_client_new` unless + * |client_chosen_version| is a reserved version. + */ + const uint32_t *preferred_versions; + /** + * :member:`preferred_versionslen` is the number of versions that + * are contained in the array pointed by + * :member:`preferred_versions`. + */ + size_t preferred_versionslen; + /** + * :member:`available_versions` is the array of versions that are + * going to be set in :member:`available_versions + * <ngtcp2_version_info.available_versions>` field of outgoing + * version_information QUIC transport parameter. + * + * For server, this corresponds to Fully-Deployed Versions described + * in :rfc:`9368`. If this field is not set, it is set to + * :member:`preferred_versions` internally if + * :member:`preferred_versionslen` is not zero. If this field is + * not set, and :member:`preferred_versionslen` is zero, this field + * is set to :macro:`NGTCP2_PROTO_VER_V1` internally. + * + * Client must include |client_chosen_version| passed to + * `ngtcp2_conn_client_new` in this array if this field is set and + * |client_chosen_version| is not a reserved version. If this field + * is not set, |client_chosen_version| passed to + * `ngtcp2_conn_client_new` will be set in this field internally + * unless |client_chosen_version| is a reserved version. + */ + const uint32_t *available_versions; + /** + * :member:`available_versionslen` is the number of versions that + * are contained in the array pointed by + * :member:`available_versions`. + */ + size_t available_versionslen; + /** + * :member:`original_version` is the original version that client + * initially used to make a connection attempt. If it is set, and + * it differs from |client_chosen_version| passed to + * `ngtcp2_conn_client_new`, the library assumes that client reacted + * upon Version Negotiation packet. Server does not use this field. + */ + uint32_t original_version; + /** + * :member:`no_pmtud`, if set to nonzero, disables Path MTU + * Discovery. + */ + uint8_t no_pmtud; + /** + * :member:`pkt_num` is the initial packet number for each packet + * number space. It must be in range [0, INT32_MAX], inclusive. + */ + uint32_t initial_pkt_num; +} ngtcp2_settings; + +/** + * @struct + * + * :type:`ngtcp2_addr` is the endpoint address. + */ +typedef struct ngtcp2_addr { + /** + * :member:`addr` points to the buffer which contains endpoint + * address. It must not be ``NULL``. + */ + ngtcp2_sockaddr *addr; + /** + * :member:`addrlen` is the length of :member:`addr`. It must not + * be longer than sizeof(:type:`ngtcp2_sockaddr_union`). + */ + ngtcp2_socklen addrlen; +} ngtcp2_addr; + +/** + * @struct + * + * :type:`ngtcp2_path` is the network endpoints where a packet is sent + * and received. + */ +typedef struct ngtcp2_path { + /** + * :member:`local` is the address of local endpoint. + */ + ngtcp2_addr local; + /** + * :member:`remote` is the address of remote endpoint. + */ + ngtcp2_addr remote; + /** + * :member:`user_data` is an arbitrary data and opaque to the + * library. + * + * Note that :type:`ngtcp2_path` is generally passed to + * :type:`ngtcp2_conn` by an application, and :type:`ngtcp2_conn` + * stores their copies. Unfortunately, there is no way for the + * application to know when :type:`ngtcp2_conn` finished using a + * specific :type:`ngtcp2_path` object in mid connection, which + * means that the application cannot free the data pointed by this + * field. Therefore, it is advised to use this field only when the + * data pointed by this field persists in an entire lifetime of the + * connection. + */ + void *user_data; +} ngtcp2_path; + +/** + * @struct + * + * :type:`ngtcp2_path_storage` is a convenient struct to have buffers + * to store the longest addresses. + */ +typedef struct ngtcp2_path_storage { + /** + * :member:`path` stores network path. + */ + ngtcp2_path path; + /** + * :member:`local_addrbuf` is a buffer to store local address. + */ + ngtcp2_sockaddr_union local_addrbuf; + /** + * :member:`remote_addrbuf` is a buffer to store remote address. + */ + ngtcp2_sockaddr_union remote_addrbuf; +} ngtcp2_path_storage; + +/** + * @struct + * + * :type:`ngtcp2_crypto_md` is a wrapper around native message digest + * object. + */ +typedef struct ngtcp2_crypto_md { + /** + * :member:`native_handle` is a pointer to an underlying message + * digest object. + */ + void *native_handle; +} ngtcp2_crypto_md; + +/** + * @struct + * + * :type:`ngtcp2_crypto_aead` is a wrapper around native AEAD object. + */ +typedef struct ngtcp2_crypto_aead { + /** + * :member:`native_handle` is a pointer to an underlying AEAD + * object. + */ + void *native_handle; + /** + * :member:`max_overhead` is the number of additional bytes which + * AEAD encryption needs on encryption. + */ + size_t max_overhead; +} ngtcp2_crypto_aead; + +/** + * @struct + * + * :type:`ngtcp2_crypto_cipher` is a wrapper around native cipher + * object. + */ +typedef struct ngtcp2_crypto_cipher { + /** + * :member:`native_handle` is a pointer to an underlying cipher + * object. + */ + void *native_handle; +} ngtcp2_crypto_cipher; + +/** + * @struct + * + * :type:`ngtcp2_crypto_aead_ctx` is a wrapper around native AEAD + * cipher context object. It should be initialized with a specific + * key. ngtcp2 library reuses this context object to encrypt or + * decrypt multiple packets. + */ +typedef struct ngtcp2_crypto_aead_ctx { + /** + * :member:`native_handle` is a pointer to an underlying AEAD + * context object. + */ + void *native_handle; +} ngtcp2_crypto_aead_ctx; + +/** + * @struct + * + * :type:`ngtcp2_crypto_cipher_ctx` is a wrapper around native cipher + * context object. It should be initialized with a specific key. + * ngtcp2 library reuses this context object to encrypt or decrypt + * multiple packet headers. + */ +typedef struct ngtcp2_crypto_cipher_ctx { + /** + * :member:`native_handle` is a pointer to an underlying cipher + * context object. + */ + void *native_handle; +} ngtcp2_crypto_cipher_ctx; + +/** + * @struct + * + * :type:`ngtcp2_crypto_ctx` is a convenient structure to bind all + * crypto related objects in one place. Use + * `ngtcp2_crypto_ctx_initial` to initialize this struct for Initial + * packet encryption. For Handshake and 1-RTT packets, use + * `ngtcp2_crypto_ctx_tls`. For 0-RTT packets, use + * `ngtcp2_crypto_ctx_tls_early`. + */ +typedef struct ngtcp2_crypto_ctx { + /** + * :member:`aead` is AEAD object. + */ + ngtcp2_crypto_aead aead; + /** + * :member:`md` is message digest object. + */ + ngtcp2_crypto_md md; + /** + * :member:`hp` is header protection cipher. + */ + ngtcp2_crypto_cipher hp; + /** + * :member:`max_encryption` is the number of encryption which this + * key can be used with. + */ + uint64_t max_encryption; + /** + * :member:`max_decryption_failure` is the number of decryption + * failure with this key. + */ + uint64_t max_decryption_failure; +} ngtcp2_crypto_ctx; + +/** + * @function + * + * `ngtcp2_transport_params_encode` encodes |params| in |dest| of + * length |destlen|. + * + * If |dest| is NULL, and |destlen| is zero, this function just + * returns the number of bytes required to store the encoded transport + * parameters. + * + * This function returns the number of bytes written, or one of the + * following negative error codes: + * + * :macro:`NGTCP2_ERR_NOBUF` + * Buffer is too small. + */ +NGTCP2_EXTERN ngtcp2_ssize ngtcp2_transport_params_encode_versioned( + uint8_t *dest, size_t destlen, int transport_params_version, + const ngtcp2_transport_params *params); + +/** + * @function + * + * `ngtcp2_transport_params_decode` decodes transport parameters in + * |data| of length |datalen|, and stores the result in the object + * pointed by |params|. + * + * If an optional parameter is missing, the default value is assigned. + * + * The following fields may point to somewhere inside the buffer + * pointed by |data| of length |datalen|: + * + * - :member:`ngtcp2_transport_params.version_info.available_versions + * <ngtcp2_version_info.available_versions>` + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM` + * The input is malformed. + */ +NGTCP2_EXTERN int +ngtcp2_transport_params_decode_versioned(int transport_params_version, + ngtcp2_transport_params *params, + const uint8_t *data, size_t datalen); + +/** + * @function + * + * `ngtcp2_transport_params_decode_new` decodes transport parameters + * in |data| of length |datalen|, and stores the result in the object + * allocated dynamically. The pointer to the allocated object is + * assigned to |*pparams|. Unlike `ngtcp2_transport_params_decode`, + * all direct and indirect fields are also allocated dynamically if + * needed. + * + * |mem| is a memory allocator to allocate memory. If |mem| is + * ``NULL``, the memory allocator returned by `ngtcp2_mem_default()` + * is used. + * + * If the optional parameters are missing, the default value is + * assigned. + * + * `ngtcp2_transport_params_del` frees the memory allocated by this + * function. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM` + * The input is malformed. + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory. + */ +NGTCP2_EXTERN int +ngtcp2_transport_params_decode_new(ngtcp2_transport_params **pparams, + const uint8_t *data, size_t datalen, + const ngtcp2_mem *mem); + +/** + * @function + * + * `ngtcp2_transport_params_del` frees the |params| which must be + * dynamically allocated by `ngtcp2_transport_params_decode_new`. + * + * |mem| is a memory allocator that allocated |params|. If |mem| is + * ``NULL``, the memory allocator returned by `ngtcp2_mem_default()` + * is used. + * + * If |params| is ``NULL``, this function does nothing. + */ +NGTCP2_EXTERN void ngtcp2_transport_params_del(ngtcp2_transport_params *params, + const ngtcp2_mem *mem); + +/** + * @struct + * + * :type:`ngtcp2_version_cid` is a convenient struct to store the + * result of `ngtcp2_pkt_decode_version_cid`. + */ +typedef struct ngtcp2_version_cid { + /** + * :member:`version` stores QUIC version. + */ + uint32_t version; + /** + * :member:`dcid` points to the Destination Connection ID. + */ + const uint8_t *dcid; + /** + * :member:`dcidlen` is the length of the Destination Connection ID + * pointed by :member:`dcid`. + */ + size_t dcidlen; + /** + * :member:`scid` points to the Source Connection ID. + */ + const uint8_t *scid; + /** + * :member:`scidlen` is the length of the Source Connection ID + * pointed by :member:`scid`. + */ + size_t scidlen; +} ngtcp2_version_cid; + +/** + * @function + * + * `ngtcp2_pkt_decode_version_cid` extracts QUIC version, Destination + * Connection ID and Source Connection ID from the packet pointed by + * |data| of length |datalen|. This function can handle Connection ID + * up to 255 bytes unlike `ngtcp2_pkt_decode_hd_long` or + * `ngtcp2_pkt_decode_hd_short` which are only capable of handling + * Connection ID less than or equal to :macro:`NGTCP2_MAX_CIDLEN`. + * Longer Connection ID is only valid if the version is unsupported + * QUIC version. + * + * If the given packet is Long header packet, this function extracts + * the version from the packet, and assigns it to + * :member:`dest->version <ngtcp2_version_cid.version>`. It also + * extracts the pointer to the Destination Connection ID and its + * length, and assigns them to :member:`dest->dcid + * <ngtcp2_version_cid.dcid>` and :member:`dest->dcidlen + * <ngtcp2_version_cid.dcidlen>` respectively. Similarly, it extracts + * the pointer to the Source Connection ID and its length, and assigns + * them to :member:`dest->scid <ngtcp2_version_cid.scid>` and + * :member:`dest->scidlen <ngtcp2_version_cid.scidlen>` respectively. + * |short_dcidlen| is ignored. + * + * If the given packet is Short header packet, :member:`dest->version + * <ngtcp2_version_cid.version>` will be 0, :member:`dest->scid + * <ngtcp2_version_cid.scid>` will be ``NULL``, and + * :member:`dest->scidlen <ngtcp2_version_cid.scidlen>` will be 0. + * Because the Short header packet does not have the length of + * Destination Connection ID, the caller has to pass the length in + * |short_dcidlen|. This function extracts the pointer to the + * Destination Connection ID, and assigns it to :member:`dest->dcid + * <ngtcp2_version_cid.dcid>`. |short_dcidlen| is assigned to + * :member:`dest->dcidlen <ngtcp2_version_cid.dcidlen>`. + * + * If Version Negotiation is required, this function returns + * :macro:`NGTCP2_ERR_VERSION_NEGOTIATION`. Unlike the other error + * cases, all fields of |dest| are assigned as described above. + * + * This function returns 0 if it succeeds. Otherwise, one of the + * following negative error code: + * + * :macro:`NGTCP2_ERR_INVALID_ARGUMENT` + * The function could not decode the packet header. + * :macro:`NGTCP2_ERR_VERSION_NEGOTIATION` + * Version Negotiation packet should be sent. + */ +NGTCP2_EXTERN int ngtcp2_pkt_decode_version_cid(ngtcp2_version_cid *dest, + const uint8_t *data, + size_t datalen, + size_t short_dcidlen); + +/** + * @function + * + * `ngtcp2_pkt_decode_hd_long` decodes QUIC long packet header in + * |pkt| of length |pktlen|. This function only parses the input just + * before packet number field. + * + * This function does not verify that length field is correct. In + * other words, this function succeeds even if length > |pktlen|. + * + * This function can handle Connection ID up to + * :macro:`NGTCP2_MAX_CIDLEN`. Consider to use + * `ngtcp2_pkt_decode_version_cid` to get longer Connection ID. + * + * This function handles Version Negotiation specially. If version + * field is 0, |pkt| must contain Version Negotiation packet. Version + * Negotiation packet has random type in wire format. For + * convenience, this function sets + * :enum:`ngtcp2_pkt_type.NGTCP2_PKT_VERSION_NEGOTIATION` to + * :member:`dest->type <ngtcp2_pkt_hd.type>`, clears + * :macro:`NGTCP2_PKT_FLAG_LONG_FORM` flag from :member:`dest->flags + * <ngtcp2_pkt_hd.flags>`, and sets 0 to :member:`dest->len + * <ngtcp2_pkt_hd.len>`. Version Negotiation packet occupies a single + * packet. + * + * It stores the result in the object pointed by |dest|, and returns + * the number of bytes decoded to read the packet header if it + * succeeds, or one of the following error codes: + * + * :macro:`NGTCP2_ERR_INVALID_ARGUMENT` + * Packet is too short; or it is not a long header + */ +NGTCP2_EXTERN ngtcp2_ssize ngtcp2_pkt_decode_hd_long(ngtcp2_pkt_hd *dest, + const uint8_t *pkt, + size_t pktlen); + +/** + * @function + * + * `ngtcp2_pkt_decode_hd_short` decodes QUIC short header in |pkt| of + * length |pktlen|. Short header packet does not encode the length of + * Connection ID, thus we need the input from the outside. |dcidlen| + * is the length of Destination Connection ID in packet header. This + * function only parses the input just before packet number field. + * This function can handle Connection ID up to + * :macro:`NGTCP2_MAX_CIDLEN`. Consider to use + * `ngtcp2_pkt_decode_version_cid` to get longer Connection ID. It + * stores the result in the object pointed by |dest|, and returns the + * number of bytes decoded to read the packet header if it succeeds, + * or one of the following error codes: + * + * :macro:`NGTCP2_ERR_INVALID_ARGUMENT` + * Packet is too short; or it is not a short header + */ +NGTCP2_EXTERN ngtcp2_ssize ngtcp2_pkt_decode_hd_short(ngtcp2_pkt_hd *dest, + const uint8_t *pkt, + size_t pktlen, + size_t dcidlen); + +/** + * @function + * + * `ngtcp2_pkt_write_stateless_reset` writes Stateless Reset packet in + * the buffer pointed by |dest| whose length is |destlen|. + * |stateless_reset_token| is a pointer to the Stateless Reset Token, + * and its length must be :macro:`NGTCP2_STATELESS_RESET_TOKENLEN` + * bytes long. |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`. + */ +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_version_negotiation` writes Version Negotiation + * packet in the buffer pointed by |dest| whose length is |destlen|. + * |unused_random| should be generated randomly. |dcid| is a + * Connection ID which appeared in a packet as a Source Connection ID + * sent by client which caused version negotiation. Similarly, |scid| + * is a Connection ID which appeared in a packet as a Destination + * Connection ID sent by client. |sv| is a list of supported + * versions, and |nsv| specifies the number of supported versions + * included in |sv|. + * + * 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. + */ +NGTCP2_EXTERN ngtcp2_ssize ngtcp2_pkt_write_version_negotiation( + uint8_t *dest, size_t destlen, uint8_t unused_random, const uint8_t *dcid, + size_t dcidlen, const uint8_t *scid, size_t scidlen, const uint32_t *sv, + size_t nsv); + +/** + * @struct + * + * :type:`ngtcp2_conn` represents a single QUIC connection. + */ +typedef struct ngtcp2_conn ngtcp2_conn; + +/** + * @functypedef + * + * :type:`ngtcp2_client_initial` is invoked when client application + * asks TLS stack to produce first TLS cryptographic handshake data. + * + * This implementation of this callback must get the first handshake + * data from TLS stack, and pass it to ngtcp2 library using + * `ngtcp2_conn_submit_crypto_data` function. Make sure that before + * calling `ngtcp2_conn_submit_crypto_data` function, client + * application must create initial packet protection keys and IVs, and + * provide them to ngtcp2 library using + * `ngtcp2_conn_install_initial_key`. + * + * This callback function must return 0 if it succeeds, or + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` which makes the library call + * return immediately. + */ +typedef int (*ngtcp2_client_initial)(ngtcp2_conn *conn, void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_recv_client_initial` is invoked when server receives + * Initial packet from client. An server application must implement + * this callback, and generate initial keys and IVs for both + * transmission and reception. Install them using + * `ngtcp2_conn_install_initial_key`. |dcid| is the Destination + * Connection ID in Initial packet received from client. It is used + * to derive initial packet protection keys. + * + * The callback function must return 0 if it succeeds. If an error + * occurs, return :macro:`NGTCP2_ERR_CALLBACK_FAILURE` which makes the + * library call return immediately. + */ +typedef int (*ngtcp2_recv_client_initial)(ngtcp2_conn *conn, + const ngtcp2_cid *dcid, + void *user_data); + +/** + * @enum + * + * :type:`ngtcp2_encryption_level` is QUIC encryption level. + */ +typedef enum ngtcp2_encryption_level { + /** + * :enum:`NGTCP2_ENCRYPTION_LEVEL_INITIAL` is Initial encryption + * level. + */ + NGTCP2_ENCRYPTION_LEVEL_INITIAL, + /** + * :enum:`NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE` is Handshake encryption + * level. + */ + NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE, + /** + * :enum:`NGTCP2_ENCRYPTION_LEVEL_1RTT` is 1-RTT encryption level. + */ + NGTCP2_ENCRYPTION_LEVEL_1RTT, + /** + * :enum:`NGTCP2_ENCRYPTION_LEVEL_0RTT` is 0-RTT encryption level. + */ + NGTCP2_ENCRYPTION_LEVEL_0RTT +} ngtcp2_encryption_level; + +/** + * @functypedef + * + * :type`ngtcp2_recv_crypto_data` is invoked when crypto data is + * received. The received data is pointed by |data|, and its length + * is |datalen|. The |offset| specifies the offset where |data| is + * positioned. |user_data| is the arbitrary pointer passed to + * `ngtcp2_conn_client_new` or `ngtcp2_conn_server_new`. The ngtcp2 + * library ensures that the crypto data is passed to the application + * in the increasing order of |offset|. |datalen| is always strictly + * greater than 0. |encryption_level| indicates the encryption level + * where this data is received. Crypto data can never be received in + * :enum:`ngtcp2_encryption_level.NGTCP2_ENCRYPTION_LEVEL_0RTT`. + * + * The application should provide the given data to TLS stack. + * + * The callback function must return 0 if it succeeds, or one of the + * following negative error codes: + * + * - :macro:`NGTCP2_ERR_CRYPTO` + * - :macro:`NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM` + * - :macro:`NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM` + * - :macro:`NGTCP2_ERR_TRANSPORT_PARAM` + * - :macro:`NGTCP2_ERR_PROTO` + * - :macro:`NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE` + * - :macro:`NGTCP2_ERR_NOMEM` + * - :macro:`NGTCP2_ERR_CALLBACK_FAILURE` + * + * If the other value is returned, it is treated as + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE`. + * + * If application encounters fatal error, return + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` which makes the library call + * return immediately. + */ +typedef int (*ngtcp2_recv_crypto_data)(ngtcp2_conn *conn, + ngtcp2_encryption_level encryption_level, + uint64_t offset, const uint8_t *data, + size_t datalen, void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_handshake_completed` is invoked when QUIC + * cryptographic handshake has completed. + * + * The callback function must return 0 if it succeeds. Returning + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return + * immediately. + */ +typedef int (*ngtcp2_handshake_completed)(ngtcp2_conn *conn, void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_handshake_confirmed` is invoked when QUIC + * cryptographic handshake is confirmed. The handshake confirmation + * means that both endpoints agree that handshake has finished. + * + * The callback function must return 0 if it succeeds. Returning + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return + * immediately. + */ +typedef int (*ngtcp2_handshake_confirmed)(ngtcp2_conn *conn, void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_recv_version_negotiation` is invoked when Version + * Negotiation packet is received. |hd| is the pointer to the QUIC + * packet header object. The vector |sv| of |nsv| elements contains + * the QUIC version the server supports. Since Version Negotiation is + * only sent by server, this callback function is used by client only. + * + * The callback function must return 0 if it succeeds, or + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` which makes the library call + * return immediately. + */ +typedef int (*ngtcp2_recv_version_negotiation)(ngtcp2_conn *conn, + const ngtcp2_pkt_hd *hd, + const uint32_t *sv, size_t nsv, + void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_recv_retry` is invoked when Retry packet is received. + * This callback is client use only. + * + * Application must regenerate packet protection key, IV, and header + * protection key for Initial packets using the Destination Connection + * ID obtained by :member:`hd->scid <ngtcp2_pkt_hd.scid>`, and install + * them by calling `ngtcp2_conn_install_initial_key`. + * + * 0-RTT data accepted by the ngtcp2 library will be automatically + * retransmitted as 0-RTT data by the library. + * + * The callback function must return 0 if it succeeds. Returning + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return + * immediately. + */ +typedef int (*ngtcp2_recv_retry)(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd, + void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_encrypt` is invoked when the ngtcp2 library asks the + * application to encrypt packet payload. The packet payload to + * encrypt is passed as |plaintext| of length |plaintextlen|. The + * AEAD cipher is |aead|. |aead_ctx| is the AEAD cipher context + * object which is initialized with the specific encryption key. The + * nonce is passed as |nonce| of length |noncelen|. The Additional + * Authenticated Data is passed as |aad| of length |aadlen|. + * + * The implementation of this callback must encrypt |plaintext| using + * the negotiated cipher suite, and write the ciphertext into the + * buffer pointed by |dest|. |dest| has enough capacity to store the + * ciphertext and any additional AEAD tag data. + * + * |dest| and |plaintext| may point to the same buffer. + * + * The callback function must return 0 if it succeeds, or + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` which makes the library call + * return immediately. + */ +typedef int (*ngtcp2_encrypt)(uint8_t *dest, const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *plaintext, size_t plaintextlen, + const uint8_t *nonce, size_t noncelen, + const uint8_t *aad, size_t aadlen); + +/** + * @functypedef + * + * :type:`ngtcp2_decrypt` is invoked when the ngtcp2 library asks the + * application to decrypt packet payload. The packet payload to + * decrypt is passed as |ciphertext| of length |ciphertextlen|. The + * AEAD cipher is |aead|. |aead_ctx| is the AEAD cipher context + * object which is initialized with the specific decryption key. The + * nonce is passed as |nonce| of length |noncelen|. The Additional + * Authenticated Data is passed as |aad| of length |aadlen|. + * + * The implementation of this callback must decrypt |ciphertext| using + * the negotiated cipher suite, and write the ciphertext into the + * buffer pointed by |dest|. |dest| has enough capacity to store the + * cleartext. + * + * |dest| and |ciphertext| may point to the same buffer. + * + * The callback function must return 0 if it succeeds. If TLS stack + * fails to decrypt data, return :macro:`NGTCP2_ERR_DECRYPT`. For any + * other errors, return :macro:`NGTCP2_ERR_CALLBACK_FAILURE` which + * makes the library call return immediately. + */ +typedef int (*ngtcp2_decrypt)(uint8_t *dest, const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *ciphertext, size_t ciphertextlen, + const uint8_t *nonce, size_t noncelen, + const uint8_t *aad, size_t aadlen); + +/** + * @functypedef + * + * :type:`ngtcp2_hp_mask` is invoked when the ngtcp2 library asks the + * application to produce a mask to encrypt or decrypt packet header. + * The encryption cipher is |hp|. |hp_ctx| is the cipher context + * object which is initialized with the specific header protection + * key. The sample is passed as |sample| which is + * :macro:`NGTCP2_HP_SAMPLELEN` bytes long. + * + * The implementation of this callback must produce a mask using the + * header protection cipher suite specified by QUIC specification, and + * write the result into the buffer pointed by |dest|. The length of + * the mask must be at least :macro:`NGTCP2_HP_MASKLEN`. The library + * only uses the first :macro:`NGTCP2_HP_MASKLEN` bytes of the + * produced mask. The buffer pointed by |dest| is guaranteed to have + * at least :macro:`NGTCP2_HP_SAMPLELEN` bytes available for + * convenience. + * + * The callback function must return 0 if it succeeds, or + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` which makes the library call + * return immediately. + */ +typedef int (*ngtcp2_hp_mask)(uint8_t *dest, const ngtcp2_crypto_cipher *hp, + const ngtcp2_crypto_cipher_ctx *hp_ctx, + const uint8_t *sample); + +/** + * @macrosection + * + * STREAM frame data flags + */ + +/** + * @macro + * + * :macro:`NGTCP2_STREAM_DATA_FLAG_NONE` indicates no flag set. + */ +#define NGTCP2_STREAM_DATA_FLAG_NONE 0x00u + +/** + * @macro + * + * :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 + +/** + * @macro + * + * :macro:`NGTCP2_STREAM_DATA_FLAG_0RTT` indicates that this chunk of + * 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 + +/** + * @functypedef + * + * :type:`ngtcp2_recv_stream_data` is invoked when stream data is + * received. The stream is specified by |stream_id|. |flags| is the + * bitwise-OR of zero or more of :macro:`NGTCP2_STREAM_DATA_FLAG_* + * <NGTCP2_STREAM_DATA_FLAG_NONE>`. If |flags| & + * :macro:`NGTCP2_STREAM_DATA_FLAG_FIN` is nonzero, this portion of + * the data is the last data in this stream. |offset| is the offset + * where this data begins. The library ensures that data is passed to + * the application in the non-decreasing order of |offset| without any + * overlap. The data is passed as |data| of length |datalen|. + * |datalen| may be 0 if and only if |fin| is nonzero. + * + * If :macro:`NGTCP2_STREAM_DATA_FLAG_0RTT` is set in |flags|, it + * indicates that a part of or whole data was received in 0-RTT + * packet, and a handshake has not completed yet. + * + * The callback function must return 0 if it succeeds, or + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` which makes the library return + * immediately. + */ +typedef int (*ngtcp2_recv_stream_data)(ngtcp2_conn *conn, uint32_t flags, + int64_t stream_id, uint64_t offset, + const uint8_t *data, size_t datalen, + void *user_data, void *stream_user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_stream_open` is a callback function which is called + * when remote stream is opened by a remote endpoint. This function + * is not called if stream is opened by implicitly (we might + * reconsider this behaviour later). + * + * The implementation of this callback should return 0 if it succeeds. + * Returning :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library + * call return immediately. + */ +typedef int (*ngtcp2_stream_open)(ngtcp2_conn *conn, int64_t stream_id, + void *user_data); + +/** + * @macrosection + * + * Stream close flags + */ + +/** + * @macro + * + * :macro:`NGTCP2_STREAM_CLOSE_FLAG_NONE` indicates no flag set. + */ +#define NGTCP2_STREAM_CLOSE_FLAG_NONE 0x00u + +/** + * @macro + * + * :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 + +/** + * @functypedef + * + * :type:`ngtcp2_stream_close` is invoked when a stream is closed. + * This callback is not called when QUIC connection is closed before + * existing streams are closed. |flags| is the bitwise-OR of zero or + * more of :macro:`NGTCP2_STREAM_CLOSE_FLAG_* + * <NGTCP2_STREAM_CLOSE_FLAG_NONE>`. |app_error_code| indicates the + * error code of this closure if + * :macro:`NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET` is set in + * |flags|. If it is not set, the stream was closed without any error + * code, which generally means success. + * + * |app_error_code| is the first application error code sent by a + * local endpoint, or received from a remote endpoint. If a stream is + * closed cleanly, no application error code is exchanged. Since QUIC + * stack does not know the application error code which indicates "no + * errors", |app_error_code| is set to 0 and + * :macro:`NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET` is not set in + * |flags| in this case. + * + * The implementation of this callback should return 0 if it succeeds. + * Returning :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library + * call return immediately. + */ +typedef int (*ngtcp2_stream_close)(ngtcp2_conn *conn, uint32_t flags, + int64_t stream_id, uint64_t app_error_code, + void *user_data, void *stream_user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_stream_reset` is invoked when a stream identified by + * |stream_id| is reset by a remote endpoint. + * + * The implementation of this callback should return 0 if it succeeds. + * Returning :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library + * call return immediately. + */ +typedef int (*ngtcp2_stream_reset)(ngtcp2_conn *conn, int64_t stream_id, + uint64_t final_size, uint64_t app_error_code, + void *user_data, void *stream_user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_acked_stream_data_offset` is a callback function + * which is called when stream data in range [|offset|, |offset| + + * |datalen|) is acknowledged, and application can free the portion of + * data. For a given |stream_id|, this callback is called + * sequentially in increasing order of |offset| without any overlap. + * |datalen| is normally strictly greater than 0. One exception is + * that when a STREAM frame has fin flag set and 0 length data, this + * callback is invoked with |datalen| == 0. + * + * If a stream is closed prematurely, and stream data is still + * in-flight, this callback function is not called for those data. + * After :member:`ngtcp2_callbacks.stream_close` is called for a + * particular stream, |conn| does not touch data for the closed stream + * again, and application can free all unacknowledged stream data. + * + * The implementation of this callback should return 0 if it succeeds. + * Returning :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library + * call return immediately. + */ +typedef int (*ngtcp2_acked_stream_data_offset)( + ngtcp2_conn *conn, int64_t stream_id, uint64_t offset, uint64_t datalen, + void *user_data, void *stream_user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_recv_stateless_reset` 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. + */ +typedef int (*ngtcp2_recv_stateless_reset)(ngtcp2_conn *conn, + const ngtcp2_pkt_stateless_reset *sr, + void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_extend_max_streams` is a callback function which is + * called every time max stream ID is strictly extended. + * |max_streams| is the cumulative number of streams which an endpoint + * can open. + * + * The callback function must return 0 if it succeeds. Returning + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return + * immediately. + */ +typedef int (*ngtcp2_extend_max_streams)(ngtcp2_conn *conn, + uint64_t max_streams, void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_extend_max_stream_data` is a callback function which + * is invoked when max stream data is extended. |stream_id| + * identifies the stream. |max_data| is a cumulative number of bytes + * an endpoint can send on this stream. + * + * The callback function must return 0 if it succeeds. Returning + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return + * immediately. + */ +typedef int (*ngtcp2_extend_max_stream_data)(ngtcp2_conn *conn, + int64_t stream_id, + uint64_t max_data, void *user_data, + void *stream_user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_rand` is a callback function to get random data of + * length |destlen|. Application must fill random |destlen| bytes to + * the buffer pointed by |dest|. The generated data is used only in + * non-cryptographic context. + */ +typedef void (*ngtcp2_rand)(uint8_t *dest, size_t destlen, + const ngtcp2_rand_ctx *rand_ctx); + +/** + * @functypedef + * + * :type:`ngtcp2_get_new_connection_id` 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 length of stateless reset token is + * :macro:`NGTCP2_STATELESS_RESET_TOKENLEN` and it is guaranteed that + * the buffer pointed by |token| has the sufficient space to store the + * token. + * + * The callback function must return 0 if it succeeds. Returning + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return + * immediately. + */ +typedef int (*ngtcp2_get_new_connection_id)(ngtcp2_conn *conn, ngtcp2_cid *cid, + uint8_t *token, size_t cidlen, + void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_remove_connection_id` is a callback function which + * notifies the application that connection ID |cid| is no longer used + * by a remote endpoint. This Connection ID was previously offered by + * a local endpoint, and a remote endpoint could use it as Destination + * Connection ID when sending QUIC packet. + * + * The callback function must return 0 if it succeeds. Returning + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return + * immediately. + */ +typedef int (*ngtcp2_remove_connection_id)(ngtcp2_conn *conn, + const ngtcp2_cid *cid, + void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_update_key` is a callback function which tells the + * application that it must generate new packet protection keying + * materials and AEAD cipher context objects with new keys. The + * current set of secrets are given as |current_rx_secret| and + * |current_tx_secret| of length |secretlen|. They are decryption and + * encryption secrets respectively. + * + * The application must generate new secrets and keys for both + * encryption and decryption. It must write decryption secret and IV + * to the buffer pointed by |rx_secret| and |rx_iv| respectively. It + * also must create new AEAD cipher context object with new decryption + * key and initialize |rx_aead_ctx| with it. Similarly, write + * encryption secret and IV to the buffer pointed by |tx_secret| and + * |tx_iv|. Create new AEAD cipher context object with new encryption + * key and initialize |tx_aead_ctx| with it. All given buffers have + * the enough capacity to store secret, key and IV. + * + * The callback function must return 0 if it succeeds. Returning + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return + * immediately. + */ +typedef int (*ngtcp2_update_key)( + ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret, + ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv, + ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv, + const uint8_t *current_rx_secret, const uint8_t *current_tx_secret, + size_t secretlen, void *user_data); + +/** + * @macrosection + * + * Path validation related macros + */ + +/** + * @macro + * + * :macro:`NGTCP2_PATH_VALIDATION_FLAG_NONE` indicates no flag set. + */ +#define NGTCP2_PATH_VALIDATION_FLAG_NONE 0x00u + +/** + * @macro + * + * :macro:`NGTCP2_PATH_VALIDATION_FLAG_PREFERRED_ADDR` indicates the + * validation involving server preferred address. This flag is only + * set for client. + */ +#define NGTCP2_PATH_VALIDATION_FLAG_PREFERRED_ADDR 0x01u + +/** + * @macro + * + * :macro:`NGTCP2_PATH_VALIDATION_FLAG_NEW_TOKEN` indicates that + * 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 + +/** + * @functypedef + * + * :type:`ngtcp2_path_validation` is a callback function which tells + * an application the outcome of path validation. |flags| is zero or + * more of :macro:`NGTCP2_PATH_VALIDATION_FLAG_* + * <NGTCP2_PATH_VALIDATION_FLAG_NONE>`. |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 + * :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`, + * the path validation failed. + * + * The callback function must return 0 if it succeeds. Returning + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return + * immediately. + */ +typedef int (*ngtcp2_path_validation)(ngtcp2_conn *conn, uint32_t flags, + const ngtcp2_path *path, + const ngtcp2_path *old_path, + ngtcp2_path_validation_result res, + void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_select_preferred_addr` is a callback function which + * asks a client application to choose server address from preferred + * addresses |paddr| received from server. An application should + * write a network path for a selected preferred address in |dest|. + * More specifically, the selected preferred address must be set to + * :member:`dest->remote <ngtcp2_path.remote>`, a client source + * address must be set to :member:`dest->local <ngtcp2_path.local>`. + * If a client source address does not change for the new server + * address, leave :member:`dest->local <ngtcp2_path.local>` + * unmodified, or copy the value of :member:`local + * <ngtcp2_path.local>` field of the current network path obtained + * from `ngtcp2_conn_get_path()`. Both :member:`dest->local.addr + * <ngtcp2_addr.addr>` and :member:`dest->remote.addr + * <ngtcp2_addr.addr>` point to buffers which are at least + * sizeof(:type:`ngtcp2_sockaddr_union`) bytes long, respectively. If + * an application denies the preferred addresses, just leave |dest| + * unmodified (or set :member:`dest->remote.addrlen + * <ngtcp2_addr.addrlen>` to 0), and return 0. + * + * The callback function must return 0 if it succeeds. Returning + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return + * immediately. + */ +typedef int (*ngtcp2_select_preferred_addr)(ngtcp2_conn *conn, + ngtcp2_path *dest, + const ngtcp2_preferred_addr *paddr, + void *user_data); + +/** + * @enum + * + * :type:`ngtcp2_connection_id_status_type` defines a set of status + * for Destination Connection ID. + */ +typedef enum ngtcp2_connection_id_status_type { + /** + * :enum:`NGTCP2_CONNECTION_ID_STATUS_TYPE_ACTIVATE` indicates that + * a local endpoint starts using new Destination Connection ID. + */ + NGTCP2_CONNECTION_ID_STATUS_TYPE_ACTIVATE, + /** + * :enum:`NGTCP2_CONNECTION_ID_STATUS_TYPE_DEACTIVATE` indicates + * that a local endpoint stops using a given Destination Connection + * ID. + */ + NGTCP2_CONNECTION_ID_STATUS_TYPE_DEACTIVATE +} ngtcp2_connection_id_status_type; + +/** + * @functypedef + * + * :type:`ngtcp2_connection_id_status` 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. + */ +typedef int (*ngtcp2_connection_id_status)( + ngtcp2_conn *conn, ngtcp2_connection_id_status_type type, uint64_t seq, + const ngtcp2_cid *cid, const uint8_t *token, void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_recv_new_token` is a callback function which is + * called when new token is received from server. This callback is + * client use only. + * + * |token| is the received token of length |tokenlen| bytes long. + * + * The callback function must return 0 if it succeeds. Returning + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return + * immediately. + */ +typedef int (*ngtcp2_recv_new_token)(ngtcp2_conn *conn, const uint8_t *token, + size_t tokenlen, void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_delete_crypto_aead_ctx` is a callback function which + * must delete the native object pointed by + * :member:`aead_ctx->native_handle + * <ngtcp2_crypto_aead_ctx.native_handle>`. + */ +typedef void (*ngtcp2_delete_crypto_aead_ctx)(ngtcp2_conn *conn, + ngtcp2_crypto_aead_ctx *aead_ctx, + void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_delete_crypto_cipher_ctx` is a callback function + * which must delete the native object pointed by + * :member:`cipher_ctx->native_handle + * <ngtcp2_crypto_cipher_ctx.native_handle>`. + */ +typedef void (*ngtcp2_delete_crypto_cipher_ctx)( + ngtcp2_conn *conn, ngtcp2_crypto_cipher_ctx *cipher_ctx, void *user_data); + +/** + * @macrosection + * + * DATAGRAM frame flags + */ + +/** + * @macro + * + * :macro:`NGTCP2_DATAGRAM_FLAG_NONE` indicates no flag set. + */ +#define NGTCP2_DATAGRAM_FLAG_NONE 0x00u + +/** + * @macro + * + * :macro:`NGTCP2_DATAGRAM_FLAG_0RTT` indicates that DATAGRAM frame is + * 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 + +/** + * @functypedef + * + * :type:`ngtcp2_recv_datagram` is invoked when DATAGRAM frame is + * received. |flags| is bitwise-OR of zero or more of + * :macro:`NGTCP2_DATAGRAM_FLAG_* <NGTCP2_DATAGRAM_FLAG_NONE>`. + * + * If :macro:`NGTCP2_DATAGRAM_FLAG_0RTT` is set in |flags|, it + * indicates that DATAGRAM frame was received in 0-RTT packet, and a + * handshake has not completed yet. + * + * The callback function must return 0 if it succeeds, or + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` which makes the library return + * immediately. + */ +typedef int (*ngtcp2_recv_datagram)(ngtcp2_conn *conn, uint32_t flags, + const uint8_t *data, size_t datalen, + void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_ack_datagram` is invoked when a packet which contains + * DATAGRAM frame which is identified by |dgram_id| is acknowledged. + * |dgram_id| is the valued passed to `ngtcp2_conn_writev_datagram`. + * + * The callback function must return 0 if it succeeds, or + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` which makes the library return + * immediately. + */ +typedef int (*ngtcp2_ack_datagram)(ngtcp2_conn *conn, uint64_t dgram_id, + void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_lost_datagram` is invoked when a packet which + * contains DATAGRAM frame which is identified by |dgram_id| is + * declared lost. |dgram_id| is the valued passed to + * `ngtcp2_conn_writev_datagram`. Note that the loss might be + * spurious, and DATAGRAM frame might be acknowledged later. + * + * The callback function must return 0 if it succeeds, or + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` which makes the library return + * immediately. + */ +typedef int (*ngtcp2_lost_datagram)(ngtcp2_conn *conn, uint64_t dgram_id, + void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_get_path_challenge_data` 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 the buffer pointed by |data|. + * + * The callback function must return 0 if it succeeds. Returning + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return + * immediately. + */ +typedef int (*ngtcp2_get_path_challenge_data)(ngtcp2_conn *conn, uint8_t *data, + void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_stream_stop_sending` is invoked when a stream is no + * longer read by a local endpoint before it receives all stream data. + * This function is called at most once per stream. |app_error_code| + * is the error code passed to `ngtcp2_conn_shutdown_stream_read` or + * `ngtcp2_conn_shutdown_stream`. + * + * The callback function must return 0 if it succeeds. Returning + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return + * immediately. + */ +typedef int (*ngtcp2_stream_stop_sending)(ngtcp2_conn *conn, int64_t stream_id, + uint64_t app_error_code, + void *user_data, + void *stream_user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_version_negotiation` is invoked when the compatible + * version negotiation takes place. For client, it is called when it + * sees a change in version field of a long header packet. This + * callback function might be called multiple times for client. For + * server, it is called once when the version is negotiated. + * + * The implementation of this callback must install new Initial keys + * for |version| and Destination Connection ID |client_dcid| from + * client. Use `ngtcp2_conn_install_vneg_initial_key` to install + * keys. + * + * The callback function must return 0 if it succeeds. Returning + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return + * immediately. + */ +typedef int (*ngtcp2_version_negotiation)(ngtcp2_conn *conn, uint32_t version, + const ngtcp2_cid *client_dcid, + void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_recv_key` is invoked when new key is installed to + * |conn| during QUIC cryptographic handshake. + * + * The callback function must return 0 if it succeeds. Returning + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return + * immediately. + */ +typedef int (*ngtcp2_recv_key)(ngtcp2_conn *conn, ngtcp2_encryption_level level, + void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_tls_early_data_rejected` is invoked when early data + * was rejected by server during TLS handshake, or client decided not + * to attempt early data. + * + * The callback function must return 0 if it succeeds. Returning + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return + * immediately. + */ +typedef int (*ngtcp2_tls_early_data_rejected)(ngtcp2_conn *conn, + void *user_data); + +#define NGTCP2_CALLBACKS_V1 1 +#define NGTCP2_CALLBACKS_VERSION NGTCP2_CALLBACKS_V1 + +/** + * @struct + * + * :type:`ngtcp2_callbacks` holds a set of callback functions. + */ +typedef struct ngtcp2_callbacks { + /** + * :member:`client_initial` is a callback function which is invoked + * when client asks TLS stack to produce first TLS cryptographic + * handshake message. This callback function must be specified for + * a client application. + */ + ngtcp2_client_initial client_initial; + /** + * :member:`recv_client_initial` is a callback function which is + * invoked when a server receives the first Initial packet from + * client. This callback function must be specified for a server + * application. + */ + ngtcp2_recv_client_initial recv_client_initial; + /** + * :member:`recv_crypto_data` is a callback function which is + * invoked when cryptographic data (CRYPTO frame, in other words, + * TLS message) is received. This callback function must be + * specified. + */ + ngtcp2_recv_crypto_data recv_crypto_data; + /** + * :member:`handshake_completed` is a callback function which is + * invoked when QUIC cryptographic handshake has completed. This + * callback function is optional. + */ + ngtcp2_handshake_completed handshake_completed; + /** + * :member:`recv_version_negotiation` is a callback function which + * is invoked when Version Negotiation packet is received by a + * client. This callback function is optional. + */ + ngtcp2_recv_version_negotiation recv_version_negotiation; + /** + * :member:`encrypt` is a callback function which is invoked to + * encrypt a QUIC packet. This callback function must be specified. + */ + ngtcp2_encrypt encrypt; + /** + * :member:`decrypt` is a callback function which is invoked to + * decrypt a QUIC packet. This callback function must be specified. + */ + ngtcp2_decrypt decrypt; + /** + * :member:`hp_mask` is a callback function which is invoked to get + * a mask to encrypt or decrypt QUIC packet header. This callback + * function must be specified. + */ + ngtcp2_hp_mask hp_mask; + /** + * :member:`recv_stream_data` is a callback function which is + * invoked when stream data, which includes application data, is + * received. This callback function is optional. + */ + ngtcp2_recv_stream_data recv_stream_data; + /** + * :member:`acked_stream_data_offset` is a callback function which + * is invoked when stream data, which includes application data, is + * acknowledged by a remote endpoint. It tells an application the + * largest offset of acknowledged stream data without a gap so that + * application can free memory for the data up to that offset. This + * callback function is optional. + */ + ngtcp2_acked_stream_data_offset acked_stream_data_offset; + /** + * :member:`stream_open` is a callback function which is invoked + * when new remote stream is opened by a remote endpoint. This + * callback function is optional. + */ + ngtcp2_stream_open stream_open; + /** + * :member:`stream_close` is a callback function which is invoked + * when a stream is closed. This callback function is optional. + */ + ngtcp2_stream_close stream_close; + /** + * :member:`recv_stateless_reset` is a callback function which is + * invoked when Stateless Reset packet is received. This callback + * function is optional. + */ + ngtcp2_recv_stateless_reset recv_stateless_reset; + /** + * :member:`recv_retry` is a callback function which is invoked when + * a client receives Retry packet. For client, this callback + * function must be specified. Server never receive Retry packet. + */ + ngtcp2_recv_retry recv_retry; + /** + * :member:`extend_max_local_streams_bidi` is a callback function + * which is invoked when the number of bidirectional stream which a + * local endpoint can open is increased. This callback function is + * optional. + */ + ngtcp2_extend_max_streams extend_max_local_streams_bidi; + /** + * :member:`extend_max_local_streams_uni` is a callback function + * which is invoked when the number of unidirectional stream which a + * local endpoint can open is increased. This callback function is + * optional. + */ + ngtcp2_extend_max_streams extend_max_local_streams_uni; + /** + * :member:`rand` is a callback function which is invoked when the + * library needs random data. This callback function must be + * specified. + */ + 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. + */ + ngtcp2_get_new_connection_id get_new_connection_id; + /** + * :member:`remove_connection_id` is a callback function which + * notifies an application that connection ID is no longer used by a + * remote endpoint. This callback function is optional. + */ + ngtcp2_remove_connection_id remove_connection_id; + /** + * :member:`update_key` is a callback function which is invoked when + * the library tells an application that it must update keying + * materials, and install new keys. This callback function must be + * specified. + */ + ngtcp2_update_key update_key; + /** + * :member:`path_validation` is a callback function which is invoked + * when path validation completed. This callback function is + * optional. + */ + ngtcp2_path_validation path_validation; + /** + * :member:`select_preferred_addr` is a callback function which is + * invoked when the library asks a client to select preferred + * address presented by a server. If not set, client ignores + * preferred addresses. This callback function is optional. + */ + ngtcp2_select_preferred_addr select_preferred_addr; + /** + * :member:`stream_reset` is a callback function which is invoked + * when a stream is reset by a remote endpoint. This callback + * function is optional. + */ + ngtcp2_stream_reset stream_reset; + /** + * :member:`extend_max_remote_streams_bidi` is a callback function + * which is invoked when the number of bidirectional streams which a + * remote endpoint can open is increased. This callback function is + * optional. + */ + ngtcp2_extend_max_streams extend_max_remote_streams_bidi; + /** + * :member:`extend_max_remote_streams_uni` is a callback function + * which is invoked when the number of unidirectional streams which + * a remote endpoint can open is increased. This callback function + * is optional. + */ + ngtcp2_extend_max_streams extend_max_remote_streams_uni; + /** + * :member:`extend_max_stream_data` is callback function which is + * invoked when the maximum offset of stream data that a local + * endpoint can send is increased. This callback function is + * optional. + */ + ngtcp2_extend_max_stream_data extend_max_stream_data; + /** + * :member:`dcid_status` 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. + */ + ngtcp2_connection_id_status dcid_status; + /** + * :member:`handshake_confirmed` is a callback function which is + * invoked when both endpoints agree that handshake has finished. + * This field is ignored by server because + * :member:`handshake_completed` also indicates the handshake + * confirmation for server. This callback function is optional. + */ + ngtcp2_handshake_confirmed handshake_confirmed; + /** + * :member:`recv_new_token` is a callback function which is invoked + * when new token is received from server. This field is ignored by + * server. This callback function is optional. + */ + ngtcp2_recv_new_token recv_new_token; + /** + * :member:`delete_crypto_aead_ctx` is a callback function which + * deletes a given AEAD cipher context object. This callback + * function must be specified. + */ + ngtcp2_delete_crypto_aead_ctx delete_crypto_aead_ctx; + /** + * :member:`delete_crypto_cipher_ctx` is a callback function which + * deletes a given cipher context object. This callback function + * must be specified. + */ + ngtcp2_delete_crypto_cipher_ctx delete_crypto_cipher_ctx; + /** + * :member:`recv_datagram` is a callback function which is invoked + * when DATAGRAM frame is received. This callback function is + * optional. + */ + ngtcp2_recv_datagram recv_datagram; + /** + * :member:`ack_datagram` is a callback function which is invoked + * when a QUIC packet containing DATAGRAM frame is acknowledged by a + * remote endpoint. This callback function is optional. + */ + ngtcp2_ack_datagram ack_datagram; + /** + * :member:`lost_datagram` is a callback function which is invoked + * when a QUIC packet containing DATAGRAM frame is declared lost. + * This callback function is optional. + */ + ngtcp2_lost_datagram lost_datagram; + /** + * :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. + */ + ngtcp2_get_path_challenge_data get_path_challenge_data; + /** + * :member:`stream_stop_sending` is a callback function which is + * invoked when a local endpoint no longer reads from a stream + * before it receives all stream data. This callback function is + * optional. + */ + ngtcp2_stream_stop_sending stream_stop_sending; + /** + * :member:`version_negotiation` is a callback function which is + * invoked when the compatible version negotiation takes place. + * This callback function must be specified. + */ + ngtcp2_version_negotiation version_negotiation; + /** + * :member:`recv_rx_key` is a callback function which is invoked + * when a new key for decrypting packets is installed during QUIC + * cryptographic handshake. It is not called for + * :enum:`ngtcp2_encryption_level.NGTCP2_ENCRYPTION_LEVEL_INITIAL`. + */ + ngtcp2_recv_key recv_rx_key; + /** + * :member:`recv_tx_key` is a callback function which is invoked + * when a new key for encrypting packets is installed during QUIC + * cryptographic handshake. It is not called for + * :enum:`ngtcp2_encryption_level.NGTCP2_ENCRYPTION_LEVEL_INITIAL`. + */ + ngtcp2_recv_key recv_tx_key; + /** + * :member:`tls_early_data_rejected` is a callback function which is + * invoked when server rejected early data during TLS handshake, or + * client decided not to attempt early data. This callback function + * is only used by client. + */ + ngtcp2_tls_early_data_rejected tls_early_data_rejected; +} ngtcp2_callbacks; + +/** + * @function + * + * `ngtcp2_pkt_write_connection_close` writes Initial packet + * containing CONNECTION_CLOSE frame with the given |error_code| and + * the optional |reason| of length |reasonlen| to the buffer pointed + * by |dest| of length |destlen|. All encryption parameters are for + * Initial packet encryption. The packet number is always 0. + * + * The primary use case of this function is for server to send + * CONNECTION_CLOSE frame in Initial packet to close connection + * without committing any state when validating Retry token fails. + * + * This function returns the number of bytes written if it succeeds, + * or one of the following negative error codes: + * + * :macro:`NGTCP2_ERR_NOBUF` + * Buffer is too small. + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` + * Callback function failed. + */ +NGTCP2_EXTERN ngtcp2_ssize ngtcp2_pkt_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, + size_t reasonlen, ngtcp2_encrypt encrypt, const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx, const uint8_t *iv, + ngtcp2_hp_mask hp_mask, const ngtcp2_crypto_cipher *hp, + const ngtcp2_crypto_cipher_ctx *hp_ctx); + +/** + * @function + * + * `ngtcp2_pkt_write_retry` writes Retry packet in the buffer pointed + * by |dest| whose length is |destlen|. |dcid| is the Connection ID + * which appeared in a packet as a Source Connection ID sent by + * client. |scid| is a server chosen Source Connection ID. |odcid| + * specifies Original Destination Connection ID which appeared in a + * packet as a Destination Connection ID sent by client. |token| + * specifies Retry Token, and |tokenlen| specifies its length. |aead| + * must be AEAD_AES_128_GCM. |aead_ctx| must be initialized with + * :macro:`NGTCP2_RETRY_KEY` as an encryption key. + * + * 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_CALLBACK_FAILURE` + * Callback function failed. + * :macro:`NGTCP2_ERR_INVALID_ARGUMENT` + * :member:`odcid->datalen <ngtcp2_cid.datalen>` is less than + * :macro:`NGTCP2_MIN_INITIAL_DCIDLEN`. + */ +NGTCP2_EXTERN ngtcp2_ssize ngtcp2_pkt_write_retry( + uint8_t *dest, size_t destlen, uint32_t version, const ngtcp2_cid *dcid, + const ngtcp2_cid *scid, const ngtcp2_cid *odcid, const uint8_t *token, + size_t tokenlen, ngtcp2_encrypt encrypt, const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx); + +/** + * @function + * + * `ngtcp2_accept` is used by server implementation, and decides + * whether packet |pkt| of length |pktlen| from client is acceptable + * for the very first packet to a connection. + * + * If |dest| is not ``NULL`` and the function returns 0, the decoded + * packet header is stored in the object pointed by |dest|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_INVALID_ARGUMENT` + * The packet is not acceptable for the very first packet to a new + * connection; or the function failed to parse the packet header. + */ +NGTCP2_EXTERN int ngtcp2_accept(ngtcp2_pkt_hd *dest, const uint8_t *pkt, + size_t pktlen); + +/** + * @function + * + * `ngtcp2_conn_client_new` creates new :type:`ngtcp2_conn`, and + * initializes it as client. On success, it stores the pointer to the + * newly allocated object in |*pconn|. |dcid| is a randomized + * Destination Connection ID which must be longer than or equal to + * :macro:`NGTCP2_MIN_INITIAL_DCIDLEN`. |scid| is a Source Connection + * ID chosen by client. |client_chosen_version| is a QUIC version + * that a client chooses. |path| is the network path where this QUIC + * connection is being established, and must not be ``NULL``. + * |callbacks|, |settings|, and |params| must not be ``NULL``, and the + * function makes a copy of each of them. |params| is a local QUIC + * transport parameters, and sent to a remote endpoint during + * handshake. |user_data| is the arbitrary pointer which is passed to + * the user-defined callback functions. If |mem| is ``NULL``, the + * memory allocator returned by `ngtcp2_mem_default()` is used. + * + * Call `ngtcp2_conn_del` to free memory allocated for |*pconn|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory. + */ +NGTCP2_EXTERN int ngtcp2_conn_client_new_versioned( + ngtcp2_conn **pconn, const ngtcp2_cid *dcid, const ngtcp2_cid *scid, + const ngtcp2_path *path, uint32_t client_chosen_version, + int callbacks_version, const ngtcp2_callbacks *callbacks, + int settings_version, const ngtcp2_settings *settings, + int transport_params_version, const ngtcp2_transport_params *params, + const ngtcp2_mem *mem, void *user_data); + +/** + * @function + * + * `ngtcp2_conn_server_new` creates new :type:`ngtcp2_conn`, and + * initializes it as server. On success, it stores the pointer to the + * newly allocated object in |*pconn|. |dcid| is a Destination + * Connection ID, and is usually the Connection ID that appears in + * client Initial packet as Source Connection ID. |scid| is a Source + * Connection ID chosen by server. |path| is the network path where + * this QUIC connection is being established, and must not be + * ``NULL``. |client_chosen_version| is a QUIC version that a client + * chooses. |callbacks|, |settings|, and |params| must not be + * ``NULL``, and the function makes a copy of each of them. |params| + * is a local QUIC transport parameters, and sent to a remote endpoint + * during handshake. |user_data| is the arbitrary pointer which is + * passed to the user-defined callback functions. If |mem| is + * ``NULL``, the memory allocator returned by `ngtcp2_mem_default()` + * is used. + * + * Call `ngtcp2_conn_del` to free memory allocated for |*pconn|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory. + */ +NGTCP2_EXTERN int ngtcp2_conn_server_new_versioned( + ngtcp2_conn **pconn, const ngtcp2_cid *dcid, const ngtcp2_cid *scid, + const ngtcp2_path *path, uint32_t client_chosen_version, + int callbacks_version, const ngtcp2_callbacks *callbacks, + int settings_version, const ngtcp2_settings *settings, + int transport_params_version, const ngtcp2_transport_params *params, + const ngtcp2_mem *mem, void *user_data); + +/** + * @function + * + * `ngtcp2_conn_del` frees resources allocated for |conn|. It also + * frees memory pointed by |conn|. + */ +NGTCP2_EXTERN void ngtcp2_conn_del(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_read_pkt` decrypts QUIC packet given in |pkt| of + * length |pktlen| and processes it. |path| is the network path the + * packet is delivered and must not be ``NULL``. |pi| is packet + * metadata and may be ``NULL``. This function performs QUIC handshake + * as well. + * + * This function must not be called from inside the callback + * functions. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_RETRY` + * Server must perform address validation by sending Retry packet + * (see `ngtcp2_crypto_write_retry` and `ngtcp2_pkt_write_retry`), + * and discard the connection state. Client application does not + * get this error code. + * :macro:`NGTCP2_ERR_DROP_CONN` + * Server application must drop the connection silently (without + * sending any CONNECTION_CLOSE frame), and discard connection + * state. Client application does not get this error code. + * :macro:`NGTCP2_ERR_DRAINING` + * A connection has entered the draining state, and no further + * packet transmission is allowed. + * :macro:`NGTCP2_ERR_CLOSING` + * A connection has entered the closing state, and no further + * packet transmission is allowed. Calling + * `ngtcp2_conn_write_connection_close` makes a connection enter + * this state. + * :macro:`NGTCP2_ERR_CRYPTO` + * An error happened in TLS stack. `ngtcp2_conn_get_tls_alert` + * returns TLS alert if set. + * + * If any other negative error is returned, call + * `ngtcp2_conn_write_connection_close` to get terminal packet, and + * sending it makes QUIC connection enter the closing state. + */ +NGTCP2_EXTERN int +ngtcp2_conn_read_pkt_versioned(ngtcp2_conn *conn, const ngtcp2_path *path, + int pkt_info_version, const ngtcp2_pkt_info *pi, + const uint8_t *pkt, size_t pktlen, + ngtcp2_tstamp ts); + +/** + * @function + * + * `ngtcp2_conn_write_pkt` is equivalent to calling + * `ngtcp2_conn_writev_stream` with -1 as |stream_id|, no stream data, + * and :macro:`NGTCP2_WRITE_STREAM_FLAG_NONE` as flags. + */ +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_tls_handshake_completed` tells |conn| that the TLS + * stack declares TLS handshake completion. This does not mean QUIC + * handshake has completed. The library needs extra conditions to be + * met. + */ +NGTCP2_EXTERN void ngtcp2_conn_tls_handshake_completed(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_get_handshake_completed` returns nonzero if QUIC + * handshake has completed. + */ +NGTCP2_EXTERN int ngtcp2_conn_get_handshake_completed(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_install_initial_key` installs packet protection keying + * materials for Initial packets. |rx_aead_ctx| is AEAD cipher + * context object, and must be initialized with a decryption key. + * |rx_iv| is IV of length |rx_ivlen| for decryption. |rx_hp_ctx| is + * a packet header protection cipher context object for decryption. + * Similarly, |tx_aead_ctx|, |tx_iv| and |tx_hp_ctx| are for + * encrypting outgoing packets, and are the same length with the + * decryption counterpart . If they have already been set, they are + * overwritten. + * + * |ivlen| must be the minimum length of AEAD nonce, or 8 bytes if + * that is larger. + * + * If this function succeeds, |conn| takes ownership of |rx_aead_ctx|, + * |rx_hp_ctx|, |tx_aead_ctx|, and |tx_hp_ctx|. + * :member:`ngtcp2_callbacks.delete_crypto_aead_ctx` and + * :member:`ngtcp2_callbacks.delete_crypto_cipher_ctx` will be called + * to delete these objects when they are no longer used. If this + * function fails, the caller is responsible to delete them. + * + * After receiving Retry packet, a Destination Connection ID that + * client sends in Initial packet most likely changes. In that case, + * client application must generate these keying materials again based + * on new Destination Connection ID, and install them again with this + * function. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory. + */ +NGTCP2_EXTERN int ngtcp2_conn_install_initial_key( + ngtcp2_conn *conn, const ngtcp2_crypto_aead_ctx *rx_aead_ctx, + const uint8_t *rx_iv, const ngtcp2_crypto_cipher_ctx *rx_hp_ctx, + const ngtcp2_crypto_aead_ctx *tx_aead_ctx, const uint8_t *tx_iv, + const ngtcp2_crypto_cipher_ctx *tx_hp_ctx, size_t ivlen); + +/** + * @function + * + * `ngtcp2_conn_install_vneg_initial_key` installs packet protection + * keying materials for Initial packets on compatible version + * negotiation for |version|. |rx_aead_ctx| is AEAD cipher context + * object, and must be initialized with a decryption key. |rx_iv| is + * IV of length |rx_ivlen| for decryption. |rx_hp_ctx| is a packet + * header protection cipher context object for decryption. Similarly, + * |tx_aead_ctx|, |tx_iv| and |tx_hp_ctx| are for encrypting outgoing + * packets, and are the same length with the decryption counterpart. + * If they have already been set, they are overwritten. + * + * |ivlen| must be the minimum length of AEAD nonce, or 8 bytes if + * that is larger. + * + * If this function succeeds, |conn| takes ownership of |rx_aead_ctx|, + * |rx_hp_ctx|, |tx_aead_ctx|, and |tx_hp_ctx|. + * :member:`ngtcp2_callbacks.delete_crypto_aead_ctx` and + * :member:`ngtcp2_callbacks.delete_crypto_cipher_ctx` will be called + * to delete these objects when they are no longer used. If this + * function fails, the caller is responsible to delete them. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory. + */ +NGTCP2_EXTERN int ngtcp2_conn_install_vneg_initial_key( + ngtcp2_conn *conn, uint32_t version, + const ngtcp2_crypto_aead_ctx *rx_aead_ctx, const uint8_t *rx_iv, + const ngtcp2_crypto_cipher_ctx *rx_hp_ctx, + const ngtcp2_crypto_aead_ctx *tx_aead_ctx, const uint8_t *tx_iv, + const ngtcp2_crypto_cipher_ctx *tx_hp_ctx, size_t ivlen); + +/** + * @function + * + * `ngtcp2_conn_install_rx_handshake_key` installs packet protection + * keying materials for decrypting incoming Handshake packets. + * |aead_ctx| is AEAD cipher context object which must be initialized + * with a decryption key. |iv| is IV of length |ivlen|. |hp_ctx| is + * a packet header protection cipher context object. + * + * |ivlen| must be the minimum length of AEAD nonce, or 8 bytes if + * that is larger. + * + * If this function succeeds, |conn| takes ownership of |aead_ctx|, + * and |hp_ctx|. :member:`ngtcp2_callbacks.delete_crypto_aead_ctx` + * and :member:`ngtcp2_callbacks.delete_crypto_cipher_ctx` will be + * called to delete these objects when they are no longer used. If + * this function fails, the caller is responsible to delete them. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory. + */ +NGTCP2_EXTERN int ngtcp2_conn_install_rx_handshake_key( + ngtcp2_conn *conn, const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *iv, size_t ivlen, const ngtcp2_crypto_cipher_ctx *hp_ctx); + +/** + * @function + * + * `ngtcp2_conn_install_tx_handshake_key` installs packet protection + * keying materials for encrypting outgoing Handshake packets. + * |aead_ctx| is AEAD cipher context object which must be initialized + * with an encryption key. |iv| is IV of length |ivlen|. |hp_ctx| is + * a packet header protection cipher context object. + * + * |ivlen| must be the minimum length of AEAD nonce, or 8 bytes if + * that is larger. + * + * If this function succeeds, |conn| takes ownership of |aead_ctx| and + * |hp_ctx|. :member:`ngtcp2_callbacks.delete_crypto_aead_ctx` and + * :member:`ngtcp2_callbacks.delete_crypto_cipher_ctx` will be called + * to delete these objects when they are no longer used. If this + * function fails, the caller is responsible to delete them. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory. + */ +NGTCP2_EXTERN int ngtcp2_conn_install_tx_handshake_key( + ngtcp2_conn *conn, const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *iv, size_t ivlen, const ngtcp2_crypto_cipher_ctx *hp_ctx); + +/** + * @function + * + * `ngtcp2_conn_install_0rtt_key` installs packet protection AEAD + * cipher context object |aead_ctx|, IV |iv| of length |ivlen|, and + * packet header protection cipher context object |hp_ctx| to encrypt + * (for client) or decrypt (for server) 0-RTT packets. + * + * |ivlen| must be the minimum length of AEAD nonce, or 8 bytes if + * that is larger. + * + * If this function succeeds, |conn| takes ownership of |aead_ctx| and + * |hp_ctx|. :member:`ngtcp2_callbacks.delete_crypto_aead_ctx` and + * :member:`ngtcp2_callbacks.delete_crypto_cipher_ctx` will be called + * to delete these objects when they are no longer used. If this + * function fails, the caller is responsible to delete them. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory. + */ +NGTCP2_EXTERN int ngtcp2_conn_install_0rtt_key( + ngtcp2_conn *conn, const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *iv, size_t ivlen, const ngtcp2_crypto_cipher_ctx *hp_ctx); + +/** + * @function + * + * `ngtcp2_conn_install_rx_key` installs packet protection keying + * materials for decrypting 1-RTT packets. |secret| of length + * |secretlen| is the decryption secret which is used to derive keying + * materials passed to this function. |aead_ctx| is AEAD cipher + * context object which must be initialized with a decryption key. + * |iv| is IV of length |ivlen|. |hp_ctx| is a packet header + * protection cipher context object. + * + * |ivlen| must be the minimum length of AEAD nonce, or 8 bytes if + * that is larger. + * + * If this function succeeds, |conn| takes ownership of |aead_ctx| and + * |hp_ctx|. :member:`ngtcp2_callbacks.delete_crypto_aead_ctx` and + * :member:`ngtcp2_callbacks.delete_crypto_cipher_ctx` will be called + * to delete these objects when they are no longer used. If this + * function fails, the caller is responsible to delete them. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory. + */ +NGTCP2_EXTERN int ngtcp2_conn_install_rx_key( + ngtcp2_conn *conn, const uint8_t *secret, size_t secretlen, + const ngtcp2_crypto_aead_ctx *aead_ctx, const uint8_t *iv, size_t ivlen, + const ngtcp2_crypto_cipher_ctx *hp_ctx); + +/** + * @function + * + * `ngtcp2_conn_install_tx_key` installs packet protection keying + * materials for encrypting 1-RTT packets. |secret| of length + * |secretlen| is the encryption secret which is used to derive keying + * materials passed to this function. |aead_ctx| is AEAD cipher + * context object which must be initialized with an encryption key. + * |iv| is IV of length |ivlen|. |hp_ctx| is a packet header + * protection cipher context object. + * + * |ivlen| must be the minimum length of AEAD nonce, or 8 bytes if + * that is larger. + * + * If this function succeeds, |conn| takes ownership of |aead_ctx| and + * |hp_ctx|. :member:`ngtcp2_callbacks.delete_crypto_aead_ctx` and + * :member:`ngtcp2_callbacks.delete_crypto_cipher_ctx` will be called + * to delete these objects when they are no longer used. If this + * function fails, the caller is responsible to delete them. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory. + */ +NGTCP2_EXTERN int ngtcp2_conn_install_tx_key( + ngtcp2_conn *conn, const uint8_t *secret, size_t secretlen, + const ngtcp2_crypto_aead_ctx *aead_ctx, const uint8_t *iv, size_t ivlen, + const ngtcp2_crypto_cipher_ctx *hp_ctx); + +/** + * @function + * + * `ngtcp2_conn_initiate_key_update` initiates the key update. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_INVALID_STATE` + * The previous key update has not been confirmed yet; or key + * update is too frequent; or new keys are not available yet. + */ +NGTCP2_EXTERN int ngtcp2_conn_initiate_key_update(ngtcp2_conn *conn, + ngtcp2_tstamp ts); + +/** + * @function + * + * `ngtcp2_conn_set_tls_error` sets the TLS related error |liberr| in + * |conn|. |liberr| must be one of ngtcp2 library error codes (which + * is defined as NGTCP2_ERR_* macro, such as + * :macro:`NGTCP2_ERR_DECRYPT`). In general, error code should be + * propagated via return value, but sometimes ngtcp2 API is called + * inside callback function of TLS stack, and it does not allow to + * return ngtcp2 error code directly. In this case, implementation + * can set the error code (e.g., + * :macro:`NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM`) using this function. + * + * See also `ngtcp2_conn_get_tls_error`. + */ +NGTCP2_EXTERN void ngtcp2_conn_set_tls_error(ngtcp2_conn *conn, int liberr); + +/** + * @function + * + * `ngtcp2_conn_get_tls_error` returns the value set by + * `ngtcp2_conn_set_tls_error`. If no value is set, this function + * returns 0. + */ +NGTCP2_EXTERN int ngtcp2_conn_get_tls_error(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_set_tls_alert` sets a TLS alert |alert| generated by a + * TLS stack of a local endpoint to |conn|. + * + * See also `ngtcp2_conn_get_tls_alert`. + */ +NGTCP2_EXTERN void ngtcp2_conn_set_tls_alert(ngtcp2_conn *conn, uint8_t alert); + +/** + * @function + * + * `ngtcp2_conn_get_tls_alert` returns the value set by + * `ngtcp2_conn_set_tls_alert`. If no value is set, this function + * returns 0. + */ +NGTCP2_EXTERN uint8_t ngtcp2_conn_get_tls_alert(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_set_keep_alive_timeout` sets keep-alive timeout. If + * nonzero value is given, after a connection is idle at least in a + * given amount of time, a keep-alive packet is sent. If UINT64_MAX + * is set, keep-alive functionality is disabled, and this is the + * default. Specifying 0 in |timeout| is reserved for a future + * extension, and for now it is treated as if UINT64_MAX is given. + */ +NGTCP2_EXTERN void ngtcp2_conn_set_keep_alive_timeout(ngtcp2_conn *conn, + ngtcp2_duration timeout); + +/** + * @function + * + * `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. + */ +NGTCP2_EXTERN ngtcp2_tstamp ngtcp2_conn_get_expiry(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_handle_expiry` handles expired timer. + */ +NGTCP2_EXTERN int ngtcp2_conn_handle_expiry(ngtcp2_conn *conn, + ngtcp2_tstamp ts); + +/** + * @function + * + * `ngtcp2_conn_get_pto` returns Probe Timeout (PTO). + */ +NGTCP2_EXTERN ngtcp2_duration ngtcp2_conn_get_pto(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_decode_and_set_remote_transport_params` decodes QUIC + * transport parameters from the buffer pointed by |data| of length + * |datalen|, and sets the result to |conn|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM` + * The required parameter is missing. + * :macro:`NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM` + * The input is malformed. + * :macro:`NGTCP2_ERR_TRANSPORT_PARAM` + * Failed to validate the remote QUIC transport parameters. + * :macro:`NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE` + * Version negotiation failure. + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` + * User callback failed + */ +NGTCP2_EXTERN int ngtcp2_conn_decode_and_set_remote_transport_params( + ngtcp2_conn *conn, const uint8_t *data, size_t datalen); + +/** + * @function + * + * `ngtcp2_conn_get_remote_transport_params` returns a pointer to the + * remote QUIC transport parameters. If no remote transport + * parameters are set, it returns NULL. + */ +NGTCP2_EXTERN const ngtcp2_transport_params * +ngtcp2_conn_get_remote_transport_params(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_encode_0rtt_transport_params` encodes the QUIC + * transport parameters that are used for 0-RTT data in the buffer + * pointed by |dest| of length |destlen|. It includes at least the + * following fields: + * + * - :member:`ngtcp2_transport_params.initial_max_streams_bidi` + * - :member:`ngtcp2_transport_params.initial_max_streams_uni` + * - :member:`ngtcp2_transport_params.initial_max_stream_data_bidi_local` + * - :member:`ngtcp2_transport_params.initial_max_stream_data_bidi_remote` + * - :member:`ngtcp2_transport_params.initial_max_stream_data_uni` + * - :member:`ngtcp2_transport_params.initial_max_data` + * - :member:`ngtcp2_transport_params.active_connection_id_limit` + * - :member:`ngtcp2_transport_params.max_datagram_frame_size` + * + * If |conn| is initialized as server, the following additional fields + * are also included: + * + * - :member:`ngtcp2_transport_params.max_idle_timeout` + * - :member:`ngtcp2_transport_params.max_udp_payload_size` + * - :member:`ngtcp2_transport_params.disable_active_migration` + * + * If |conn| is initialized as client, these parameters are + * synthesized from the remote transport parameters received from + * server. Otherwise, it is the local transport parameters that are + * set by the local endpoint. + * + * This function returns the number of bytes written, or one of the + * following negative error codes: + * + * :macro:`NGTCP2_ERR_NOBUF` + * Buffer is too small. + */ +NGTCP2_EXTERN +ngtcp2_ssize ngtcp2_conn_encode_0rtt_transport_params(ngtcp2_conn *conn, + uint8_t *dest, + size_t destlen); + +/** + * @function + * + * `ngtcp2_conn_decode_and_set_0rtt_transport_params` decodes QUIC + * transport parameters from |data| of length |datalen|, which is + * assumed to be the parameters received from the server in the + * previous connection, and sets it to |conn|. These parameters are + * used to send 0-RTT data. QUIC requires that client application + * should remember transport parameters along with a session ticket. + * + * At least following fields should be included: + * + * - :member:`ngtcp2_transport_params.initial_max_streams_bidi` + * - :member:`ngtcp2_transport_params.initial_max_streams_uni` + * - :member:`ngtcp2_transport_params.initial_max_stream_data_bidi_local` + * - :member:`ngtcp2_transport_params.initial_max_stream_data_bidi_remote` + * - :member:`ngtcp2_transport_params.initial_max_stream_data_uni` + * - :member:`ngtcp2_transport_params.initial_max_data` + * - :member:`ngtcp2_transport_params.active_connection_id_limit` + * - :member:`ngtcp2_transport_params.max_datagram_frame_size` (if + * DATAGRAM extension was negotiated) + * + * This function must only be used by client. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory. + * :macro:`NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM` + * The input is malformed. + */ +NGTCP2_EXTERN int ngtcp2_conn_decode_and_set_0rtt_transport_params( + ngtcp2_conn *conn, const uint8_t *data, size_t datalen); + +/** + * @function + * + * `ngtcp2_conn_set_local_transport_params` sets the local transport + * parameters |params|. This function can only be called by server. + * Although the local transport parameters are passed to + * `ngtcp2_conn_server_new`, server might want to update them after + * ALPN is chosen. In that case, server can update the transport + * parameters with this function. Server must call this function + * before calling `ngtcp2_conn_install_tx_handshake_key`. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_INVALID_STATE` + * `ngtcp2_conn_install_tx_handshake_key` has been called. + */ +NGTCP2_EXTERN int ngtcp2_conn_set_local_transport_params_versioned( + ngtcp2_conn *conn, int transport_params_version, + const ngtcp2_transport_params *params); + +/** + * @function + * + * `ngtcp2_conn_get_local_transport_params` returns a pointer to the + * local QUIC transport parameters. + */ +NGTCP2_EXTERN const ngtcp2_transport_params * +ngtcp2_conn_get_local_transport_params(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_encode_local_transport_params` encodes the local QUIC + * transport parameters in |dest| of length |destlen|. + * + * This function returns the number of bytes written, or one of the + * following negative error codes: + * + * :macro:`NGTCP2_ERR_NOBUF` + * Buffer is too small. + */ +NGTCP2_EXTERN ngtcp2_ssize ngtcp2_conn_encode_local_transport_params( + ngtcp2_conn *conn, uint8_t *dest, size_t destlen); + +/** + * @function + * + * `ngtcp2_conn_open_bidi_stream` opens new bidirectional stream. The + * |stream_user_data| is the user data specific to the stream. The + * stream ID of the opened stream is stored in |*pstream_id|. + * + * Application can call this function before handshake completes. For + * 0-RTT packet, application can call this function after calling + * `ngtcp2_conn_decode_and_set_0rtt_transport_params`. For 1-RTT + * packet, application can call this function after calling + * `ngtcp2_conn_decode_and_set_remote_transport_params` and + * `ngtcp2_conn_install_tx_key`. If ngtcp2 crypto support library is + * used, application can call this function after calling + * `ngtcp2_crypto_derive_and_install_tx_key` for 1-RTT packet. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory + * :macro:`NGTCP2_ERR_STREAM_ID_BLOCKED` + * The remote endpoint does not allow |stream_id| yet. + */ +NGTCP2_EXTERN int ngtcp2_conn_open_bidi_stream(ngtcp2_conn *conn, + int64_t *pstream_id, + void *stream_user_data); + +/** + * @function + * + * `ngtcp2_conn_open_uni_stream` opens new unidirectional stream. The + * |stream_user_data| is the user data specific to the stream. The + * stream ID of the opened stream is stored in |*pstream_id|. + * + * Application can call this function before handshake completes. For + * 0-RTT packet, application can call this function after calling + * `ngtcp2_conn_decode_and_set_0rtt_transport_params`. For 1-RTT + * packet, application can call this function after calling + * `ngtcp2_conn_decode_and_set_remote_transport_params` and + * `ngtcp2_conn_install_tx_key`. If ngtcp2 crypto support library is + * used, application can call this function after calling + * `ngtcp2_crypto_derive_and_install_tx_key` for 1-RTT packet. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory + * :macro:`NGTCP2_ERR_STREAM_ID_BLOCKED` + * The remote endpoint does not allow |stream_id| yet. + */ +NGTCP2_EXTERN int ngtcp2_conn_open_uni_stream(ngtcp2_conn *conn, + int64_t *pstream_id, + void *stream_user_data); + +/** + * @function + * + * `ngtcp2_conn_shutdown_stream` closes a stream denoted by + * |stream_id| abruptly. |app_error_code| is one of application error + * codes, and indicates the reason of shutdown. Successful call of + * this function does not immediately erase the state of the stream. + * The actual deletion is done when the remote endpoint sends + * acknowledgement. Calling this function is equivalent to call + * `ngtcp2_conn_shutdown_stream_read`, and + * `ngtcp2_conn_shutdown_stream_write` sequentially with the following + * differences. If |stream_id| refers to a local unidirectional + * stream, this function only shutdowns write side of the stream. If + * |stream_id| refers to a remote unidirectional stream, this function + * only shutdowns read side of the stream. + * + * |flags| is currently unused, and should be set to 0. + * + * This function returns 0 if a stream denoted by |stream_id| is not + * found. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory + */ +NGTCP2_EXTERN int ngtcp2_conn_shutdown_stream(ngtcp2_conn *conn, uint32_t flags, + int64_t stream_id, + uint64_t app_error_code); + +/** + * @function + * + * `ngtcp2_conn_shutdown_stream_write` closes write-side of a stream + * denoted by |stream_id| abruptly. |app_error_code| is one of + * application error codes, and indicates the reason of shutdown. If + * this function succeeds, no further application data is sent to the + * remote endpoint. It discards all data which has not been + * acknowledged yet. + * + * |flags| is currently unused, and should be set to 0. + * + * This function returns 0 if a stream denoted by |stream_id| is not + * found. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory + * :macro:`NGTCP2_ERR_INVALID_ARGUMENT` + * |stream_id| refers to a remote unidirectional stream. + */ +NGTCP2_EXTERN int ngtcp2_conn_shutdown_stream_write(ngtcp2_conn *conn, + uint32_t flags, + int64_t stream_id, + uint64_t app_error_code); + +/** + * @function + * + * `ngtcp2_conn_shutdown_stream_read` closes read-side of a stream + * denoted by |stream_id| abruptly. |app_error_code| is one of + * application error codes, and indicates the reason of shutdown. If + * this function succeeds, no further application data is forwarded to + * an application layer. + * + * |flags| is currently unused, and should be set to 0. + * + * This function returns 0 if a stream denoted by |stream_id| is not + * found. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory + * :macro:`NGTCP2_ERR_INVALID_ARGUMENT` + * |stream_id| refers to a local unidirectional stream. + */ +NGTCP2_EXTERN int ngtcp2_conn_shutdown_stream_read(ngtcp2_conn *conn, + uint32_t flags, + int64_t stream_id, + uint64_t app_error_code); + +/** + * @macrosection + * + * Write stream data flags + */ + +/** + * @macro + * + * :macro:`NGTCP2_WRITE_STREAM_FLAG_NONE` indicates no flag set. + */ +#define NGTCP2_WRITE_STREAM_FLAG_NONE 0x00u + +/** + * @macro + * + * :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 + +/** + * @macro + * + * :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 + +/** + * @function + * + * `ngtcp2_conn_write_stream` is just like + * `ngtcp2_conn_writev_stream`. The only difference is that it + * conveniently accepts a single buffer. + */ +NGTCP2_EXTERN ngtcp2_ssize ngtcp2_conn_write_stream_versioned( + ngtcp2_conn *conn, ngtcp2_path *path, int pkt_info_version, + ngtcp2_pkt_info *pi, uint8_t *dest, size_t destlen, ngtcp2_ssize *pdatalen, + uint32_t flags, int64_t stream_id, const uint8_t *data, size_t datalen, + ngtcp2_tstamp ts); + +/** + * @function + * + * `ngtcp2_conn_writev_stream` writes a packet containing stream data + * of a stream denoted by |stream_id|. The buffer of the packet is + * pointed by |dest| of length |destlen|. This function performs QUIC + * handshake as well. + * + * |destlen| should be at least + * :member:`ngtcp2_settings.max_tx_udp_payload_size`. + * + * Specifying -1 to |stream_id| means no new stream data to send. + * + * If |path| is not ``NULL``, this function stores the network path + * with which the packet should be sent. Each addr field + * (:member:`ngtcp2_path.local` and :member:`ngtcp2_path.remote`) must + * point to the buffer which should be at least + * sizeof(:type:`sockaddr_union`) bytes long. The assignment might + * not be done if nothing is written to |dest|. + * + * If |pi| is not ``NULL``, this function stores packet metadata in it + * if it succeeds. The metadata includes ECN markings. When calling + * this function again after it returns + * :macro:`NGTCP2_ERR_WRITE_MORE`, caller must pass the same |pi| to + * this function. + * + * Stream data is specified as vector of data |datav|. |datavcnt| + * specifies the number of :type:`ngtcp2_vec` that |datav| includes. + * + * If all given data is encoded as STREAM frame in |dest|, and if + * |flags| & :macro:`NGTCP2_WRITE_STREAM_FLAG_FIN` is nonzero, fin + * flag is set to outgoing STREAM frame. Otherwise, fin flag in + * STREAM frame is not set. + * + * This packet may contain frames other than STREAM frame. The packet + * might not contain STREAM frame if other frames occupy the packet. + * In that case, |*pdatalen| would be -1 if |pdatalen| is not + * ``NULL``. + * + * If |flags| & :macro:`NGTCP2_WRITE_STREAM_FLAG_FIN` is nonzero, and + * 0 length STREAM frame is successfully serialized, |*pdatalen| would + * be 0. + * + * The number of data encoded in STREAM frame is stored in |*pdatalen| + * if it is not ``NULL``. The caller must keep the portion of data + * covered by |*pdatalen| bytes in tact until + * :member:`ngtcp2_callbacks.acked_stream_data_offset` indicates that + * they are acknowledged by a remote endpoint or the stream is closed. + * + * If the given stream data is small (e.g., few bytes), the packet + * might be severely under filled. Too many small packet might + * increase overall packet processing costs. Unless there are + * retransmissions, by default, application can only send 1 STREAM + * frame in one QUIC packet. In order to include more than 1 STREAM + * frame in one QUIC packet, specify + * :macro:`NGTCP2_WRITE_STREAM_FLAG_MORE` in |flags|. This is + * analogous to ``MSG_MORE`` flag in :manpage:`send(2)`. If the + * :macro:`NGTCP2_WRITE_STREAM_FLAG_MORE` is used, there are 4 + * outcomes: + * + * - The function returns the written length of packet just like + * without :macro:`NGTCP2_WRITE_STREAM_FLAG_MORE`. This is because + * packet is nearly full, and the library decided to make a complete + * packet. |*pdatalen| might be -1 or >= 0. It may return 0 which + * indicates that no packet transmission is possible at the moment + * for some reason. + * + * - The function returns :macro:`NGTCP2_ERR_WRITE_MORE`. In this + * case, |*pdatalen| >= 0 is asserted. It indicates that + * application can still call this function with different stream + * data (or `ngtcp2_conn_writev_datagram` if it has data to send in + * unreliable datagram) to pack them into the same packet. + * Application has to specify the same |conn|, |path|, |pi|, |dest|, + * |destlen|, and |ts| parameters, otherwise the behaviour is + * undefined. The application can change |flags|. + * + * - The function returns one of the following negative error codes: + * :macro:`NGTCP2_ERR_STREAM_DATA_BLOCKED`, + * :macro:`NGTCP2_ERR_STREAM_NOT_FOUND`, or + * :macro:`NGTCP2_ERR_STREAM_SHUT_WR`. In this case, |*pdatalen| == + * -1 is asserted. Application can still write the stream data of + * the other streams by calling this function (or + * `ngtcp2_conn_writev_datagram` if it has data to send in + * unreliable datagram) to pack them into the same packet. + * Application has to specify the same |conn|, |path|, |pi|, |dest|, + * |destlen|, and |ts| parameters, otherwise the behaviour is + * undefined. The application can change |flags|. + * + * - The other negative error codes might be returned just like + * without :macro:`NGTCP2_WRITE_STREAM_FLAG_MORE`. These errors + * should be treated as a connection error. + * + * When application uses :macro:`NGTCP2_WRITE_STREAM_FLAG_MORE` at + * least once, it must not call other ngtcp2 API functions + * (application can still call `ngtcp2_conn_write_connection_close` to + * handle error from this function. It can also call + * `ngtcp2_conn_shutdown_stream_read`, + * `ngtcp2_conn_shutdown_stream_write`, and + * `ngtcp2_conn_shutdown_stream`), just keep calling this function (or + * `ngtcp2_conn_writev_datagram`) until it returns 0, a positive + * number (which indicates a complete packet is ready), or the error + * codes other than :macro:`NGTCP2_ERR_WRITE_MORE`, + * :macro:`NGTCP2_ERR_STREAM_DATA_BLOCKED`, + * :macro:`NGTCP2_ERR_STREAM_NOT_FOUND`, and + * :macro:`NGTCP2_ERR_STREAM_SHUT_WR`. If there is no stream data to + * include, call this function with |stream_id| as -1 to stop + * coalescing and write a packet. + * + * This function returns 0 if it cannot write any frame because buffer + * is too small, or packet is congestion limited. Application should + * keep reading and wait for congestion window to grow. + * + * This function must not be called from inside the callback + * functions. + * + * `ngtcp2_conn_update_pkt_tx_time` must be called after this + * function. Application may call this function multiple times before + * calling `ngtcp2_conn_update_pkt_tx_time`. + * + * This function returns the number of bytes written in |dest| if it + * succeeds, or one of the following negative error codes: + * + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory + * :macro:`NGTCP2_ERR_STREAM_NOT_FOUND` + * Stream does not exist + * :macro:`NGTCP2_ERR_STREAM_SHUT_WR` + * Stream is half closed (local); or stream is being reset. + * :macro:`NGTCP2_ERR_PKT_NUM_EXHAUSTED` + * Packet number is exhausted, and cannot send any more packet. + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` + * User callback failed + * :macro:`NGTCP2_ERR_INVALID_ARGUMENT` + * The total length of stream data is too large. + * :macro:`NGTCP2_ERR_STREAM_DATA_BLOCKED` + * Stream is blocked because of flow control. + * :macro:`NGTCP2_ERR_WRITE_MORE` + * (Only when :macro:`NGTCP2_WRITE_STREAM_FLAG_MORE` is specified) + * Application can call this function to pack more stream data + * into the same packet. See above to know how it works. + * + * If any other negative error is returned, call + * `ngtcp2_conn_write_connection_close` to get terminal packet, and + * sending it makes QUIC connection enter the closing state. + */ +NGTCP2_EXTERN ngtcp2_ssize ngtcp2_conn_writev_stream_versioned( + ngtcp2_conn *conn, ngtcp2_path *path, int pkt_info_version, + ngtcp2_pkt_info *pi, uint8_t *dest, size_t destlen, ngtcp2_ssize *pdatalen, + uint32_t flags, int64_t stream_id, const ngtcp2_vec *datav, size_t datavcnt, + ngtcp2_tstamp ts); + +/** + * @macrosection + * + * Write datagram flags + */ + +/** + * @macro + * + * :macro:`NGTCP2_WRITE_DATAGRAM_FLAG_NONE` indicates no flag set. + */ +#define NGTCP2_WRITE_DATAGRAM_FLAG_NONE 0x00u + +/** + * @macro + * + * :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 + +/** + * @function + * + * `ngtcp2_conn_write_datagram` is just like + * `ngtcp2_conn_writev_datagram`. The only difference is that it + * conveniently accepts a single buffer. + */ +NGTCP2_EXTERN ngtcp2_ssize ngtcp2_conn_write_datagram_versioned( + ngtcp2_conn *conn, ngtcp2_path *path, int pkt_info_version, + ngtcp2_pkt_info *pi, uint8_t *dest, size_t destlen, int *paccepted, + uint32_t flags, uint64_t dgram_id, const uint8_t *data, size_t datalen, + ngtcp2_tstamp ts); + +/** + * @function + * + * `ngtcp2_conn_writev_datagram` writes a packet containing unreliable + * data in DATAGRAM frame. The buffer of the packet is pointed by + * |dest| of length |destlen|. This function performs QUIC handshake + * as well. + * + * |destlen| should be at least + * :member:`ngtcp2_settings.max_tx_udp_payload_size`. + * + * For |path| and |pi| parameters, refer to + * `ngtcp2_conn_writev_stream`. + * + * Stream data is specified as vector of data |datav|. |datavcnt| + * specifies the number of :type:`ngtcp2_vec` that |datav| includes. + * + * If the given data is written to the buffer, nonzero value is + * assigned to |*paccepted| if it is not NULL. The data in DATAGRAM + * frame cannot be fragmented; writing partial data is not possible. + * + * |dgram_id| is an opaque identifier which should uniquely identify + * the given DATAGRAM data. It is passed to + * :member:`ngtcp2_callbacks.ack_datagram` callback when a packet that + * contains DATAGRAM frame is acknowledged. It is also passed to + * :member:`ngtcp2_callbacks.lost_datagram` callback when a packet + * that contains DATAGRAM frame is declared lost. If an application + * uses neither of those callbacks, it can sets 0 to this parameter. + * + * This function might write other frames other than DATAGRAM frame, + * just like `ngtcp2_conn_writev_stream`. + * + * If the function returns 0, it means that no more data cannot be + * sent because of congestion control limit; or, data does not fit + * into the provided buffer; or, a local endpoint, as a server, is + * unable to send data because of its amplification limit. In this + * case, |*paccepted| is assigned zero if it is not NULL. + * + * If :macro:`NGTCP2_WRITE_DATAGRAM_FLAG_MORE` is set in |flags|, + * there are 3 outcomes: + * + * - The function returns the written length of packet just like + * without :macro:`NGTCP2_WRITE_DATAGRAM_FLAG_MORE`. This is + * because packet is nearly full and the library decided to make a + * complete packet. |*paccepted| might be zero or nonzero. + * + * - The function returns :macro:`NGTCP2_ERR_WRITE_MORE`. In this + * case, |*paccepted| != 0 is asserted. This indicates that + * application can call this function with another unreliable data + * (or `ngtcp2_conn_writev_stream` if it has stream data to send) to + * pack them into the same packet. Application has to specify the + * same |conn|, |path|, |pi|, |dest|, |destlen|, and |ts| + * parameters, otherwise the behaviour is undefined. The + * application can change |flags|. + * + * - The other error might be returned just like without + * :macro:`NGTCP2_WRITE_DATAGRAM_FLAG_MORE`. + * + * When application sees :macro:`NGTCP2_ERR_WRITE_MORE`, it must not + * call other ngtcp2 API functions (application can still call + * `ngtcp2_conn_write_connection_close` to handle error from this + * function. It can also call `ngtcp2_conn_shutdown_stream_read`, + * `ngtcp2_conn_shutdown_stream_write`, and + * `ngtcp2_conn_shutdown_stream`). Just keep calling this function + * (or `ngtcp2_conn_writev_stream`) until it returns a positive number + * (which indicates a complete packet is ready). + * + * This function returns the number of bytes written in |dest| if it + * succeeds, or one of the following negative error codes: + * + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory + * :macro:`NGTCP2_ERR_PKT_NUM_EXHAUSTED` + * Packet number is exhausted, and cannot send any more packet. + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` + * User callback failed + * :macro:`NGTCP2_ERR_WRITE_MORE` + * (Only when :macro:`NGTCP2_WRITE_DATAGRAM_FLAG_MORE` is + * specified) Application can call this function to pack more data + * into the same packet. See above to know how it works. + * :macro:`NGTCP2_ERR_INVALID_STATE` + * A remote endpoint did not express the DATAGRAM frame support. + * :macro:`NGTCP2_ERR_INVALID_ARGUMENT` + * The provisional DATAGRAM frame size exceeds the maximum + * DATAGRAM frame size that a remote endpoint can receive. + * + * If any other negative error is returned, call + * `ngtcp2_conn_write_connection_close` to get terminal packet, and + * sending it makes QUIC connection enter the closing state. + */ +NGTCP2_EXTERN ngtcp2_ssize ngtcp2_conn_writev_datagram_versioned( + ngtcp2_conn *conn, ngtcp2_path *path, int pkt_info_version, + ngtcp2_pkt_info *pi, uint8_t *dest, size_t destlen, int *paccepted, + uint32_t flags, uint64_t dgram_id, const ngtcp2_vec *datav, size_t datavcnt, + ngtcp2_tstamp ts); + +/** + * @function + * + * `ngtcp2_conn_in_closing_period` returns nonzero if |conn| is in the + * closing period. + */ +NGTCP2_EXTERN int ngtcp2_conn_in_closing_period(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_in_draining_period` returns nonzero if |conn| is in + * the draining period. + */ +NGTCP2_EXTERN int ngtcp2_conn_in_draining_period(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_extend_max_stream_offset` extends the maximum stream + * data that a remote endpoint can send by |datalen|. |stream_id| + * specifies the stream ID. This function only extends stream-level + * flow control window. + * + * This function returns 0 if a stream denoted by |stream_id| is not + * found. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory. + * :macro:`NGTCP2_ERR_INVALID_ARGUMENT` + * |stream_id| refers to a local unidirectional stream. + */ +NGTCP2_EXTERN int ngtcp2_conn_extend_max_stream_offset(ngtcp2_conn *conn, + int64_t stream_id, + uint64_t datalen); + +/** + * @function + * + * `ngtcp2_conn_extend_max_offset` extends max data offset by + * |datalen|. This function only extends connection-level flow + * control window. + */ +NGTCP2_EXTERN void ngtcp2_conn_extend_max_offset(ngtcp2_conn *conn, + uint64_t datalen); + +/** + * @function + * + * `ngtcp2_conn_extend_max_streams_bidi` extends the number of maximum + * remote bidirectional streams that a remote endpoint can open by + * |n|. + * + * The library does not increase maximum stream limit automatically. + * The exception is when a stream is closed without + * :member:`ngtcp2_callbacks.stream_open` callback being called. In + * this case, stream limit is increased automatically. + */ +NGTCP2_EXTERN void ngtcp2_conn_extend_max_streams_bidi(ngtcp2_conn *conn, + size_t n); + +/** + * @function + * + * `ngtcp2_conn_extend_max_streams_uni` extends the number of maximum + * remote unidirectional streams that a remote endpoint can open by + * |n|. + * + * The library does not increase maximum stream limit automatically. + * The exception is when a stream is closed without + * :member:`ngtcp2_callbacks.stream_open` callback being called. In + * this case, stream limit is increased automatically. + */ +NGTCP2_EXTERN void ngtcp2_conn_extend_max_streams_uni(ngtcp2_conn *conn, + size_t n); + +/** + * @function + * + * `ngtcp2_conn_get_dcid` returns the non-NULL pointer to the current + * Destination Connection ID. If no Destination Connection ID is + * present, the return value is not ``NULL``, and its :member:`datalen + * <ngtcp2_cid.datalen>` field is 0. + */ +NGTCP2_EXTERN const ngtcp2_cid *ngtcp2_conn_get_dcid(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_get_client_initial_dcid` returns the non-NULL pointer + * to the Destination Connection ID that client sent in its Initial + * packet. If the Destination Connection ID is not present, the + * return value is not ``NULL``, and its :member:`datalen + * <ngtcp2_cid.datalen>` field is 0. + */ +NGTCP2_EXTERN const ngtcp2_cid * +ngtcp2_conn_get_client_initial_dcid(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_get_scid` writes the all Source Connection IDs which a + * local endpoint has provided to a remote endpoint, and are not + * retired in |dest|. If |dest| is NULL, this function does not write + * anything, and returns the number of Source Connection IDs that + * would otherwise be written to the provided buffer. The buffer + * pointed by |dest| must have sizeof(:type:`ngtcp2_cid`) * n bytes + * available, where n is the return value of `ngtcp2_conn_get_scid` + * with |dest| == NULL. + */ +NGTCP2_EXTERN size_t ngtcp2_conn_get_scid(ngtcp2_conn *conn, ngtcp2_cid *dest); + +/** + * @struct + * + * :type:`ngtcp2_cid_token` is the convenient struct to store + * Connection ID, its associated path, and stateless reset token. + */ +typedef struct ngtcp2_cid_token { + /** + * :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. + */ + uint8_t token[NGTCP2_STATELESS_RESET_TOKENLEN]; + /** + * :member:`token_present` is nonzero if token contains stateless + * reset token. + */ + uint8_t token_present; +} ngtcp2_cid_token; + +/** + * @function + * + * `ngtcp2_conn_get_active_dcid` 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_token`) * n bytes available, where n is + * the return value of `ngtcp2_conn_get_active_dcid` with |dest| == + * NULL. + */ +NGTCP2_EXTERN size_t ngtcp2_conn_get_active_dcid(ngtcp2_conn *conn, + ngtcp2_cid_token *dest); + +/** + * @function + * + * `ngtcp2_conn_get_client_chosen_version` returns the client chosen + * version. + */ +NGTCP2_EXTERN uint32_t ngtcp2_conn_get_client_chosen_version(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_get_negotiated_version` returns the negotiated + * version. + * + * Until the version is negotiated, this function returns 0. + */ +NGTCP2_EXTERN uint32_t ngtcp2_conn_get_negotiated_version(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_tls_early_data_rejected` tells |conn| that early data + * was rejected by a server during TLS handshake, or client decided + * not to attempt early data for some reason. |conn| discards the + * following connection states: + * + * - Any opened streams. + * - Stream identifier allocations. + * - Max data extended by `ngtcp2_conn_extend_max_offset`. + * - Max bidi streams extended by `ngtcp2_conn_extend_max_streams_bidi`. + * - Max uni streams extended by `ngtcp2_conn_extend_max_streams_uni`. + * + * Application which wishes to retransmit early data, it has to open + * streams, and send stream data again. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` + * User callback failed + */ +NGTCP2_EXTERN int ngtcp2_conn_tls_early_data_rejected(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_get_tls_early_data_rejected` returns nonzero if + * `ngtcp2_conn_tls_early_data_rejected` has been called. + */ +NGTCP2_EXTERN int ngtcp2_conn_get_tls_early_data_rejected(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_get_conn_info` assigns connection statistics data to + * |*cinfo|. + */ +NGTCP2_EXTERN void ngtcp2_conn_get_conn_info_versioned(ngtcp2_conn *conn, + int conn_info_version, + ngtcp2_conn_info *cinfo); + +/** + * @function + * + * `ngtcp2_conn_submit_crypto_data` submits crypto data |data| of + * length |datalen| to the library for transmission. + * |encryption_level| specifies the encryption level of data. + * + * The library makes a copy of the buffer pointed by |data| of length + * |datalen|. Application can discard |data|. + */ +NGTCP2_EXTERN int +ngtcp2_conn_submit_crypto_data(ngtcp2_conn *conn, + ngtcp2_encryption_level encryption_level, + const uint8_t *data, const size_t datalen); + +/** + * @function + * + * `ngtcp2_conn_submit_new_token` submits address validation token. + * It is sent in NEW_TOKEN frame. Only server can call this function. + * |tokenlen| must not be 0. + * + * This function makes a copy of the buffer pointed by |token| of + * length |tokenlen|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory. + */ +NGTCP2_EXTERN int ngtcp2_conn_submit_new_token(ngtcp2_conn *conn, + const uint8_t *token, + size_t tokenlen); + +/** + * @function + * + * `ngtcp2_conn_set_local_addr` sets local endpoint address |addr| to + * the current path of |conn|. This function is provided for testing + * purpose only. + */ +NGTCP2_EXTERN void ngtcp2_conn_set_local_addr(ngtcp2_conn *conn, + const ngtcp2_addr *addr); + +/** + * @function + * + * `ngtcp2_conn_set_path_user_data` sets the |path_user_data| to the + * current path (see :member:`ngtcp2_path.user_data`). + */ +NGTCP2_EXTERN void ngtcp2_conn_set_path_user_data(ngtcp2_conn *conn, + void *path_user_data); + +/** + * @function + * + * `ngtcp2_conn_get_path` returns the current path. + */ +NGTCP2_EXTERN const ngtcp2_path *ngtcp2_conn_get_path(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_get_max_tx_udp_payload_size` returns the maximum UDP + * payload size that this local endpoint would send. This is the + * value of :member:`ngtcp2_settings.max_tx_udp_payload_size` that is + * passed to `ngtcp2_conn_client_new` or `ngtcp2_conn_server_new`. + */ +NGTCP2_EXTERN size_t ngtcp2_conn_get_max_tx_udp_payload_size(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_get_path_max_tx_udp_payload_size` returns the maximum + * UDP payload size for the current path. If + * :member:`ngtcp2_settings.no_tx_udp_payload_size_shaping` is set to + * nonzero, this function is equivalent to + * `ngtcp2_conn_get_max_tx_udp_payload_size`. Otherwise, it returns + * the maximum UDP payload size that is probed for the current path. + */ +NGTCP2_EXTERN size_t +ngtcp2_conn_get_path_max_tx_udp_payload_size(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_initiate_immediate_migration` starts connection + * migration to the given |path|. Only client can initiate migration. + * This function does immediate migration; while the path validation + * is nonetheless performed, this function does not wait for it to + * succeed. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_INVALID_STATE` + * Migration is disabled; or handshake is not yet confirmed; or + * client is migrating to server's preferred address. + * :macro:`NGTCP2_ERR_CONN_ID_BLOCKED` + * No unused connection ID is available. + * :macro:`NGTCP2_ERR_INVALID_ARGUMENT` + * :member:`local <ngtcp2_path.local>` field of |path| equals the + * current local address. + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory + */ +NGTCP2_EXTERN int ngtcp2_conn_initiate_immediate_migration( + ngtcp2_conn *conn, const ngtcp2_path *path, ngtcp2_tstamp ts); + +/** + * @function + * + * `ngtcp2_conn_initiate_migration` starts connection migration to the + * given |path|. Only client can initiate migration. Unlike + * `ngtcp2_conn_initiate_immediate_migration`, this function starts a + * path validation with a new path, and migrate to the new path after + * successful path validation. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_INVALID_STATE` + * Migration is disabled; or handshake is not yet confirmed; or + * client is migrating to server's preferred address. + * :macro:`NGTCP2_ERR_CONN_ID_BLOCKED` + * No unused connection ID is available. + * :macro:`NGTCP2_ERR_INVALID_ARGUMENT` + * :member:`local <ngtcp2_path.local>` field of |path| equals the + * current local address. + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory + */ +NGTCP2_EXTERN int ngtcp2_conn_initiate_migration(ngtcp2_conn *conn, + const ngtcp2_path *path, + ngtcp2_tstamp ts); + +/** + * @function + * + * `ngtcp2_conn_get_max_data_left` returns the number of bytes that + * this local endpoint can send in this connection without violating + * connection-level flow control. + */ +NGTCP2_EXTERN uint64_t ngtcp2_conn_get_max_data_left(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_get_max_stream_data_left` returns the number of bytes + * that this local endpoint can send to a stream identified by + * |stream_id| without violating stream-level flow control. If no + * such stream is found, this function returns 0. + */ +NGTCP2_EXTERN uint64_t ngtcp2_conn_get_max_stream_data_left(ngtcp2_conn *conn, + int64_t stream_id); + +/** + * @function + * + * `ngtcp2_conn_get_streams_bidi_left` returns the number of + * bidirectional streams which the local endpoint can open without + * violating stream concurrency limit. + */ +NGTCP2_EXTERN uint64_t ngtcp2_conn_get_streams_bidi_left(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_get_streams_uni_left` returns the number of + * unidirectional streams which the local endpoint can open without + * violating stream concurrency limit. + */ +NGTCP2_EXTERN uint64_t ngtcp2_conn_get_streams_uni_left(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_get_cwnd_left` returns the cwnd minus the number of + * bytes in flight on the current path. If the former is smaller than + * the latter, this function returns 0. + */ +NGTCP2_EXTERN uint64_t ngtcp2_conn_get_cwnd_left(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_set_initial_crypto_ctx` sets |ctx| for Initial packet + * encryption. The passed data will be passed to + * :type:`ngtcp2_encrypt`, :type:`ngtcp2_decrypt` and + * :type:`ngtcp2_hp_mask` callbacks. + */ +NGTCP2_EXTERN void +ngtcp2_conn_set_initial_crypto_ctx(ngtcp2_conn *conn, + const ngtcp2_crypto_ctx *ctx); + +/** + * @function + * + * `ngtcp2_conn_get_initial_crypto_ctx` returns + * :type:`ngtcp2_crypto_ctx` object for Initial packet encryption. + */ +NGTCP2_EXTERN const ngtcp2_crypto_ctx * +ngtcp2_conn_get_initial_crypto_ctx(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_set_crypto_ctx` sets |ctx| for Handshake/1-RTT packet + * encryption. The passed data will be passed to + * :type:`ngtcp2_encrypt`, :type:`ngtcp2_decrypt` and + * :type:`ngtcp2_hp_mask` callbacks. + */ +NGTCP2_EXTERN void ngtcp2_conn_set_crypto_ctx(ngtcp2_conn *conn, + const ngtcp2_crypto_ctx *ctx); + +/** + * @function + * + * `ngtcp2_conn_get_crypto_ctx` returns :type:`ngtcp2_crypto_ctx` + * object for Handshake/1-RTT packet encryption. + */ +NGTCP2_EXTERN const ngtcp2_crypto_ctx * +ngtcp2_conn_get_crypto_ctx(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_set_0rtt_crypto_ctx` sets |ctx| for 0-RTT packet + * encryption. The passed data will be passed to + * :type:`ngtcp2_encrypt`, :type:`ngtcp2_decrypt` and + * :type:`ngtcp2_hp_mask` callbacks. + */ +NGTCP2_EXTERN void +ngtcp2_conn_set_0rtt_crypto_ctx(ngtcp2_conn *conn, + const ngtcp2_crypto_ctx *ctx); + +/** + * @function + * + * `ngtcp2_conn_get_0rtt_crypto_ctx` returns :type:`ngtcp2_crypto_ctx` + * object for 0-RTT packet encryption. + */ +NGTCP2_EXTERN const ngtcp2_crypto_ctx * +ngtcp2_conn_get_0rtt_crypto_ctx(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_get_tls_native_handle` returns TLS native handle set + * by `ngtcp2_conn_set_tls_native_handle`. + */ +NGTCP2_EXTERN void *ngtcp2_conn_get_tls_native_handle(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_set_tls_native_handle` sets TLS native handle + * |tls_native_handle| to |conn|. Internally, it is used as an opaque + * pointer. + */ +NGTCP2_EXTERN void ngtcp2_conn_set_tls_native_handle(ngtcp2_conn *conn, + void *tls_native_handle); + +/** + * @function + * + * `ngtcp2_conn_set_retry_aead` sets |aead| and |aead_ctx| for Retry + * integrity tag verification. |aead| must be AEAD_AES_128_GCM. + * |aead_ctx| must be initialized with :macro:`NGTCP2_RETRY_KEY` as + * encryption key. This function must be called if |conn| is + * initialized as client. Server does not verify the tag, and has no + * need to call this function. + * + * |conn| takes ownership of |aead_ctx|. + * :member:`ngtcp2_callbacks.delete_crypto_aead_ctx` will be called to + * delete this object when it is no longer used. + */ +NGTCP2_EXTERN void +ngtcp2_conn_set_retry_aead(ngtcp2_conn *conn, const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx); + +/** + * @enum + * + * :type:`ngtcp2_ccerr_type` defines connection error type. + */ +typedef enum ngtcp2_ccerr_type { + /** + * :enum:`NGTCP2_CCERR_TYPE_TRANSPORT` indicates the QUIC transport + * error, and the error code is QUIC transport error code. + */ + NGTCP2_CCERR_TYPE_TRANSPORT, + /** + * :enum:`NGTCP2_CCERR_TYPE_APPLICATION` indicates an application + * error, and the error code is application error code. + */ + NGTCP2_CCERR_TYPE_APPLICATION, + /** + * :enum:`NGTCP2_CCERR_TYPE_VERSION_NEGOTIATION` is a special case + * of QUIC transport error, and it indicates that client receives + * Version Negotiation packet. + */ + NGTCP2_CCERR_TYPE_VERSION_NEGOTIATION, + /** + * :enum:`NGTCP2_CCERR_TYPE_IDLE_CLOSE` is a special case of QUIC + * transport error, and it indicates that connection is closed + * because of idle timeout. + */ + NGTCP2_CCERR_TYPE_IDLE_CLOSE +} ngtcp2_ccerr_type; + +/** + * @struct + * + * :type:`ngtcp2_ccerr` contains connection error code, its type, a + * frame type that caused this error, and the optional reason phrase. + */ +typedef struct ngtcp2_ccerr { + /** + * :member:`type` is the type of this error. + */ + ngtcp2_ccerr_type type; + /** + * :member:`error_code` is the error code for connection closure. + * Its interpretation depends on :member:`type`. + */ + uint64_t error_code; + /** + * :member:`frame_type` is the type of QUIC frame which triggers + * this connection error. This field is set to 0 if the frame type + * is unknown. + */ + uint64_t frame_type; + /** + * :member:`reason` points to the buffer which contains a reason + * phrase. It may be NULL if there is no reason phrase. If it is + * received from a remote endpoint, it is truncated to at most 1024 + * bytes. + */ + const uint8_t *reason; + /** + * :member:`reasonlen` is the length of data pointed by + * :member:`reason`. + */ + size_t reasonlen; +} ngtcp2_ccerr; + +/** + * @function + * + * `ngtcp2_ccerr_default` initializes |ccerr| with the default values. + * It sets the following fields: + * + * - :member:`type <ngtcp2_ccerr.type>` = + * :enum:`ngtcp2_ccerr_type.NGTCP2_CCERR_TYPE_TRANSPORT` + * - :member:`error_code <ngtcp2_ccerr.error_code>` = + * :macro:`NGTCP2_NO_ERROR`. + * - :member:`frame_type <ngtcp2_ccerr.frame_type>` = 0 + * - :member:`reason <ngtcp2_ccerr.reason>` = NULL + * - :member:`reasonlen <ngtcp2_ccerr.reasonlen>` = 0 + */ +NGTCP2_EXTERN void ngtcp2_ccerr_default(ngtcp2_ccerr *ccerr); + +/** + * @function + * + * `ngtcp2_ccerr_set_transport_error` sets :member:`ccerr->type + * <ngtcp2_ccerr.type>` to + * :enum:`ngtcp2_ccerr_type.NGTCP2_CCERR_TYPE_TRANSPORT`, and + * :member:`ccerr->error_code <ngtcp2_ccerr.error_code>` to + * |error_code|. |reason| is the reason phrase of length |reasonlen|. + * This function does not make a copy of the reason phrase. + */ +NGTCP2_EXTERN void ngtcp2_ccerr_set_transport_error(ngtcp2_ccerr *ccerr, + uint64_t error_code, + const uint8_t *reason, + size_t reasonlen); + +/** + * @function + * + * `ngtcp2_ccerr_set_liberr` sets type and error_code based on + * |liberr|. + * + * |reason| is the reason phrase of length |reasonlen|. This function + * does not make a copy of the reason phrase. + * + * If |liberr| is :macro:`NGTCP2_ERR_RECV_VERSION_NEGOTIATION`, + * :member:`ccerr->type <ngtcp2_ccerr.type>` is set to + * :enum:`ngtcp2_ccerr_type.NGTCP2_CCERR_TYPE_VERSION_NEGOTIATION`, + * and :member:`ccerr->error_code <ngtcp2_ccerr.error_code>` to + * :macro:`NGTCP2_NO_ERROR`. + * + * If |liberr| is :macro:`NGTCP2_ERR_IDLE_CLOSE`, :member:`ccerr->type + * <ngtcp2_ccerr.type>` is set to + * :enum:`ngtcp2_ccerr_type.NGTCP2_CCERR_TYPE_IDLE_CLOSE`, and + * :member:`ccerr->error_code <ngtcp2_ccerr.error_code>` to + * :macro:`NGTCP2_NO_ERROR`. + * + * Otherwise, :member:`ccerr->type <ngtcp2_ccerr.type>` is set to + * :enum:`ngtcp2_ccerr_type.NGTCP2_CCERR_TYPE_TRANSPORT`, and + * :member:`ccerr->error_code <ngtcp2_ccerr.error_code>` is set to an + * error code inferred by |liberr| (see + * `ngtcp2_err_infer_quic_transport_error_code`). + */ +NGTCP2_EXTERN void ngtcp2_ccerr_set_liberr(ngtcp2_ccerr *ccerr, int liberr, + const uint8_t *reason, + size_t reasonlen); + +/** + * @function + * + * `ngtcp2_ccerr_set_tls_alert` sets :member:`ccerr->type + * <ngtcp2_ccerr.type>` to + * :enum:`ngtcp2_ccerr_type.NGTCP2_CCERR_TYPE_TRANSPORT`, and + * :member:`ccerr->error_code <ngtcp2_ccerr.error_code>` to bitwise-OR + * of :macro:`NGTCP2_CRYPTO_ERROR` and |tls_alert|. |reason| is the + * reason phrase of length |reasonlen|. This function does not make a + * copy of the reason phrase. + */ +NGTCP2_EXTERN void ngtcp2_ccerr_set_tls_alert(ngtcp2_ccerr *ccerr, + uint8_t tls_alert, + const uint8_t *reason, + size_t reasonlen); + +/** + * @function + * + * `ngtcp2_ccerr_set_application_error` sets :member:`ccerr->type + * <ngtcp2_ccerr.type>` to + * :enum:`ngtcp2_ccerr_type.NGTCP2_CCERR_TYPE_APPLICATION`, and + * :member:`ccerr->error_code <ngtcp2_ccerr.error_code>` to + * |error_code|. |reason| is the reason phrase of length |reasonlen|. + * This function does not make a copy of the reason phrase. + */ +NGTCP2_EXTERN void ngtcp2_ccerr_set_application_error(ngtcp2_ccerr *ccerr, + uint64_t error_code, + const uint8_t *reason, + size_t reasonlen); + +/** + * @function + * + * `ngtcp2_conn_write_connection_close` writes a packet which contains + * 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 + * :macro:`NGTCP2_MAX_UDP_PAYLOAD_SIZE`. + * + * If |path| is not ``NULL``, this function stores the network path + * with which the packet should be sent. Each addr field must point + * to the buffer which should be at least + * sizeof(:type:`ngtcp2_sockaddr_union`) bytes long. The assignment + * might not be done if nothing is written to |dest|. + * + * If |pi| is not ``NULL``, this function stores packet metadata in it + * if it succeeds. The metadata includes ECN markings. + * + * If :member:`ccerr->type <ngtcp2_ccerr.type>` == + * :enum:`ngtcp2_ccerr_type.NGTCP2_CCERR_TYPE_TRANSPORT`, this + * function sends CONNECTION_CLOSE (type 0x1c) frame. If + * :member:`ccerr->type <ngtcp2_ccerr.type>` == + * :enum:`ngtcp2_ccerr_type.NGTCP2_CCERR_TYPE_APPLICATION`, it sends + * 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. + * + * This function must not be called from inside the callback + * functions. + * + * At the moment, successful call to this function makes connection + * close. We may change this behaviour in the future to allow + * graceful shutdown. + * + * This function returns the number of bytes written in |dest| if it + * succeeds, or one of the following negative error codes: + * + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory + * :macro:`NGTCP2_ERR_NOBUF` + * Buffer is too small + * :macro:`NGTCP2_ERR_INVALID_STATE` + * The current state does not allow sending CONNECTION_CLOSE + * frame. + * :macro:`NGTCP2_ERR_PKT_NUM_EXHAUSTED` + * Packet number is exhausted, and cannot send any more packet. + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` + * User callback failed + */ +NGTCP2_EXTERN ngtcp2_ssize ngtcp2_conn_write_connection_close_versioned( + ngtcp2_conn *conn, ngtcp2_path *path, int pkt_info_version, + ngtcp2_pkt_info *pi, uint8_t *dest, size_t destlen, + const ngtcp2_ccerr *ccerr, ngtcp2_tstamp ts); + +/** + * @function + * + * `ngtcp2_conn_get_ccerr` returns the received connection close + * error. If no connection error is received, it returns + * :type:`ngtcp2_ccerr` that is initialized by `ngtcp2_ccerr_default`. + */ +NGTCP2_EXTERN const ngtcp2_ccerr *ngtcp2_conn_get_ccerr(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_is_local_stream` returns nonzero if |stream_id| + * denotes a locally initiated stream. + */ +NGTCP2_EXTERN int ngtcp2_conn_is_local_stream(ngtcp2_conn *conn, + int64_t stream_id); + +/** + * @function + * + * `ngtcp2_conn_is_server` returns nonzero if |conn| is initialized as + * server. + */ +NGTCP2_EXTERN int ngtcp2_conn_is_server(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_after_retry` returns nonzero if |conn| as a client has + * received Retry packet from server, and successfully validated it. + */ +NGTCP2_EXTERN int ngtcp2_conn_after_retry(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_set_stream_user_data` sets |stream_user_data| to the + * stream identified by |stream_id|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_STREAM_NOT_FOUND` + * Stream does not exist + */ +NGTCP2_EXTERN int ngtcp2_conn_set_stream_user_data(ngtcp2_conn *conn, + int64_t stream_id, + void *stream_user_data); + +/** + * @function + * + * `ngtcp2_conn_update_pkt_tx_time` sets the time instant of the next + * packet transmission to pace packets. This function must be called + * after (multiple invocation of) `ngtcp2_conn_writev_stream`. If + * packet aggregation (e.g., packet batching, GSO) is used, call this + * function after all aggregated datagrams are sent, which indicates + * multiple invocation of `ngtcp2_conn_writev_stream`. + */ +NGTCP2_EXTERN void ngtcp2_conn_update_pkt_tx_time(ngtcp2_conn *conn, + ngtcp2_tstamp ts); + +/** + * @function + * + * `ngtcp2_conn_get_send_quantum` returns the maximum number of bytes + * that can be sent in one go without packet spacing. + */ +NGTCP2_EXTERN size_t ngtcp2_conn_get_send_quantum(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_get_stream_loss_count` returns the number of packets + * that contain STREAM frame for a stream identified by |stream_id| + * and are declared to be lost. The number may include the spurious + * losses. If no stream identified by |stream_id| is found, this + * function returns 0. + */ +NGTCP2_EXTERN size_t ngtcp2_conn_get_stream_loss_count(ngtcp2_conn *conn, + int64_t stream_id); + +/** + * @function + * + * `ngtcp2_strerror` returns the text representation of |liberr|. + * |liberr| must be one of ngtcp2 library error codes (which is + * defined as :macro:`NGTCP2_ERR_* <NGTCP2_ERR_INVALID_ARGUMENT>` + * macros). + */ +NGTCP2_EXTERN const char *ngtcp2_strerror(int liberr); + +/** + * @function + * + * `ngtcp2_err_is_fatal` returns nonzero if |liberr| is a fatal error. + * |liberr| must be one of ngtcp2 library error codes (which is + * defined as :macro:`NGTCP2_ERR_* <NGTCP2_ERR_INVALID_ARGUMENT>` + * macros). + */ +NGTCP2_EXTERN int ngtcp2_err_is_fatal(int liberr); + +/** + * @function + * + * `ngtcp2_err_infer_quic_transport_error_code` returns a QUIC + * transport error code which corresponds to |liberr|. |liberr| must + * be one of ngtcp2 library error codes (which is defined as + * :macro:`NGTCP2_ERR_* <NGTCP2_ERR_INVALID_ARGUMENT>` macros). + */ +NGTCP2_EXTERN uint64_t ngtcp2_err_infer_quic_transport_error_code(int liberr); + +/** + * @function + * + * `ngtcp2_addr_init` initializes |dest| with the given arguments and + * returns |dest|. + */ +NGTCP2_EXTERN ngtcp2_addr *ngtcp2_addr_init(ngtcp2_addr *dest, + const ngtcp2_sockaddr *addr, + ngtcp2_socklen addrlen); + +/** + * @function + * + * `ngtcp2_addr_copy_byte` copies |addr| of length |addrlen| into the + * buffer pointed by :member:`dest->addr <ngtcp2_addr.addr>`. + * :member:`dest->addrlen <ngtcp2_addr.addrlen>` is updated to have + * |addrlen|. This function assumes that :member:`dest->addr + * <ngtcp2_addr.addr>` points to a buffer which has a sufficient + * capacity to store the copy. + */ +NGTCP2_EXTERN void ngtcp2_addr_copy_byte(ngtcp2_addr *dest, + const ngtcp2_sockaddr *addr, + ngtcp2_socklen addrlen); + +/** + * @function + * + * `ngtcp2_path_storage_init` initializes |ps| with the given + * arguments. This function copies |local_addr| and |remote_addr|. + */ +NGTCP2_EXTERN void ngtcp2_path_storage_init(ngtcp2_path_storage *ps, + const ngtcp2_sockaddr *local_addr, + ngtcp2_socklen local_addrlen, + const ngtcp2_sockaddr *remote_addr, + ngtcp2_socklen remote_addrlen, + void *user_data); + +/** + * @function + * + * `ngtcp2_path_storage_zero` initializes |ps| with the zero length + * addresses. + */ +NGTCP2_EXTERN void ngtcp2_path_storage_zero(ngtcp2_path_storage *ps); + +/** + * @function + * + * `ngtcp2_settings_default` initializes |settings| with the default + * values. First this function fills |settings| with 0, and set the + * default value to the following fields: + * + * * :type:`cc_algo <ngtcp2_settings.cc_algo>` = + * :enum:`ngtcp2_cc_algo.NGTCP2_CC_ALGO_CUBIC` + * * :type:`initial_rtt <ngtcp2_settings.initial_rtt>` = + * :macro:`NGTCP2_DEFAULT_INITIAL_RTT` + * * :type:`ack_thresh <ngtcp2_settings.ack_thresh>` = 2 + * * :type:`max_tx_udp_payload_size + * <ngtcp2_settings.max_tx_udp_payload_size>` = 1452 + * * :type:`handshake_timeout <ngtcp2_settings.handshake_timeout>` = + * ``UINT64_MAX`` + */ +NGTCP2_EXTERN void ngtcp2_settings_default_versioned(int settings_version, + ngtcp2_settings *settings); + +/** + * @function + * + * `ngtcp2_transport_params_default` initializes |params| with the + * default values. First this function fills |params| with 0, and set + * the default value to the following fields: + * + * * :type:`max_udp_payload_size + * <ngtcp2_transport_params.max_udp_payload_size>` = + * :macro:`NGTCP2_DEFAULT_MAX_RECV_UDP_PAYLOAD_SIZE` + * * :type:`ack_delay_exponent + * <ngtcp2_transport_params.ack_delay_exponent>` = + * :macro:`NGTCP2_DEFAULT_ACK_DELAY_EXPONENT` + * * :type:`max_ack_delay <ngtcp2_transport_params.max_ack_delay>` = + * :macro:`NGTCP2_DEFAULT_MAX_ACK_DELAY` + * * :type:`active_connection_id_limit + * <ngtcp2_transport_params.active_connection_id_limit>` = + * :macro:`NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT` + */ +NGTCP2_EXTERN void +ngtcp2_transport_params_default_versioned(int transport_params_version, + ngtcp2_transport_params *params); + +/** + * @function + * + * `ngtcp2_mem_default` returns the default, system standard memory + * allocator. + */ +NGTCP2_EXTERN const ngtcp2_mem *ngtcp2_mem_default(void); + +/** + * @macrosection + * + * ngtcp2_info macros + */ + +/** + * @macro + * + * :macro:`NGTCP2_VERSION_AGE` is the age of :type:`ngtcp2_info` + */ +#define NGTCP2_VERSION_AGE 1 + +/** + * @struct + * + * :type:`ngtcp2_info` is what `ngtcp2_version` returns. It holds + * information about the particular ngtcp2 version. + */ +typedef struct ngtcp2_info { + /** + * :member:`age` is the age of this struct. This instance of ngtcp2 + * sets it to :macro:`NGTCP2_VERSION_AGE` but a future version may + * bump it and add more struct fields at the bottom + */ + int age; + /** + * :member:`version_num` is the :macro:`NGTCP2_VERSION_NUM` number + * (since :member:`age` ==1) + */ + int version_num; + /** + * :member:`version_str` points to the :macro:`NGTCP2_VERSION` + * string (since :member:`age` ==1) + */ + const char *version_str; + /* -------- the above fields all exist when age == 1 */ +} ngtcp2_info; + +/** + * @function + * + * `ngtcp2_version` returns a pointer to a :type:`ngtcp2_info` struct + * with version information about the run-time library in use. The + * |least_version| argument can be set to a 24 bit numerical value for + * the least accepted version number, and if the condition is not met, + * this function will return a ``NULL``. Pass in 0 to skip the + * version checking. + */ +NGTCP2_EXTERN const ngtcp2_info *ngtcp2_version(int least_version); + +/** + * @function + * + * `ngtcp2_is_bidi_stream` returns nonzero if |stream_id| denotes + * bidirectional stream. + */ +NGTCP2_EXTERN int ngtcp2_is_bidi_stream(int64_t stream_id); + +/** + * @function + * + * `ngtcp2_path_copy` copies |src| into |dest|. This function assumes + * that |dest| has enough buffer to store the deep copy of + * :member:`src->local <ngtcp2_path.local>` and :member:`src->remote + * <ngtcp2_path.remote>`. + */ +NGTCP2_EXTERN void ngtcp2_path_copy(ngtcp2_path *dest, const ngtcp2_path *src); + +/** + * @function + * + * `ngtcp2_path_eq` returns nonzero if |a| and |b| shares the same + * local and remote addresses. + */ +NGTCP2_EXTERN int ngtcp2_path_eq(const ngtcp2_path *a, const ngtcp2_path *b); + +/** + * @function + * + * `ngtcp2_is_supported_version` returns nonzero if the library + * supports QUIC version |version|. + */ +NGTCP2_EXTERN int ngtcp2_is_supported_version(uint32_t version); + +/** + * @function + * + * `ngtcp2_is_reserved_version` returns nonzero if |version| is a + * reserved version. + */ +NGTCP2_EXTERN int ngtcp2_is_reserved_version(uint32_t version); + +/** + * @function + * + * `ngtcp2_select_version` selects and returns a version from the + * version set |offered_versions| of |offered_versionslen| elements. + * |preferred_versions| of |preferred_versionslen| elements specifies + * the preference of versions, which is sorted in the order of + * preference. All versions included in |preferred_versions| must be + * supported by the library, that is, passing any version in the array + * to `ngtcp2_is_supported_version` must return nonzero. This + * function is intended to be used by client when it receives Version + * Negotiation packet. If no version is selected, this function + * returns 0. + */ +NGTCP2_EXTERN uint32_t ngtcp2_select_version(const uint32_t *preferred_versions, + size_t preferred_versionslen, + const uint32_t *offered_versions, + size_t offered_versionslen); + +/* + * Versioned function wrappers + */ + +/* + * `ngtcp2_conn_read_pkt` is a wrapper around + * `ngtcp2_conn_read_pkt_versioned` to set the correct struct version. + */ +#define ngtcp2_conn_read_pkt(CONN, PATH, PI, PKT, PKTLEN, TS) \ + ngtcp2_conn_read_pkt_versioned((CONN), (PATH), NGTCP2_PKT_INFO_VERSION, \ + (PI), (PKT), (PKTLEN), (TS)) + +/* + * `ngtcp2_conn_write_pkt` is a wrapper around + * `ngtcp2_conn_write_pkt_versioned` to set the correct struct + * version. + */ +#define ngtcp2_conn_write_pkt(CONN, PATH, PI, DEST, DESTLEN, TS) \ + ngtcp2_conn_write_pkt_versioned((CONN), (PATH), NGTCP2_PKT_INFO_VERSION, \ + (PI), (DEST), (DESTLEN), (TS)) + +/* + * `ngtcp2_conn_write_stream` is a wrapper around + * `ngtcp2_conn_write_stream_versioned` to set the correct struct + * version. + */ +#define ngtcp2_conn_write_stream(CONN, PATH, PI, DEST, DESTLEN, PDATALEN, \ + FLAGS, STREAM_ID, DATA, DATALEN, TS) \ + ngtcp2_conn_write_stream_versioned( \ + (CONN), (PATH), NGTCP2_PKT_INFO_VERSION, (PI), (DEST), (DESTLEN), \ + (PDATALEN), (FLAGS), (STREAM_ID), (DATA), (DATALEN), (TS)) + +/* + * `ngtcp2_conn_writev_stream` is a wrapper around + * `ngtcp2_conn_writev_stream_versioned` to set the correct struct + * version. + */ +#define ngtcp2_conn_writev_stream(CONN, PATH, PI, DEST, DESTLEN, PDATALEN, \ + FLAGS, STREAM_ID, DATAV, DATAVCNT, TS) \ + ngtcp2_conn_writev_stream_versioned( \ + (CONN), (PATH), NGTCP2_PKT_INFO_VERSION, (PI), (DEST), (DESTLEN), \ + (PDATALEN), (FLAGS), (STREAM_ID), (DATAV), (DATAVCNT), (TS)) + +/* + * `ngtcp2_conn_write_datagram` is a wrapper around + * `ngtcp2_conn_write_datagram_versioned` to set the correct struct + * version. + */ +#define ngtcp2_conn_write_datagram(CONN, PATH, PI, DEST, DESTLEN, PACCEPTED, \ + FLAGS, DGRAM_ID, DATA, DATALEN, TS) \ + ngtcp2_conn_write_datagram_versioned( \ + (CONN), (PATH), NGTCP2_PKT_INFO_VERSION, (PI), (DEST), (DESTLEN), \ + (PACCEPTED), (FLAGS), (DGRAM_ID), (DATA), (DATALEN), (TS)) + +/* + * `ngtcp2_conn_writev_datagram` is a wrapper around + * `ngtcp2_conn_writev_datagram_versioned` to set the correct struct + * version. + */ +#define ngtcp2_conn_writev_datagram(CONN, PATH, PI, DEST, DESTLEN, PACCEPTED, \ + FLAGS, DGRAM_ID, DATAV, DATAVCNT, TS) \ + ngtcp2_conn_writev_datagram_versioned( \ + (CONN), (PATH), NGTCP2_PKT_INFO_VERSION, (PI), (DEST), (DESTLEN), \ + (PACCEPTED), (FLAGS), (DGRAM_ID), (DATAV), (DATAVCNT), (TS)) + +/* + * `ngtcp2_conn_write_connection_close` is a wrapper around + * `ngtcp2_conn_write_connection_close_versioned` to set the correct + * struct version. + */ +#define ngtcp2_conn_write_connection_close(CONN, PATH, PI, DEST, DESTLEN, \ + CCERR, TS) \ + ngtcp2_conn_write_connection_close_versioned( \ + (CONN), (PATH), NGTCP2_PKT_INFO_VERSION, (PI), (DEST), (DESTLEN), \ + (CCERR), (TS)) + +/* + * `ngtcp2_transport_params_encode` is a wrapper around + * `ngtcp2_transport_params_encode_versioned` to set the correct + * struct version. + */ +#define ngtcp2_transport_params_encode(DEST, DESTLEN, PARAMS) \ + ngtcp2_transport_params_encode_versioned( \ + (DEST), (DESTLEN), NGTCP2_TRANSPORT_PARAMS_VERSION, (PARAMS)) + +/* + * `ngtcp2_transport_params_decode` is a wrapper around + * `ngtcp2_transport_params_decode_versioned` to set the correct + * struct version. + */ +#define ngtcp2_transport_params_decode(PARAMS, DATA, DATALEN) \ + ngtcp2_transport_params_decode_versioned(NGTCP2_TRANSPORT_PARAMS_VERSION, \ + (PARAMS), (DATA), (DATALEN)) + +/* + * `ngtcp2_conn_client_new` is a wrapper around + * `ngtcp2_conn_client_new_versioned` to set the correct struct + * version. + */ +#define ngtcp2_conn_client_new(PCONN, DCID, SCID, PATH, VERSION, CALLBACKS, \ + SETTINGS, PARAMS, MEM, USER_DATA) \ + ngtcp2_conn_client_new_versioned( \ + (PCONN), (DCID), (SCID), (PATH), (VERSION), NGTCP2_CALLBACKS_VERSION, \ + (CALLBACKS), NGTCP2_SETTINGS_VERSION, (SETTINGS), \ + NGTCP2_TRANSPORT_PARAMS_VERSION, (PARAMS), (MEM), (USER_DATA)) + +/* + * `ngtcp2_conn_server_new` is a wrapper around + * `ngtcp2_conn_server_new_versioned` to set the correct struct + * version. + */ +#define ngtcp2_conn_server_new(PCONN, DCID, SCID, PATH, VERSION, CALLBACKS, \ + SETTINGS, PARAMS, MEM, USER_DATA) \ + ngtcp2_conn_server_new_versioned( \ + (PCONN), (DCID), (SCID), (PATH), (VERSION), NGTCP2_CALLBACKS_VERSION, \ + (CALLBACKS), NGTCP2_SETTINGS_VERSION, (SETTINGS), \ + NGTCP2_TRANSPORT_PARAMS_VERSION, (PARAMS), (MEM), (USER_DATA)) + +/* + * `ngtcp2_conn_set_local_transport_params` is a wrapper around + * `ngtcp2_conn_set_local_transport_params_versioned` to set the + * correct struct version. + */ +#define ngtcp2_conn_set_local_transport_params(CONN, PARAMS) \ + ngtcp2_conn_set_local_transport_params_versioned( \ + (CONN), NGTCP2_TRANSPORT_PARAMS_VERSION, (PARAMS)) + +/* + * `ngtcp2_transport_params_default` is a wrapper around + * `ngtcp2_transport_params_default_versioned` to set the correct + * struct version. + */ +#define ngtcp2_transport_params_default(PARAMS) \ + ngtcp2_transport_params_default_versioned(NGTCP2_TRANSPORT_PARAMS_VERSION, \ + (PARAMS)) + +/* + * `ngtcp2_conn_get_conn_info` is a wrapper around + * `ngtcp2_conn_get_conn_info_versioned` to set the correct struct + * version. + */ +#define ngtcp2_conn_get_conn_info(CONN, CINFO) \ + ngtcp2_conn_get_conn_info_versioned((CONN), NGTCP2_CONN_INFO_VERSION, (CINFO)) + +/* + * `ngtcp2_settings_default` is a wrapper around + * `ngtcp2_settings_default_versioned` to set the correct struct + * version. + */ +#define ngtcp2_settings_default(SETTINGS) \ + ngtcp2_settings_default_versioned(NGTCP2_SETTINGS_VERSION, (SETTINGS)) + +#ifdef _MSC_VER +# pragma warning(pop) +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* NGTCP2_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/ngtcp2_crypto.h b/src/contrib/libngtcp2/ngtcp2/ngtcp2_crypto.h new file mode 100644 index 0000000..06427d7 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/ngtcp2_crypto.h @@ -0,0 +1,841 @@ +/* + * 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. + */ +#ifndef NGTCP2_CRYPTO_H +#define NGTCP2_CRYPTO_H + +#include <ngtcp2/ngtcp2.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef WIN32 +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif +# include <ws2tcpip.h> +#endif /* WIN32 */ + +/** + * @function + * + * `ngtcp2_crypto_ctx_tls` initializes |ctx| by extracting negotiated + * ciphers and message digests from native TLS session + * |tls_native_handle|. This is used for encrypting/decrypting + * Handshake and 1-RTT packets. If it is unable to obtain necessary + * data from |tls_native_handle|, this function returns NULL. + * + * If libngtcp2_crypto_quictls is linked, |tls_native_handle| must be + * a pointer to SSL object. + */ +NGTCP2_EXTERN ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_tls(ngtcp2_crypto_ctx *ctx, + void *tls_native_handle); + +/** + * @function + * + * `ngtcp2_crypto_ctx_tls_early` initializes |ctx| by extracting early + * ciphers and message digests from native TLS session + * |tls_native_handle|. This is used for encrypting/decrypting 0-RTT + * packets. If it is unable to obtain necessary data from + * |tls_native_handle|, this function returns NULL. + * + * If libngtcp2_crypto_quictls is linked, |tls_native_handle| must be + * a pointer to SSL object. + */ +NGTCP2_EXTERN ngtcp2_crypto_ctx * +ngtcp2_crypto_ctx_tls_early(ngtcp2_crypto_ctx *ctx, void *tls_native_handle); + +/** + * @function + * + * `ngtcp2_crypto_md_init` initializes |md| with the provided + * |md_native_handle| which is an underlying message digest object. + * + * If libngtcp2_crypto_quictls is linked, |md_native_handle| must be a + * pointer to EVP_MD. + * + * If libngtcp2_crypto_gnutls is linked, |md_native_handle| must be + * gnutls_mac_algorithm_t casted to ``void *``. + * + * If libngtcp2_crypto_boringssl is linked, |md_native_handle| must be + * a pointer to EVP_MD. + */ +NGTCP2_EXTERN ngtcp2_crypto_md *ngtcp2_crypto_md_init(ngtcp2_crypto_md *md, + void *md_native_handle); + +/** + * @function + * + * `ngtcp2_crypto_md_hashlen` returns the length of |md| output. + */ +NGTCP2_EXTERN size_t ngtcp2_crypto_md_hashlen(const ngtcp2_crypto_md *md); + +/** + * @function + * + * `ngtcp2_crypto_aead_keylen` returns the length of key for |aead|. + */ +NGTCP2_EXTERN size_t ngtcp2_crypto_aead_keylen(const ngtcp2_crypto_aead *aead); + +/** + * @function + * + * `ngtcp2_crypto_aead_noncelen` returns the length of nonce for + * |aead|. + */ +NGTCP2_EXTERN size_t +ngtcp2_crypto_aead_noncelen(const ngtcp2_crypto_aead *aead); + +/** + * @function + * + * `ngtcp2_crypto_hkdf_extract` performs HKDF extract operation. + * + * The length of output is `ngtcp2_crypto_md_hashlen(md) + * <ngtcp2_crypto_md_hashlen>`. The output is stored in the buffer + * pointed by |dest|. The caller is responsible to specify the buffer + * that has enough capacity to store the output. + * + * This function returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN int +ngtcp2_crypto_hkdf_extract(uint8_t *dest, const ngtcp2_crypto_md *md, + const uint8_t *secret, size_t secretlen, + const uint8_t *salt, size_t saltlen); + +/** + * @function + * + * `ngtcp2_crypto_hkdf_expand` performs HKDF expand operation. The + * result is |destlen| bytes long, and is stored in the buffer pointed + * by |dest|. + * + * This function returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN int ngtcp2_crypto_hkdf_expand(uint8_t *dest, size_t destlen, + const ngtcp2_crypto_md *md, + const uint8_t *secret, + size_t secretlen, + const uint8_t *info, + size_t infolen); + +/** + * @function + * + * `ngtcp2_crypto_hkdf` performs HKDF operation. The result is + * |destlen| bytes long, and is stored in the buffer pointed by + * |dest|. + * + * This function returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN int ngtcp2_crypto_hkdf(uint8_t *dest, size_t destlen, + const ngtcp2_crypto_md *md, + const uint8_t *secret, size_t secretlen, + const uint8_t *salt, size_t saltlen, + const uint8_t *info, size_t infolen); + +/** + * @function + * + * `ngtcp2_crypto_packet_protection_ivlen` returns the length of IV + * used to encrypt QUIC packet. + */ +NGTCP2_EXTERN size_t +ngtcp2_crypto_packet_protection_ivlen(const ngtcp2_crypto_aead *aead); + +/** + * @function + * + * `ngtcp2_crypto_encrypt` encrypts |plaintext| of length + * |plaintextlen| and writes the ciphertext into the buffer pointed by + * |dest|. The length of ciphertext is |plaintextlen| + + * :member:`aead->max_overhead <ngtcp2_crypto_aead.max_overhead>` + * bytes long. |dest| must have enough capacity to store the + * ciphertext. |dest| and |plaintext| may point to the same buffer. + * + * This function returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN int ngtcp2_crypto_encrypt(uint8_t *dest, + const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *plaintext, + size_t plaintextlen, + const uint8_t *nonce, size_t noncelen, + const uint8_t *aad, size_t aadlen); + +/** + * @function + * + * `ngtcp2_crypto_encrypt_cb` is a wrapper function around + * `ngtcp2_crypto_encrypt`. It can be directly passed to + * :member:`ngtcp2_callbacks.encrypt` field. + * + * This function returns 0 if it succeeds, or + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE`. + */ +NGTCP2_EXTERN int +ngtcp2_crypto_encrypt_cb(uint8_t *dest, const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *plaintext, size_t plaintextlen, + const uint8_t *nonce, size_t noncelen, + const uint8_t *aad, size_t aadlen); + +/** + * @function + * + * `ngtcp2_crypto_decrypt` decrypts |ciphertext| of length + * |ciphertextlen| and writes the plaintext into the buffer pointed by + * |dest|. The length of plaintext is |ciphertextlen| - + * :member:`aead->max_overhead <ngtcp2_crypto_aead.max_overhead>` + * bytes long. |dest| must have enough capacity to store the + * plaintext. |dest| and |ciphertext| may point to the same buffer. + * + * This function returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN int ngtcp2_crypto_decrypt(uint8_t *dest, + const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *ciphertext, + size_t ciphertextlen, + const uint8_t *nonce, size_t noncelen, + const uint8_t *aad, size_t aadlen); + +/** + * @function + * + * `ngtcp2_crypto_decrypt_cb` is a wrapper function around + * `ngtcp2_crypto_decrypt`. It can be directly passed to + * :member:`ngtcp2_callbacks.decrypt` field. + * + * This function returns 0 if it succeeds, or + * :macro:`NGTCP2_ERR_TLS_DECRYPT`. + */ +NGTCP2_EXTERN int +ngtcp2_crypto_decrypt_cb(uint8_t *dest, const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *ciphertext, size_t ciphertextlen, + const uint8_t *nonce, size_t noncelen, + const uint8_t *aad, size_t aadlen); + +/** + * @function + * + * `ngtcp2_crypto_hp_mask` generates a mask which is used in packet + * header encryption. The mask is written to the buffer pointed by + * |dest|. The sample is passed as |sample| which is + * :macro:`NGTCP2_HP_SAMPLELEN` bytes long. The length of mask must + * be at least :macro:`NGTCP2_HP_MASKLEN`. The library only uses the + * first :macro:`NGTCP2_HP_MASKLEN` bytes of the produced mask. The + * buffer pointed by |dest| must have at least + * :macro:`NGTCP2_HP_SAMPLELEN` bytes available. + * + * This function returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN int ngtcp2_crypto_hp_mask(uint8_t *dest, + const ngtcp2_crypto_cipher *hp, + const ngtcp2_crypto_cipher_ctx *hp_ctx, + const uint8_t *sample); + +/** + * @function + * + * `ngtcp2_crypto_hp_mask_cb` is a wrapper function around + * `ngtcp2_crypto_hp_mask`. It can be directly passed to + * :member:`ngtcp2_callbacks.hp_mask` field. + * + * This function returns 0 if it succeeds, or + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE`. + */ +NGTCP2_EXTERN int +ngtcp2_crypto_hp_mask_cb(uint8_t *dest, const ngtcp2_crypto_cipher *hp, + const ngtcp2_crypto_cipher_ctx *hp_ctx, + const uint8_t *sample); + +/** + * @function + * + * `ngtcp2_crypto_derive_and_install_rx_key` derives the decryption + * keying materials from |secret|, and installs them to |conn|. + * + * If |key| is not NULL, the derived packet protection key is written + * to the buffer pointed by |key|. If |iv| is not NULL, the derived + * packet protection IV is written to the buffer pointed by |iv|. If + * |hp| is not NULL, the derived header protection key is written to + * the buffer pointed by |hp|. + * + * |secretlen| specifies the length of |secret|. + * + * The length of packet protection key and header protection key is + * `ngtcp2_crypto_aead_keylen(ctx->aead) <ngtcp2_crypto_aead_keylen>`, + * and the length of packet protection IV is + * `ngtcp2_crypto_packet_protection_ivlen(ctx->aead) + * <ngtcp2_crypto_packet_protection_ivlen>` where ctx is obtained by + * `ngtcp2_crypto_ctx_tls` (or `ngtcp2_crypto_ctx_tls_early` if + * |level| == + * :enum:`ngtcp2_encryption_level.NGTCP2_ENCRYPTION_LEVEL_0RTT`). + * + * In the first call of this function, it calls + * `ngtcp2_conn_set_crypto_ctx` (or `ngtcp2_conn_set_early_crypto_ctx` + * if |level| == + * :enum:`ngtcp2_encryption_level.NGTCP2_ENCRYPTION_LEVEL_0RTT`) to + * set negotiated AEAD and message digest algorithm. After the + * successful call of this function, application can use + * `ngtcp2_conn_get_crypto_ctx` (or `ngtcp2_conn_get_early_crypto_ctx` + * if |level| == + * :enum:`ngtcp2_encryption_level.NGTCP2_ENCRYPTION_LEVEL_0RTT`) to + * get :type:`ngtcp2_crypto_ctx`. + * + * If |conn| is initialized as client, and |level| is + * :enum:`ngtcp2_encryption_level.NGTCP2_ENCRYPTION_LEVEL_1RTT`, this + * function retrieves a remote QUIC transport parameters extension + * from an object obtained by `ngtcp2_conn_get_tls_native_handle`, and + * sets it to |conn| by calling + * `ngtcp2_conn_decode_and_set_remote_transport_params`. + * + * This function returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN int ngtcp2_crypto_derive_and_install_rx_key( + ngtcp2_conn *conn, uint8_t *key, uint8_t *iv, uint8_t *hp, + ngtcp2_encryption_level level, const uint8_t *secret, size_t secretlen); + +/** + * @function + * + * `ngtcp2_crypto_derive_and_install_tx_key` derives the encryption + * keying materials from |secret|, and installs new keys to |conn|. + * + * If |key| is not NULL, the derived packet protection key is written + * to the buffer pointed by |key|. If |iv| is not NULL, the derived + * packet protection IV is written to the buffer pointed by |iv|. If + * |hp| is not NULL, the derived header protection key is written to + * the buffer pointed by |hp|. + * + * |secretlen| specifies the length of |secret|. + * + * The length of packet protection key and header protection key is + * `ngtcp2_crypto_aead_keylen(ctx->aead) <ngtcp2_crypto_aead_keylen>`, + * and the length of packet protection IV is + * `ngtcp2_crypto_packet_protection_ivlen(ctx->aead) + * <ngtcp2_crypto_packet_protection_ivlen>` where ctx is obtained by + * `ngtcp2_crypto_ctx_tls` (or `ngtcp2_crypto_ctx_tls_early` if + * |level| == + * :enum:`ngtcp2_encryption_level.NGTCP2_ENCRYPTION_LEVEL_0RTT`). + * + * In the first call of this function, it calls + * `ngtcp2_conn_set_crypto_ctx` (or `ngtcp2_conn_set_early_crypto_ctx` + * if |level| == + * :enum:`ngtcp2_encryption_level.NGTCP2_ENCRYPTION_LEVEL_0RTT`) to + * set negotiated AEAD and message digest algorithm. After the + * successful call of this function, application can use + * `ngtcp2_conn_get_crypto_ctx` (or `ngtcp2_conn_get_early_crypto_ctx` + * if |level| == + * :enum:`ngtcp2_encryption_level.NGTCP2_ENCRYPTION_LEVEL_0RTT`) to + * get :type:`ngtcp2_crypto_ctx`. + * + * If |conn| is initialized as server, and |level| is + * :enum:`ngtcp2_encryption_level.NGTCP2_ENCRYPTION_LEVEL_1RTT`, this + * function retrieves a remote QUIC transport parameters extension + * from an object obtained by `ngtcp2_conn_get_tls_native_handle`, and + * sets it to |conn| by calling + * `ngtcp2_conn_decode_and_set_remote_transport_params`. + * + * This function returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN int ngtcp2_crypto_derive_and_install_tx_key( + ngtcp2_conn *conn, uint8_t *key, uint8_t *iv, uint8_t *hp, + ngtcp2_encryption_level level, const uint8_t *secret, size_t secretlen); + +/** + * @function + * + * `ngtcp2_crypto_update_key` updates traffic keying materials. + * + * The new decryption traffic secret is written to the buffer pointed + * by |rx_secret|. The length of secret is |secretlen| bytes, and + * |rx_secret| must point to the buffer which has enough capacity. + * + * The new encryption traffic secret is written to the buffer pointed + * by |tx_secret|. The length of secret is |secretlen| bytes, and + * |tx_secret| must point to the buffer which has enough capacity. + * + * The derived decryption packet protection key is written to the + * buffer pointed by |rx_key|. The derived decryption packet + * protection IV is written to the buffer pointed by |rx_iv|. + * |rx_aead_ctx| is initialized with the derived key and IV. + * + * The derived encryption packet protection key is written to the + * buffer pointed by |tx_key|. The derived encryption packet + * protection IV is written to the buffer pointed by |tx_iv|. + * |tx_aead_ctx| is initialized with the derived key and IV. + * + * |current_rx_secret| and |current_tx_secret| are the current + * decryption and encryption traffic secrets respectively. They share + * the same length with |rx_secret| and |tx_secret|. + * + * The length of packet protection key and header protection key is + * `ngtcp2_crypto_aead_keylen(ctx->aead) <ngtcp2_crypto_aead_keylen>`, + * and the length of packet protection IV is + * `ngtcp2_crypto_packet_protection_ivlen(ctx->aead) + * <ngtcp2_crypto_packet_protection_ivlen>` where ctx is obtained by + * `ngtcp2_crypto_ctx_tls`. + * + * This function returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN int ngtcp2_crypto_update_key( + ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret, + ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_key, uint8_t *rx_iv, + ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_key, uint8_t *tx_iv, + const uint8_t *current_rx_secret, const uint8_t *current_tx_secret, + size_t secretlen); + +/** + * @function + * + * `ngtcp2_crypto_update_key_cb` is a wrapper function around + * `ngtcp2_crypto_update_key`. It can be directly passed to + * :member:`ngtcp2_callbacks.update_key` field. + * + * This function returns 0 if it succeeds, or + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE`. + */ +NGTCP2_EXTERN int ngtcp2_crypto_update_key_cb( + ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret, + ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv, + ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv, + const uint8_t *current_rx_secret, const uint8_t *current_tx_secret, + size_t secretlen, void *user_data); + +/** + * @function + * + * `ngtcp2_crypto_client_initial_cb` installs initial secrets and + * encryption keys, and sets QUIC transport parameters. + * + * This function can be directly passed to + * :member:`ngtcp2_callbacks.client_initial` field. It is only used + * by client. + * + * This function returns 0 if it succeeds, or + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE`. + */ +NGTCP2_EXTERN int ngtcp2_crypto_client_initial_cb(ngtcp2_conn *conn, + void *user_data); + +/** + * @function + * + * `ngtcp2_crypto_recv_retry_cb` re-installs initial secrets in + * response to incoming Retry packet. + * + * This function can be directly passed to + * :member:`ngtcp2_callbacks.recv_retry` field. It is only used by + * client. + * + * This function returns 0 if it succeeds, or + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE`. + */ +NGTCP2_EXTERN int ngtcp2_crypto_recv_retry_cb(ngtcp2_conn *conn, + const ngtcp2_pkt_hd *hd, + void *user_data); + +/** + * @function + * + * `ngtcp2_crypto_recv_client_initial_cb` installs initial secrets in + * response to an incoming Initial packet from client, and sets QUIC + * transport parameters. + * + * This function can be directly passed to + * :member:`ngtcp2_callbacks.recv_client_initial` field. It is only + * used by server. + * + * This function returns 0 if it succeeds, or + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE`. + */ +NGTCP2_EXTERN int ngtcp2_crypto_recv_client_initial_cb(ngtcp2_conn *conn, + const ngtcp2_cid *dcid, + void *user_data); + +/** + * @function + * + * `ngtcp2_crypto_read_write_crypto_data` reads CRYPTO data |data| of + * length |datalen| in an encryption level |encryption_level|, and may + * feed outgoing CRYPTO data to |conn|. This function can drive + * handshake. This function can be also used after handshake + * completes. It is allowed to call this function with |datalen| == + * 0. In this case, no additional read operation is done. + * + * 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*. + */ +NGTCP2_EXTERN int +ngtcp2_crypto_read_write_crypto_data(ngtcp2_conn *conn, + ngtcp2_encryption_level encryption_level, + const uint8_t *data, size_t datalen); + +/** + * @function + * + * `ngtcp2_crypto_recv_crypto_data_cb` is a wrapper function around + * `ngtcp2_crypto_read_write_crypto_data`. It can be directly passed + * to :member:`ngtcp2_callbacks.recv_crypto_data` field. + * + * 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. + */ +NGTCP2_EXTERN int ngtcp2_crypto_recv_crypto_data_cb( + ngtcp2_conn *conn, ngtcp2_encryption_level encryption_level, + uint64_t offset, const uint8_t *data, size_t datalen, void *user_data); + +/** + * @function + * + * `ngtcp2_crypto_generate_stateless_reset_token` generates a + * stateless reset token using HKDF extraction using the given |cid| + * and |secret| as input. The token will be written to the buffer + * pointed by |token|, and it must have a capacity of at least + * :macro:`NGTCP2_STATELESS_RESET_TOKENLEN` bytes. + * + * This function returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN int ngtcp2_crypto_generate_stateless_reset_token( + uint8_t *token, const uint8_t *secret, size_t secretlen, + const ngtcp2_cid *cid); + +/** + * @macro + * + * :macro:`NGTCP2_CRYPTO_TOKEN_RAND_DATALEN` is the length of random + * data added to a token generated by + * `ngtcp2_crypto_generate_retry_token` or + * `ngtcp2_crypto_generate_regular_token`. + */ +#define NGTCP2_CRYPTO_TOKEN_RAND_DATALEN 32 + +/** + * @macro + * + * :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 + +/** + * @macro + * + * :macro:`NGTCP2_CRYPTO_TOKEN_MAGIC_REGULAR` is the magic byte for a + * token generated by `ngtcp2_crypto_generate_regular_token`. + */ +#define NGTCP2_CRYPTO_TOKEN_MAGIC_REGULAR 0x36 + +/** + * @macro + * + * :macro:`NGTCP2_CRYPTO_MAX_RETRY_TOKENLEN` is the maximum length of + * a token generated by `ngtcp2_crypto_generate_retry_token`. + */ +#define NGTCP2_CRYPTO_MAX_RETRY_TOKENLEN \ + (/* magic = */ 1 + /* cid len = */ 1 + NGTCP2_MAX_CIDLEN + \ + sizeof(ngtcp2_tstamp) + /* aead tag = */ 16 + \ + NGTCP2_CRYPTO_TOKEN_RAND_DATALEN) + +/** + * @macro + * + * :macro:`NGTCP2_CRYPTO_MAX_REGULAR_TOKENLEN` is the maximum length + * of a token generated by `ngtcp2_crypto_generate_regular_token`. + */ +#define NGTCP2_CRYPTO_MAX_REGULAR_TOKENLEN \ + (/* magic = */ 1 + sizeof(ngtcp2_tstamp) + /* aead tag = */ 16 + \ + NGTCP2_CRYPTO_TOKEN_RAND_DATALEN) + +/** + * @function + * + * `ngtcp2_crypto_generate_retry_token` generates a token in the + * buffer pointed by |token| that is sent with Retry packet. The + * buffer pointed by |token| must have at least + * :macro:`NGTCP2_CRYPTO_MAX_RETRY_TOKENLEN` bytes long. The + * successfully generated token starts with + * :macro:`NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY`. |secret| of length + * |secretlen| is a keying material to generate keys to encrypt the + * token. |version| is QUIC version. |remote_addr| of length + * |remote_addrlen| is an address of client. |retry_scid| is a Source + * Connection ID chosen by server, and set in Retry packet. |odcid| + * is a Destination Connection ID in Initial packet sent by client. + * |ts| is the timestamp when the token is generated. + * + * This function returns the length of generated token if it succeeds, + * or -1. + */ +NGTCP2_EXTERN 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); + +/** + * @function + * + * `ngtcp2_crypto_verify_retry_token` verifies Retry 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. |version| is QUIC version of the Initial packet that + * contains this token. |remote_addr| of length |remote_addrlen| is + * an address of client. |dcid| is a Destination Connection ID in + * Initial packet sent by client. |timeout| is the period during + * which the token is valid. |ts| is the current timestamp. When + * validation succeeds, the extracted Destination Connection ID (which + * is the Destination Connection ID in Initial packet sent by client + * that triggered Retry packet) is stored in the buffer pointed by + * |odcid|. + * + * This function returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN int ngtcp2_crypto_verify_retry_token( + ngtcp2_cid *odcid, const uint8_t *token, size_t tokenlen, + const uint8_t *secret, size_t secretlen, uint32_t version, + const ngtcp2_sockaddr *remote_addr, ngtcp2_socklen remote_addrlen, + const ngtcp2_cid *dcid, ngtcp2_duration timeout, ngtcp2_tstamp ts); + +/** + * @function + * + * `ngtcp2_crypto_generate_regular_token` 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` 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. + * + * This function returns the length of generated token if it succeeds, + * or -1. + */ +NGTCP2_EXTERN 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); + +/** + * @function + * + * `ngtcp2_crypto_verify_regular_token` 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. + * + * This function returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN 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); + +/** + * @function + * + * `ngtcp2_crypto_write_connection_close` writes Initial packet + * containing CONNECTION_CLOSE with the given |error_code| and the + * optional |reason| of length |reasonlen| to the buffer pointed by + * |dest| of length |destlen|. This function is designed for server + * to close connection without committing the state when validating + * Retry token fails. This function must not be used by client. The + * |dcid| must be the Source Connection ID in Initial packet from + * client. The |scid| must be the Destination Connection ID in + * Initial packet from client. |scid| is used to derive initial + * keying materials. + * + * This function wraps around `ngtcp2_pkt_write_connection_close` for + * easier use. + * + * This function returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN 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, + size_t reasonlen); + +/** + * @function + * + * `ngtcp2_crypto_write_retry` writes Retry packet to the buffer + * pointed by |dest| of length |destlen|. |dcid| is the Connection ID + * which appeared in a packet as a Source Connection ID sent by + * client. |scid| is a server chosen Source Connection ID. |odcid| + * specifies Original Destination Connection ID which appeared in a + * packet as a Destination Connection ID sent by client. |token| + * specifies Retry Token, and |tokenlen| specifies its length. + * + * This function wraps around `ngtcp2_pkt_write_retry` for easier use. + * + * This function returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN ngtcp2_ssize ngtcp2_crypto_write_retry( + uint8_t *dest, size_t destlen, uint32_t version, const ngtcp2_cid *dcid, + const ngtcp2_cid *scid, const ngtcp2_cid *odcid, const uint8_t *token, + size_t tokenlen); + +/** + * @function + * + * `ngtcp2_crypto_aead_ctx_encrypt_init` initializes |aead_ctx| with + * new AEAD cipher context object for encryption which is constructed + * to use |key| as encryption key. |aead| specifies AEAD cipher to + * use. |noncelen| is the length of nonce. + * + * This function returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN int +ngtcp2_crypto_aead_ctx_encrypt_init(ngtcp2_crypto_aead_ctx *aead_ctx, + const ngtcp2_crypto_aead *aead, + const uint8_t *key, size_t noncelen); + +/** + * @function + * + * `ngtcp2_crypto_aead_ctx_decrypt_init` initializes |aead_ctx| with + * new AEAD cipher context object for decryption which is constructed + * to use |key| as decryption key. |aead| specifies AEAD cipher to + * use. |noncelen| is the length of nonce. + * + * This function returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN int +ngtcp2_crypto_aead_ctx_decrypt_init(ngtcp2_crypto_aead_ctx *aead_ctx, + const ngtcp2_crypto_aead *aead, + const uint8_t *key, size_t noncelen); + +/** + * @function + * + * `ngtcp2_crypto_aead_ctx_free` frees up resources used by + * |aead_ctx|. This function does not free the memory pointed by + * |aead_ctx| itself. + */ +NGTCP2_EXTERN void +ngtcp2_crypto_aead_ctx_free(ngtcp2_crypto_aead_ctx *aead_ctx); + +/** + * @function + * + * `ngtcp2_crypto_delete_crypto_aead_ctx_cb` deletes the given + * |aead_ctx|. + * + * This function can be directly passed to + * :member:`ngtcp2_callbacks.delete_crypto_aead_ctx` field. + */ +NGTCP2_EXTERN void ngtcp2_crypto_delete_crypto_aead_ctx_cb( + ngtcp2_conn *conn, ngtcp2_crypto_aead_ctx *aead_ctx, void *user_data); + +/** + * @function + * + * `ngtcp2_crypto_delete_crypto_cipher_ctx_cb` deletes the given + * |cipher_ctx|. + * + * This function can be directly passed to + * :member:`ngtcp2_callbacks.delete_crypto_cipher_ctx` field. + */ +NGTCP2_EXTERN void ngtcp2_crypto_delete_crypto_cipher_ctx_cb( + ngtcp2_conn *conn, ngtcp2_crypto_cipher_ctx *cipher_ctx, void *user_data); + +/** + * @function + * + * `ngtcp2_crypto_get_path_challenge_data_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_data` field. + */ +NGTCP2_EXTERN int ngtcp2_crypto_get_path_challenge_data_cb(ngtcp2_conn *conn, + uint8_t *data, + void *user_data); + +/** + * @function + * + * `ngtcp2_crypto_version_negotiation_cb` installs Initial keys for + * |version| which is negotiated or being negotiated. |client_dcid| + * is the destination connection ID in first Initial packet from + * client. + * + * This function can be directly passed to + * :member:`ngtcp2_callbacks.version_negotiation` field. + */ +NGTCP2_EXTERN int +ngtcp2_crypto_version_negotiation_cb(ngtcp2_conn *conn, uint32_t version, + const ngtcp2_cid *client_dcid, + void *user_data); + +typedef struct ngtcp2_crypto_conn_ref ngtcp2_crypto_conn_ref; + +/** + * @functypedef + * + * :type:`ngtcp2_crypto_get_conn` is a callback function to get a + * pointer to :type:`ngtcp2_conn` from |conn_ref|. The implementation + * must return non-NULL :type:`ngtcp2_conn` object. + */ +typedef ngtcp2_conn *(*ngtcp2_crypto_get_conn)( + ngtcp2_crypto_conn_ref *conn_ref); + +/** + * @struct + * + * :type:`ngtcp2_crypto_conn_ref` is a structure to get a pointer to + * :type:`ngtcp2_conn`. It is meant to be set to TLS native handle as + * an application specific data (e.g. SSL_set_app_data in quictls). + */ +typedef struct ngtcp2_crypto_conn_ref { + /** + * :member:`get_conn` is a callback function to get a pointer to + * :type:`ngtcp2_conn` object. + */ + ngtcp2_crypto_get_conn get_conn; + /** + * :member:`user_data` is a pointer to arbitrary user data. + */ + void *user_data; +} ngtcp2_crypto_conn_ref; + +#ifdef __cplusplus +} +#endif + +#endif /* NGTCP2_CRYPTO_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/ngtcp2_crypto_gnutls.h b/src/contrib/libngtcp2/ngtcp2/ngtcp2_crypto_gnutls.h new file mode 100644 index 0000000..5d4b9d9 --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/ngtcp2_crypto_gnutls.h @@ -0,0 +1,108 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 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_CRYPTO_GNUTLS_H +#define NGTCP2_CRYPTO_GNUTLS_H + +#include <ngtcp2/ngtcp2.h> + +#include <gnutls/gnutls.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @function + * + * `ngtcp2_crypto_gnutls_from_gnutls_record_encryption_level` + * translates |gtls_level| to :type:`ngtcp2_encryption_level`. This + * function is only available for GnuTLS backend. + */ +NGTCP2_EXTERN ngtcp2_encryption_level +ngtcp2_crypto_gnutls_from_gnutls_record_encryption_level( + gnutls_record_encryption_level_t gtls_level); + +/** + * @function + * + * `ngtcp2_crypto_gnutls_from_ngtcp2_encryption_level` translates + * |encryption_level| to gnutls_record_encryption_level_t. This + * function is only available for GnuTLS backend. + */ +NGTCP2_EXTERN gnutls_record_encryption_level_t +ngtcp2_crypto_gnutls_from_ngtcp2_encryption_level( + ngtcp2_encryption_level encryption_level); + +/** + * @function + * + * `ngtcp2_crypto_gnutls_configure_server_session` configures + * |session| for server side QUIC connection. It performs the + * following modifications: + * + * - Set gnutls_handshake_set_secret_function. + * - Set gnutls_handshake_set_read_function. + * - Set gnutls_alert_set_read_function. + * - Register a TLS extension handler for QUIC Transport Parameters. + * + * Application must set a pointer to :type:`ngtcp2_crypto_conn_ref` to + * gnutls_session_t object by calling gnutls_session_set_ptr, and + * :type:`ngtcp2_crypto_conn_ref` object must have + * :member:`ngtcp2_crypto_conn_ref.get_conn` field assigned to get + * :type:`ngtcp2_conn`. + * + * It returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN int +ngtcp2_crypto_gnutls_configure_server_session(gnutls_session_t session); + +/** + * @function + * + * `ngtcp2_crypto_gnutls_configure_client_session` configures + * |session| for client side QUIC connection. It performs the + * following modifications: + * + * - Set gnutls_handshake_set_secret_function. + * - Set gnutls_handshake_set_read_function. + * - Set gnutls_alert_set_read_function. + * - Register a TLS extension handler for QUIC Transport Parameters. + * + * Application must set a pointer to :type:`ngtcp2_crypto_conn_ref` to + * gnutls_session_t object by calling gnutls_session_set_ptr, and + * :type:`ngtcp2_crypto_conn_ref` object must have + * :member:`ngtcp2_crypto_conn_ref.get_conn` field assigned to get + * :type:`ngtcp2_conn`. + * + * It returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN int +ngtcp2_crypto_gnutls_configure_client_session(gnutls_session_t session); + +#ifdef __cplusplus +} +#endif + +#endif /* NGTCP2_CRYPTO_GNUTLS_H */ diff --git a/src/contrib/libngtcp2/ngtcp2/version.h b/src/contrib/libngtcp2/ngtcp2/version.h new file mode 100644 index 0000000..b102eae --- /dev/null +++ b/src/contrib/libngtcp2/ngtcp2/version.h @@ -0,0 +1,51 @@ +/* + * ngtcp2 + * + * Copyright (c) 2016 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 VERSION_H +#define VERSION_H + +/** + * @macrosection + * + * Library version macros + */ + +/** + * @macro + * + * Version number of the ngtcp2 library release. + */ +#define NGTCP2_VERSION "1.2.0" + +/** + * @macro + * + * Numerical representation of the version number of the ngtcp2 + * library release. This is a 24 bit number with 8 bits for major + * number, 8 bits for minor and 8 bits for patch. Version 1.2.3 + * becomes 0x010203. + */ +#define NGTCP2_VERSION_NUM 0x010200 + +#endif /* VERSION_H */ diff --git a/src/contrib/licenses/0BSD b/src/contrib/licenses/0BSD new file mode 100644 index 0000000..56c5528 --- /dev/null +++ b/src/contrib/licenses/0BSD @@ -0,0 +1,12 @@ +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + diff --git a/src/contrib/licenses/BSD-3-Clause b/src/contrib/licenses/BSD-3-Clause new file mode 100644 index 0000000..8041f21 --- /dev/null +++ b/src/contrib/licenses/BSD-3-Clause @@ -0,0 +1,21 @@ +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/contrib/licenses/LGPL-2.0 b/src/contrib/licenses/LGPL-2.0 new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/src/contrib/licenses/LGPL-2.0 @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/src/contrib/licenses/MIT b/src/contrib/licenses/MIT new file mode 100644 index 0000000..fb122a0 --- /dev/null +++ b/src/contrib/licenses/MIT @@ -0,0 +1,19 @@ +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. diff --git a/src/contrib/macros.h b/src/contrib/macros.h new file mode 100644 index 0000000..78df47e --- /dev/null +++ b/src/contrib/macros.h @@ -0,0 +1,41 @@ +/* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +/*! + * \brief Common macros. + */ + +#pragma once + +#ifndef MIN +/*! \brief Type-safe minimum macro. */ +#define MIN(a, b) \ + ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a < _b ? _a : _b; }) + +/*! \brief Type-safe maximum macro. */ +#define MAX(a, b) \ + ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a > _b ? _a : _b; }) +#endif + +#ifndef likely +/*! \brief Optimize for x to be true value. */ +#define likely(x) __builtin_expect((x), 1) +#endif + +#ifndef unlikely +/*! \brief Optimize for x to be false value. */ +#define unlikely(x) __builtin_expect((x), 0) +#endif diff --git a/src/contrib/mempattern.c b/src/contrib/mempattern.c new file mode 100644 index 0000000..f57139d --- /dev/null +++ b/src/contrib/mempattern.c @@ -0,0 +1,122 @@ +/* Copyright (C) 2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> + +#include "contrib/mempattern.h" +#include "contrib/string.h" +#include "contrib/ucw/mempool.h" + +static void mm_nofree(void *p) +{ + /* nop */ +} + +static void *mm_malloc(void *ctx, size_t n) +{ + (void)ctx; + return malloc(n); +} + +void *mm_alloc(knot_mm_t *mm, size_t size) +{ + if (mm) { + return mm->alloc(mm->ctx, size); + } else { + return malloc(size); + } +} + +void *mm_calloc(knot_mm_t *mm, size_t nmemb, size_t size) +{ + if (nmemb == 0 || size == 0) { + return NULL; + } + if (mm) { + size_t total_size = nmemb * size; + if (total_size / nmemb != size) { // Overflow check + return NULL; + } + void *mem = mm_alloc(mm, total_size); + if (mem == NULL) { + return NULL; + } + return memzero(mem, total_size); + } else { + return calloc(nmemb, size); + } +} + +void *mm_realloc(knot_mm_t *mm, void *what, size_t size, size_t prev_size) +{ + if (mm) { + void *p = mm->alloc(mm->ctx, size); + if (p == NULL) { + return NULL; + } else { + if (what) { + memcpy(p, what, + prev_size < size ? prev_size : size); + } + mm_free(mm, what); + return p; + } + } else { + return realloc(what, size); + } +} + +char *mm_strdup(knot_mm_t *mm, const char *s) +{ + if (s == NULL) { + return NULL; + } + if (mm) { + size_t len = strlen(s) + 1; + void *mem = mm_alloc(mm, len); + if (mem == NULL) { + return NULL; + } + return memcpy(mem, s, len); + } else { + return strdup(s); + } +} + +void mm_free(knot_mm_t *mm, void *what) +{ + if (mm) { + if (mm->free) { + mm->free(what); + } + } else { + free(what); + } +} + +void mm_ctx_init(knot_mm_t *mm) +{ + mm->ctx = NULL; + mm->alloc = mm_malloc; + mm->free = free; +} + +void mm_ctx_mempool(knot_mm_t *mm, size_t chunk_size) +{ + mm->ctx = mp_new(chunk_size); + mm->alloc = (knot_mm_alloc_t)mp_alloc; + mm->free = mm_nofree; +} diff --git a/src/contrib/mempattern.h b/src/contrib/mempattern.h new file mode 100644 index 0000000..c74f983 --- /dev/null +++ b/src/contrib/mempattern.h @@ -0,0 +1,47 @@ +/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +/*! + * \brief Memory allocation related functions. + */ + +#pragma once + +#include "libknot/mm_ctx.h" + +/*! \brief Default memory block size. */ +#define MM_DEFAULT_BLKSIZE 4096 + +/*! \brief Allocs using 'mm' if any, uses system malloc() otherwise. */ +void *mm_alloc(knot_mm_t *mm, size_t size); + +/*! \brief Callocs using 'mm' if any, uses system calloc() otherwise. */ +void *mm_calloc(knot_mm_t *mm, size_t nmemb, size_t size); + +/*! \brief Reallocs using 'mm' if any, uses system realloc() otherwise. */ +void *mm_realloc(knot_mm_t *mm, void *what, size_t size, size_t prev_size); + +/*! \brief Strdups using 'mm' if any, uses system strdup() otherwise. */ +char *mm_strdup(knot_mm_t *mm, const char *s); + +/*! \brief Free using 'mm' if any, uses system free() otherwise. */ +void mm_free(knot_mm_t *mm, void *what); + +/*! \brief Initialize default memory allocation context. */ +void mm_ctx_init(knot_mm_t *mm); + +/*! \brief Memory pool context. */ +void mm_ctx_mempool(knot_mm_t *mm, size_t chunk_size); diff --git a/src/contrib/musl/LICENSE b/src/contrib/musl/LICENSE new file mode 100644 index 0000000..bca546a --- /dev/null +++ b/src/contrib/musl/LICENSE @@ -0,0 +1 @@ +../licenses/MIT diff --git a/src/contrib/musl/inet_ntop.c b/src/contrib/musl/inet_ntop.c new file mode 100644 index 0000000..1b72a61 --- /dev/null +++ b/src/contrib/musl/inet_ntop.c @@ -0,0 +1,79 @@ +/* + * Copyright © 2005-2020 Rich Felker, et al. + * + * 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 <arpa/inet.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> + +#include "contrib/musl/inet_ntop.h" +#include "contrib/openbsd/strlcpy.h" + +const char *knot_inet_ntop(int af, const void *restrict a0, char *restrict s, socklen_t l) +{ + const unsigned char *a = a0; + int i, j, max, best; + char buf[100]; + + switch (af) { + case AF_INET: + if (snprintf(s, l, "%d.%d.%d.%d", a[0],a[1],a[2],a[3]) < l) + return s; + break; + case AF_INET6: + if (memcmp(a, "\0\0\0\0\0\0\0\0\0\0\377\377", 12)) + (void)snprintf(buf, sizeof buf, + "%x:%x:%x:%x:%x:%x:%x:%x", + 256*a[0]+a[1],256*a[2]+a[3], + 256*a[4]+a[5],256*a[6]+a[7], + 256*a[8]+a[9],256*a[10]+a[11], + 256*a[12]+a[13],256*a[14]+a[15]); + else + (void)snprintf(buf, sizeof buf, + "%x:%x:%x:%x:%x:%x:%d.%d.%d.%d", + 256*a[0]+a[1],256*a[2]+a[3], + 256*a[4]+a[5],256*a[6]+a[7], + 256*a[8]+a[9],256*a[10]+a[11], + a[12],a[13],a[14],a[15]); + /* Replace longest /(^0|:)[:0]{2,}/ with "::" */ + for (i=best=0, max=2; buf[i]; i++) { + if (i && buf[i] != ':') continue; + j = strspn(buf+i, ":0"); + if (j>max) best=i, max=j; + } + if (max>3) { + buf[best] = buf[best+1] = ':'; + memmove(buf+best+2, buf+best+max, i-best-max+1); + } + if (strlen(buf) < l) { + strlcpy(s, buf, l); + return s; + } + break; + default: + errno = EAFNOSUPPORT; + return 0; + } + errno = ENOSPC; + return 0; +} diff --git a/src/contrib/musl/inet_ntop.h b/src/contrib/musl/inet_ntop.h new file mode 100644 index 0000000..73faaee --- /dev/null +++ b/src/contrib/musl/inet_ntop.h @@ -0,0 +1,28 @@ +/* + * Copyright © 2005-2020 Rich Felker, et al. + * + * 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. + */ + +#pragma once + +#include <sys/socket.h> + +const char *knot_inet_ntop(int af, const void *restrict a0, char *restrict s, socklen_t l); diff --git a/src/contrib/net.c b/src/contrib/net.c new file mode 100644 index 0000000..8bd1c26 --- /dev/null +++ b/src/contrib/net.c @@ -0,0 +1,852 @@ +/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <sys/types.h> // OpenBSD +#include <netinet/tcp.h> // TCP_FASTOPEN +#include <netinet/in.h> +#include <poll.h> +#include <stdbool.h> +#include <sys/socket.h> +#include <sys/uio.h> +#include <unistd.h> + +#include "libknot/errcode.h" +#include "contrib/macros.h" +#include "contrib/net.h" +#include "contrib/sockaddr.h" +#include "contrib/time.h" + +/*! + * \brief Enable socket option. + */ +static int sockopt_enable(int sock, int level, int optname) +{ + const int enable = 1; + if (setsockopt(sock, level, optname, &enable, sizeof(enable)) != 0) { + return knot_map_errno(); + } + + return KNOT_EOK; +} + +/*! + * \brief Create a non-blocking socket. + * + * Prefer SOCK_NONBLOCK if available to save one fcntl() syscall. + * + */ +static int socket_create(int family, int type, int proto) +{ +#ifdef SOCK_NONBLOCK + type |= SOCK_NONBLOCK; +#endif + int sock = socket(family, type, proto); + if (sock < 0) { + return knot_map_errno(); + } + +#ifndef SOCK_NONBLOCK + if (fcntl(sock, F_SETFL, O_NONBLOCK) != 0) { + int ret = knot_map_errno(); + close(sock); + return ret; + } +#endif + +/* + * OS X doesn't support MSG_NOSIGNAL. Use SO_NOSIGPIPE socket option instead. + */ +#if defined(__APPLE__) && !defined(MSG_NOSIGNAL) +#define MSG_NOSIGNAL 0 + int ret = sockopt_enable(sock, SOL_SOCKET, SO_NOSIGPIPE); + if (ret != KNOT_EOK) { + return ret; + } +#endif + + return sock; +} + +int net_unbound_socket(int type, const struct sockaddr_storage *addr) +{ + if (addr == NULL) { + return KNOT_EINVAL; + } + + /* Create socket. */ + return socket_create(addr->ss_family, type, 0); +} + +struct option { + int level; + int name; +}; + +/*! + * \brief Get setsock option for binding non-local address. + */ +static const struct option *nonlocal_option(int family) +{ + static const struct option ipv4 = { + #if defined(IP_FREEBIND) + IPPROTO_IP, IP_FREEBIND + #elif defined(IP_BINDANY) + IPPROTO_IP, IP_BINDANY + #else + 0, 0 + #endif + }; + + static const struct option ipv6 = { + #if defined(IP_FREEBIND) + IPPROTO_IP, IP_FREEBIND + #elif defined(IPV6_BINDANY) + IPPROTO_IPV6, IPV6_BINDANY + #else + 0, 0 + #endif + + }; + + switch (family) { + case AF_INET: return &ipv4; + case AF_INET6: return &ipv6; + default: + return NULL; + } +} + +static int enable_nonlocal(int sock, int family) +{ + const struct option *opt = nonlocal_option(family); + if (opt == NULL || opt->name == 0) { + return KNOT_ENOTSUP; + } + + return sockopt_enable(sock, opt->level, opt->name); +} + +static int enable_reuseport(int sock) +{ +#ifdef ENABLE_REUSEPORT +# if defined(__FreeBSD__) + return sockopt_enable(sock, SOL_SOCKET, SO_REUSEPORT_LB); +# else + return sockopt_enable(sock, SOL_SOCKET, SO_REUSEPORT); +# endif +#else + return KNOT_ENOTSUP; +#endif +} + +static void unlink_unix_socket(const struct sockaddr_storage *addr) +{ + char path[SOCKADDR_STRLEN] = { 0 }; + sockaddr_tostr(path, sizeof(path), addr); + unlink(path); +} + +int net_bound_socket(int type, const struct sockaddr_storage *addr, + net_bind_flag_t flags, mode_t unix_mode) +{ + /* Create socket. */ + int sock = net_unbound_socket(type, addr); + if (sock < 0) { + return sock; + } + + /* Unlink UNIX sock if exists. */ + if (addr->ss_family == AF_UNIX) { + unlink_unix_socket(addr); + } + + /* Reuse old address if taken. */ + int ret = sockopt_enable(sock, SOL_SOCKET, SO_REUSEADDR); + if (ret != KNOT_EOK) { + close(sock); + return ret; + } + +#if defined(__linux__) + /* Set MSS (Maximum Segment Size) limit. */ + if (addr->ss_family != AF_UNIX && type == SOCK_STREAM) { + const int mss = KNOT_TCP_MSS; + if (setsockopt(sock, IPPROTO_TCP, TCP_MAXSEG, &mss, sizeof(mss)) != 0) { + ret = knot_map_errno(); + close(sock); + return ret; + } + } +#endif + + /* Don't bind IPv4 for IPv6 any address. */ + if (addr->ss_family == AF_INET6) { + ret = sockopt_enable(sock, IPPROTO_IPV6, IPV6_V6ONLY); + if (ret != KNOT_EOK) { + close(sock); + return ret; + } + } + + /* Allow bind to non-local address. */ + if (flags & NET_BIND_NONLOCAL) { + ret = enable_nonlocal(sock, addr->ss_family); + if (ret != KNOT_EOK) { + close(sock); + return ret; + } + } + + /* Allow to bind the same address by multiple threads. */ + if (flags & NET_BIND_MULTIPLE) { + ret = enable_reuseport(sock); + if (ret != KNOT_EOK) { + close(sock); + return ret; + } + } + + /* Bind to specified address. */ + ret = bind(sock, (const struct sockaddr *)addr, sockaddr_len(addr)); + if (ret < 0) { + ret = knot_map_errno(); + close(sock); + return ret; + } + + if (addr->ss_family == AF_UNIX && unix_mode != 0) { + const char *path = ((const struct sockaddr_un *)addr)->sun_path; + if (chmod(path, unix_mode) != 0) { + ret = knot_map_errno(); + close(sock); + return ret; + } + } + + return sock; +} + +static int tfo_connect(int sock, const struct sockaddr_storage *addr) +{ +#if defined(__linux__) + /* connect() will be called implicitly with sendmsg(). */ + return KNOT_EOK; +#elif defined(__FreeBSD__) + return sockopt_enable(sock, IPPROTO_TCP, TCP_FASTOPEN); +#elif defined(__APPLE__) + /* Connection is performed lazily when first data is sent. */ + sa_endpoints_t ep = { + .sae_dstaddr = (const struct sockaddr *)addr, + .sae_dstaddrlen = sockaddr_len(addr) + }; + int flags = CONNECT_DATA_IDEMPOTENT | CONNECT_RESUME_ON_READ_WRITE; + + int ret = connectx(sock, &ep, SAE_ASSOCID_ANY, flags, NULL, 0, NULL, NULL); + return (ret == 0 ? KNOT_EOK : knot_map_errno()); +#else + return KNOT_ENOTSUP; +#endif +} + +int net_connected_socket(int type, const struct sockaddr_storage *dst_addr, + const struct sockaddr_storage *src_addr, bool tfo) +{ + if (dst_addr == NULL) { + return KNOT_EINVAL; + } + + /* Check port. */ + if (sockaddr_port(dst_addr) == 0) { + return KNOT_NET_EADDR; + } + + /* Bind to specific source address - if set. */ + int sock = -1; + if (src_addr && src_addr->ss_family != AF_UNSPEC) { + sock = net_bound_socket(type, src_addr, 0, 0); + } else { + sock = net_unbound_socket(type, dst_addr); + } + if (sock < 0) { + return sock; + } + + /* Connect to destination. */ + if (tfo && net_is_stream(sock)) { + int ret = tfo_connect(sock, dst_addr); + if (ret != KNOT_EOK) { + close(sock); + return ret; + } + } else { + int ret = connect(sock, (const struct sockaddr *)dst_addr, + sockaddr_len(dst_addr)); + if (ret != 0 && errno != EINPROGRESS) { + ret = knot_map_errno(); + close(sock); + return ret; + } + } + + return sock; +} + +int net_bound_tfo(int sock, int backlog) +{ +#if defined(TCP_FASTOPEN) +#if defined(__APPLE__) + if (backlog > 0) { + backlog = 1; // just on-off switch on macOS + } +#endif + if (setsockopt(sock, IPPROTO_TCP, TCP_FASTOPEN, &backlog, sizeof(backlog)) != 0) { + return knot_map_errno(); + } + + return KNOT_EOK; +#endif + return KNOT_ENOTSUP; +} + +int net_cmsg_ecn_enable(int sock, int family) +{ + switch (family) { + case AF_INET: +#ifdef IP_RECVTOS + return sockopt_enable(sock, IPPROTO_IP, IP_RECVTOS); +#else + return KNOT_ENOTSUP; +#endif + case AF_INET6: + return sockopt_enable(sock, IPPROTO_IPV6, IPV6_RECVTCLASS); + default: + return KNOT_EINVAL; + } +} + +int *net_cmsg_ecn_ptr(struct cmsghdr *cmsg) +{ +#if defined(__linux__) + const int type_v4 = IP_TOS; + const int type_v6 = IPV6_TCLASS; +#else +#ifdef IP_RECVTOS + const int type_v4 = IP_RECVTOS; +#endif + const int type_v6 = IPV6_RECVTCLASS; +#endif + + if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == type_v6) { + cmsg->cmsg_type = IPV6_TCLASS; // Update the type for outgoing use. + return (int *)CMSG_DATA(cmsg); + } +#ifdef IP_RECVTOS + else if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == type_v4) { + cmsg->cmsg_type = IP_TOS; // Update the type for outgoing use. + return (int *)CMSG_DATA(cmsg); + } +#endif + else { + return NULL; + } +} + +uint8_t net_cmsg_ecn(struct msghdr *msg) +{ + for (struct cmsghdr *c = CMSG_FIRSTHDR(msg); c != NULL; c = CMSG_NXTHDR(msg, c)) { + int *p_ecn = net_cmsg_ecn_ptr(c); + if (p_ecn != NULL) { + return (*p_ecn & 0x3); + } + } + return 0; +} + +int net_ecn_set(int sock, int family, uint8_t ecn) +{ + int val = ecn; + switch (family) { + case AF_INET: +#ifdef IP_RECVTOS /* Disallow setting TOS if RECVTOS isn't supported (OpenBSD). */ + if (setsockopt(sock, IPPROTO_IP, IP_TOS, &val, sizeof(val)) != 0) { + return knot_map_errno(); + } +#else + return KNOT_ENOTSUP; +#endif + break; + case AF_INET6: + if (setsockopt(sock, IPPROTO_IPV6, IPV6_TCLASS, &val, sizeof(val)) != 0) { + return knot_map_errno(); + } + break; + default: + return KNOT_ENOTSUP; + } + return KNOT_EOK; +} + +bool net_is_connected(int sock) +{ + struct sockaddr_storage addr; + socklen_t len = sizeof(addr); + return (getpeername(sock, (struct sockaddr *)&addr, &len) == 0); +} + +int net_socktype(int sock) +{ + int type; + socklen_t size = sizeof(type); + + if (getsockopt(sock, SOL_SOCKET, SO_TYPE, &type, &size) == 0) { + return type; + } else { + return AF_UNSPEC; + } +} + +bool net_is_stream(int sock) +{ + return net_socktype(sock) == SOCK_STREAM; +} + +int net_accept(int sock, struct sockaddr_storage *addr) +{ + socklen_t len = sizeof(*addr); + socklen_t *addr_len = (addr != NULL) ? &len : NULL; + + int remote = -1; + +#if defined(HAVE_ACCEPT4) && defined(SOCK_NONBLOCK) + remote = accept4(sock, (struct sockaddr *)addr, addr_len, SOCK_NONBLOCK); + if (remote < 0) { + return knot_map_errno(); + } +#else + remote = accept(sock, (struct sockaddr *)addr, addr_len); + if (fcntl(remote, F_SETFL, O_NONBLOCK) != 0) { + int error = knot_map_errno(); + close(remote); + return error; + } +#endif + + return remote; +} + +void net_reset(int sock) +{ + struct sockaddr unspec = { .sa_family = AF_UNSPEC }; + (void)connect(sock, &unspec, sizeof(unspec)); +} + +/* -- I/O interface handling partial -------------------------------------- */ + +/*! + * \brief Perform \a poll() on one socket. + */ +static int poll_one(int fd, int events, int timeout_ms) +{ + struct pollfd pfd = { + .fd = fd, + .events = events + }; + + return poll(&pfd, 1, timeout_ms); +} + +/*! + * \brief Check if we should wait for I/O readiness. + * + * \param error \a errno set by the failed I/O operation. + */ +static bool io_should_wait(int error) +{ + if (error == EAGAIN || error == EWOULDBLOCK || /* Socket data not ready. */ + error == ENOMEM || error == ENOBUFS) { /* Insufficient resources. */ + return true; + } + +#ifndef __linux__ + /* FreeBSD: connection in progress. */ + if (error == ENOTCONN) { + return true; + } +#endif + + return false; +} + +/*! + * \brief Check if we should wait again. + * + * \param error \a errno set by the failed wait operation. + */ +static bool wait_should_retry(int error) +{ + if (error == EINTR || /* System call interrupted. */ + error == EAGAIN || error == ENOMEM) { /* Insufficient resources. */ + return true; + } + return false; +} + +/*! + * \brief I/O operation callbacks. + */ +struct io { + ssize_t (*process)(int sockfd, struct msghdr *msg, int timeout_ms); + int (*wait)(int sockfd, int timeout_ms); +}; + +/*! + * \brief Get total size of I/O vector in a message. + */ +static size_t msg_iov_len(const struct msghdr *msg) +{ + size_t total = 0; + + for (int i = 0; i < msg->msg_iovlen; i++) { + total += msg->msg_iov[i].iov_len; + } + + return total; +} + +/*! + * \brief Shift processed data out of message IO vectors. + */ +static void msg_iov_shift(struct msghdr *msg, size_t done) +{ + struct iovec *iov = msg->msg_iov; + int iovlen = msg->msg_iovlen; + + for (int i = 0; i < iovlen && done > 0; i++) { + if (iov[i].iov_len > done) { + iov[i].iov_base += done; + iov[i].iov_len -= done; + done = 0; + } else { + done -= iov[i].iov_len; + msg->msg_iov += 1; + msg->msg_iovlen -= 1; + } + } + + assert(done == 0); +} + +#define TIMEOUT_CTX_INIT \ + struct timespec begin, end; \ + if (*timeout_ptr > 0) { \ + clock_gettime(CLOCK_MONOTONIC, &begin); \ + } + +#define TIMEOUT_CTX_UPDATE \ + if (*timeout_ptr > 0) { \ + clock_gettime(CLOCK_MONOTONIC, &end); \ + int running_ms = time_diff_ms(&begin, &end); \ + *timeout_ptr = MAX(*timeout_ptr - running_ms, 0); \ + } + +/*! + * \brief Perform an I/O operation with a socket with waiting. + * + * \param oneshot If set, doesn't wait until the buffer is fully processed. + */ +static ssize_t io_exec(const struct io *io, int fd, struct msghdr *msg, + bool oneshot, int *timeout_ptr) +{ + size_t done = 0; + size_t total = msg_iov_len(msg); + + for (;;) { + /* Perform I/O. */ + ssize_t ret = io->process(fd, msg, *timeout_ptr); + if (ret == -1 && errno == EINTR) { + continue; + } + if (ret > 0) { + done += ret; + if (oneshot || done == total) { + break; + } + msg_iov_shift(msg, ret); + } + + /* Wait for data readiness. */ + if (ret > 0 || (ret == -1 && io_should_wait(errno))) { + for (;;) { + TIMEOUT_CTX_INIT + + ret = io->wait(fd, *timeout_ptr); + + if (ret == 1) { + TIMEOUT_CTX_UPDATE + /* Ready, retry process. */ + break; + } else if (ret == -1 && wait_should_retry(errno)) { + TIMEOUT_CTX_UPDATE + /* Interrupted or transient error, continue waiting. */ + continue; + } else if (ret == 0) { + /* Timed out, exit. */ + return KNOT_ETIMEOUT; + } else { + /* In specific circumstances with Valgrind, + * poll() returns wrong value. + */ + assert(ret <= 1); + assert(ret >= -1); + /* Other error, exit. */ + return KNOT_ECONN; + } + } + } else { + /* Disconnect or error. */ + return KNOT_ECONN; + } + } + + return done; +} + +static ssize_t recv_process(int fd, struct msghdr *msg, int timeout_ms) +{ + return recvmsg(fd, msg, MSG_DONTWAIT | MSG_NOSIGNAL); +} + +static int recv_wait(int fd, int timeout_ms) +{ + return poll_one(fd, POLLIN, timeout_ms); +} + +static ssize_t recv_data(int sock, struct msghdr *msg, bool oneshot, int *timeout_ptr) +{ + static const struct io RECV_IO = { + .process = recv_process, + .wait = recv_wait + }; + + return io_exec(&RECV_IO, sock, msg, oneshot, timeout_ptr); +} + +static ssize_t send_process_tfo(int fd, struct msghdr *msg, int timeout_ms) +{ +#if defined(__linux__) + int ret = sendmsg(fd, msg, MSG_FASTOPEN); + if (ret != 0 && errno == EINPROGRESS) { + if (poll_one(fd, POLLOUT, timeout_ms) != 1) { + errno = ETIMEDOUT; + return -1; + } + ret = sendmsg(fd, msg, MSG_NOSIGNAL); + } + return ret; +#else + return sendmsg(fd, msg, MSG_NOSIGNAL); +#endif +} + +static ssize_t send_process(int fd, struct msghdr *msg, int timeout_ms) +{ + return sendmsg(fd, msg, MSG_NOSIGNAL); +} + +static int send_wait(int fd, int timeout_ms) +{ + return poll_one(fd, POLLOUT, timeout_ms); +} + +static ssize_t send_data(int sock, struct msghdr *msg, int *timeout_ptr, bool tfo) +{ + static const struct io SEND_IO = { + .process = send_process, + .wait = send_wait + }; + static const struct io SEND_IO_TFO = { + .process = send_process_tfo, + .wait = send_wait + }; + + return io_exec(tfo ? &SEND_IO_TFO : &SEND_IO, sock, msg, false, timeout_ptr); +} + +/* -- generic stream and datagram I/O -------------------------------------- */ + +ssize_t net_base_send(int sock, const uint8_t *buffer, size_t size, + const struct sockaddr_storage *addr, int timeout_ms) +{ + if (sock < 0 || buffer == NULL) { + return KNOT_EINVAL; + } + + struct iovec iov = { + .iov_base = (void *)buffer, + .iov_len = size + }; + struct msghdr msg = { + .msg_name = (void *)addr, + .msg_namelen = sockaddr_len(addr), + .msg_iov = &iov, + .msg_iovlen = 1 + }; + + int ret = send_data(sock, &msg, &timeout_ms, false); + if (ret < 0) { + return ret; + } else if (ret != size) { + return KNOT_ECONN; + } + + return ret; +} + +ssize_t net_base_recv(int sock, uint8_t *buffer, size_t size, + struct sockaddr_storage *addr, int timeout_ms) +{ + if (sock < 0 || buffer == NULL) { + return KNOT_EINVAL; + } + + struct iovec iov = { + .iov_base = buffer, + .iov_len = size + }; + struct msghdr msg = { + .msg_name = (void *)addr, + .msg_namelen = addr ? sizeof(*addr) : 0, + .msg_iov = &iov, + .msg_iovlen = 1 + }; + + return recv_data(sock, &msg, true, &timeout_ms); +} + +ssize_t net_msg_send(int sock, struct msghdr *msg, int timeout_ms) +{ + if (msg->msg_iovlen != 1) { + return KNOT_EINVAL; + } + int ret = send_data(sock, msg, &timeout_ms, false); + if (ret < 0) { + return ret; + } else if (ret != msg->msg_iov->iov_len) { + return KNOT_ECONN; + } + + return ret; +} + +ssize_t net_msg_recv(int sock, struct msghdr *msg, int timeout_ms) +{ + return recv_data(sock, msg, true, &timeout_ms); +} + +ssize_t net_dgram_send(int sock, const uint8_t *buffer, size_t size, + const struct sockaddr_storage *addr) +{ + return net_base_send(sock, buffer, size, addr, 0); +} + +ssize_t net_dgram_recv(int sock, uint8_t *buffer, size_t size, int timeout_ms) +{ + return net_base_recv(sock, buffer, size, NULL, timeout_ms); +} + +ssize_t net_stream_send(int sock, const uint8_t *buffer, size_t size, int timeout_ms) +{ + return net_base_send(sock, buffer, size, NULL, timeout_ms); +} + +ssize_t net_stream_recv(int sock, uint8_t *buffer, size_t size, int timeout_ms) +{ + return net_base_recv(sock, buffer, size, NULL, timeout_ms); +} + +/* -- DNS specific I/O ----------------------------------------------------- */ + +ssize_t net_dns_tcp_send(int sock, const uint8_t *buffer, size_t size, int timeout_ms, + struct sockaddr_storage *tfo_addr) +{ + if (sock < 0 || buffer == NULL || size > UINT16_MAX) { + return KNOT_EINVAL; + } + + uint16_t pktsize = htons(size); + struct iovec iov[2] = { + { + .iov_base = &pktsize, + .iov_len = sizeof(uint16_t) + }, + { + .iov_base = (void *)buffer, + .iov_len = size + } + }; + struct msghdr msg = { + .msg_name = (void *)tfo_addr, + .msg_namelen = tfo_addr ? sizeof(*tfo_addr) : 0, + .msg_iov = iov, + .msg_iovlen = 2 + }; + + ssize_t ret = send_data(sock, &msg, &timeout_ms, tfo_addr != NULL); + if (ret < 0) { + return ret; + } + + return size; /* Do not count the size prefix. */ +} + +ssize_t net_dns_tcp_recv(int sock, uint8_t *buffer, size_t size, int timeout_ms) +{ + if (sock < 0 || buffer == NULL) { + return KNOT_EINVAL; + } + + uint16_t pktsize = 0; + + struct iovec iov = { + .iov_base = &pktsize, + .iov_len = sizeof(pktsize) + }; + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1 + }; + + /* Receive size. */ + int ret = recv_data(sock, &msg, false, &timeout_ms); + if (ret != sizeof(pktsize)) { + return ret; + } + pktsize = ntohs(pktsize); + + /* Check packet size */ + if (size < pktsize) { + return KNOT_ESPACE; + } + + /* Receive payload. */ + iov.iov_base = buffer; + iov.iov_len = pktsize; + + return recv_data(sock, &msg, false, &timeout_ms); +} diff --git a/src/contrib/net.h b/src/contrib/net.h new file mode 100644 index 0000000..698c66b --- /dev/null +++ b/src/contrib/net.h @@ -0,0 +1,264 @@ +/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stdbool.h> +#include <stdint.h> +#include <sys/socket.h> +#include <sys/stat.h> + +// 1280 (IPv6 minimum link MTU) - 40 (IPv6 fixed header) - 20 (TCP fixed header) +#define KNOT_TCP_MSS 1220 + +/*! + * \brief Network interface flags. + */ +typedef enum { + NET_BIND_NONLOCAL = (1 << 0), //!< Allow to bind unavailable address. + NET_BIND_MULTIPLE = (1 << 1), //!< Allow to bind address multiple times. +} net_bind_flag_t; + +/*! + * \brief Create unbound socket of given family and type. + * + * \note The socket is set to non-blocking mode. + * + * \param type Socket transport type (SOCK_STREAM, SOCK_DGRAM). + * \param addr Socket address. + * + * \return socket or error code + */ +int net_unbound_socket(int type, const struct sockaddr_storage *addr); + +/*! + * \brief Create socket bound to given address. + * + * The socket is set to non-blocking mode. + * + * \param type Socket transport type (SOCK_STREAM, SOCK_DGRAM). + * \param addr Socket address. + * \param flags Socket binding options. + * \param unix_mode Socket file mode (UNIX socket only). 0 means don't set mode. + * + * \return socket or error code + */ +int net_bound_socket(int type, const struct sockaddr_storage *addr, + net_bind_flag_t flags, mode_t unix_mode); + +/*! + * \brief Create socket connected (asynchronously) to destination address. + * + * \note The socket is set to non-blocking mode. + * + * \param type Socket transport type (SOCK_STREAM, SOCK_DGRAM). + * \param dst_addr Destination address. + * \param src_addr Source address (can be NULL). + * \param tfo Enable TCP Fast Open. + * + * \return socket or error code + */ +int net_connected_socket(int type, const struct sockaddr_storage *dst_addr, + const struct sockaddr_storage *src_addr, bool tfo); + +/*! + * \brief Enables TCP Fast Open on a bound socket. + * + * \param sock Socket. + * + * \return KNOT_EOK or error code + */ +int net_bound_tfo(int sock, int backlog); + +/*! + * \brief Tell kernel to send ECN bits thru CMSG on packet receival. + * + * \param sock Socket to enable receiving ECN bits. + * \param family Address family on that socket. + * + * \return KNOT_E* + */ +int net_cmsg_ecn_enable(int sock, int family); + +/*! + * \brief Return pointer to possible ECN value in cmsg. + * + * \note The function also updates cmsg_type for outgoing use. + * + * \param cmsg Received control message. + * + * \return Pointer to ECN value or NULL. + */ +int *net_cmsg_ecn_ptr(struct cmsghdr *cmsg); + +/*! + * \brief Get ECN bits of packets received by recv(m)msg. + * + * \param msg Received message header. + * + * \return 0..3 + */ +uint8_t net_cmsg_ecn(struct msghdr *msg); + +/*! + * \brief Set ECN for outgoing packets on this socket. + * + * \param sock Socket file descriptor. + * \param family Address family for outgoing packets. + * \param ecn ECN bits to be set. + * + * \return KNOT_E* + */ +int net_ecn_set(int sock, int family, uint8_t ecn); + +/*! + * \brief Return true if the socket is fully connected. + * + * \param sock Socket. + * + * \return true if connected + */ +bool net_is_connected(int sock); + +/*! + * \brief Get socket type (e.g. \a SOCK_STREAM). + * + * \param sock Socket. + */ +int net_socktype(int sock); + +/*! + * \brief Check if socket is a SOCK_STREAM socket. + */ +bool net_is_stream(int sock); + +/*! + * \brief Accept a connection on a listening socket. + * + * \brief The socket is set to non-blocking mode. + * + * \param sock Socket + * \param addr Remote address (can be NULL). + * + * \return socket or error code + */ +int net_accept(int sock, struct sockaddr_storage *addr); + +/*! + * \brief Reset a TCP connection (with a RST packet). + */ +void net_reset(int sock); + +/*! + * \brief Send a message on a socket. + * + * The socket can be SOCK_STREAM or SOCK_DGRAM. + * + * The implementation handles partial-writes automatically. + * + * \param[in] sock Socket. + * \param[in] buffer Message buffer. + * \param[in] size Size of the message. + * \param[in] addr Remote address (ignored for SOCK_STREAM). + * \param[in] timeout_ms Write timeout in milliseconds (-1 for infinity, + * not valid for SOCK_DGRAM). + * + * \return Number of bytes sent or negative error code. + */ +ssize_t net_base_send(int sock, const uint8_t *buffer, size_t size, + const struct sockaddr_storage *addr, int timeout_ms); + +/*! + * \brief Receive a message from a socket. + * + * \param[in] sock Socket. + * \param[out] buffer Receiving buffer. + * \param[in] size Capacity of the receiving buffer. + * \param[out] addr Remote address (can be NULL). + * \param[in] timeout_ms Read timeout in milliseconds (-1 for infinity). + * + * \return Number of bytes read or negative error code. + */ +ssize_t net_base_recv(int sock, uint8_t *buffer, size_t size, + struct sockaddr_storage *addr, int timeout_ms); + +/*! + * \brief Send a message with prepared msghdr structure. + * + * \see net_base_send + */ +ssize_t net_msg_send(int sock, struct msghdr *msg, int timeout_ms); + +/*! + * \brief Receive a message into msghdr structure. + * + * \see net_base_send + */ +ssize_t net_msg_recv(int sock, struct msghdr *msg, int timeout_ms); + +/*! + * \brief Send a message on a SOCK_DGRAM socket. + * + * \see net_base_send + */ +ssize_t net_dgram_send(int sock, const uint8_t *buffer, size_t size, + const struct sockaddr_storage *addr); + +/*! + * \brief Receive a message from a SOCK_DGRAM socket. + * + * \see net_base_recv + */ +ssize_t net_dgram_recv(int sock, uint8_t *buffer, size_t size, int timeout_ms); + +/*! + * \brief Send a message on a SOCK_STREAM socket. + * + * \see net_base_send + */ +ssize_t net_stream_send(int sock, const uint8_t *buffer, size_t size, int timeout_ms); + +/*! + * \brief Receive a message from a SOCK_STREAM socket. + * + * \see net_base_recv + */ +ssize_t net_stream_recv(int sock, uint8_t *buffer, size_t size, int timeout_ms); + +/*! + * \brief Send a DNS message on a TCP socket. + * + * The outgoing message is prefixed with a two-byte value carrying the DNS + * message size according to the specification. These two bytes are not + * reflected in the return value. + * + * \param[in] tfo_addr If not NULL, send using TCP Fast Open to this address. + * + * \see net_base_send + */ +ssize_t net_dns_tcp_send(int sock, const uint8_t *buffer, size_t size, int timeout_ms, + struct sockaddr_storage *tfo_addr); + +/*! + * \brief Receive a DNS message from a TCP socket. + * + * The first two bytes of the incoming message are interpreted as a DNS message + * size according to the specification. These two bytes are not included in + * the returned size. Only a complete DNS message is retrieved. + * + * \see net_base_recv + */ +ssize_t net_dns_tcp_recv(int sock, uint8_t *buffer, size_t size, int timeout_ms); diff --git a/src/contrib/openbsd/LICENSE b/src/contrib/openbsd/LICENSE new file mode 100644 index 0000000..e9a1aaa --- /dev/null +++ b/src/contrib/openbsd/LICENSE @@ -0,0 +1,2 @@ +../licenses/0BSD +../licenses/BSD-3-Clause
\ No newline at end of file diff --git a/src/contrib/openbsd/siphash.c b/src/contrib/openbsd/siphash.c new file mode 100644 index 0000000..26b8cfc --- /dev/null +++ b/src/contrib/openbsd/siphash.c @@ -0,0 +1,176 @@ +/* $OpenBSD: siphash.c,v 1.6 2017/04/12 17:41:49 deraadt Exp $ */ + +/*- + * Copyright (c) 2013 Andre Oppermann <andre@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * SipHash is a family of PRFs SipHash-c-d where the integer parameters c and d + * are the number of compression rounds and the number of finalization rounds. + * A compression round is identical to a finalization round and this round + * function is called SipRound. Given a 128-bit key k and a (possibly empty) + * byte string m, SipHash-c-d returns a 64-bit value SipHash-c-d(k; m). + * + * Implemented from the paper "SipHash: a fast short-input PRF", 2012.09.18, + * by Jean-Philippe Aumasson and Daniel J. Bernstein, + * Permanent Document ID b9a943a805fbfc6fde808af9fc0ecdfa + * https://131002.net/siphash/siphash.pdf + * https://131002.net/siphash/ + */ + +#include <string.h> + +#include "libknot/endian.h" +#include "contrib/string.h" +#include "contrib/openbsd/siphash.h" + +static void SipHash_CRounds(SIPHASH_CTX *, int); +static void SipHash_Rounds(SIPHASH_CTX *, int); + +void +SipHash_Init(SIPHASH_CTX *ctx, const SIPHASH_KEY *key) +{ + uint64_t k0, k1; + + k0 = le64toh(key->k0); + k1 = le64toh(key->k1); + + ctx->v[0] = 0x736f6d6570736575ULL ^ k0; + ctx->v[1] = 0x646f72616e646f6dULL ^ k1; + ctx->v[2] = 0x6c7967656e657261ULL ^ k0; + ctx->v[3] = 0x7465646279746573ULL ^ k1; + + memset(ctx->buf, 0, sizeof(ctx->buf)); + ctx->bytes = 0; +} + +void +SipHash_Update(SIPHASH_CTX *ctx, int rc, int rf, const void *src, size_t len) +{ + const uint8_t *ptr = src; + size_t left, used; + + if (len == 0) + return; + + used = ctx->bytes % sizeof(ctx->buf); + ctx->bytes += len; + + if (used > 0) { + left = sizeof(ctx->buf) - used; + + if (len >= left) { + memcpy(&ctx->buf[used], ptr, left); + SipHash_CRounds(ctx, rc); + len -= left; + ptr += left; + } else { + memcpy(&ctx->buf[used], ptr, len); + return; + } + } + + while (len >= sizeof(ctx->buf)) { + memcpy(ctx->buf, ptr, sizeof(ctx->buf)); + SipHash_CRounds(ctx, rc); + len -= sizeof(ctx->buf); + ptr += sizeof(ctx->buf); + } + + if (len > 0) + memcpy(&ctx->buf, ptr, len); +} + +uint64_t +SipHash_End(SIPHASH_CTX *ctx, int rc, int rf) +{ + uint64_t r; + size_t left, used; + + used = ctx->bytes % sizeof(ctx->buf); + left = sizeof(ctx->buf) - used; + memset(&ctx->buf[used], 0, left - 1); + ctx->buf[7] = ctx->bytes; + + SipHash_CRounds(ctx, rc); + ctx->v[2] ^= 0xff; + SipHash_Rounds(ctx, rf); + + r = (ctx->v[0] ^ ctx->v[1]) ^ (ctx->v[2] ^ ctx->v[3]); + memzero(ctx, sizeof(*ctx)); + return htole64(r); +} + +uint64_t +SipHash(const SIPHASH_KEY *key, int rc, int rf, const void *src, size_t len) +{ + SIPHASH_CTX ctx; + + SipHash_Init(&ctx, key); + SipHash_Update(&ctx, rc, rf, src, len); + return (SipHash_End(&ctx, rc, rf)); +} + +#define SIP_ROTL(x, b) ((x) << (b)) | ( (x) >> (64 - (b))) + +static void +SipHash_Rounds(SIPHASH_CTX *ctx, int rounds) +{ + while (rounds--) { + ctx->v[0] += ctx->v[1]; + ctx->v[2] += ctx->v[3]; + ctx->v[1] = SIP_ROTL(ctx->v[1], 13); + ctx->v[3] = SIP_ROTL(ctx->v[3], 16); + + ctx->v[1] ^= ctx->v[0]; + ctx->v[3] ^= ctx->v[2]; + ctx->v[0] = SIP_ROTL(ctx->v[0], 32); + + ctx->v[2] += ctx->v[1]; + ctx->v[0] += ctx->v[3]; + ctx->v[1] = SIP_ROTL(ctx->v[1], 17); + ctx->v[3] = SIP_ROTL(ctx->v[3], 21); + + ctx->v[1] ^= ctx->v[2]; + ctx->v[3] ^= ctx->v[0]; + ctx->v[2] = SIP_ROTL(ctx->v[2], 32); + } +} + +static void +SipHash_CRounds(SIPHASH_CTX *ctx, int rounds) +{ + uint64_t tmp; + + memcpy(&tmp, ctx->buf, sizeof(tmp)); + uint64_t m = le64toh(tmp); + + ctx->v[3] ^= m; + SipHash_Rounds(ctx, rounds); + ctx->v[0] ^= m; +} diff --git a/src/contrib/openbsd/siphash.h b/src/contrib/openbsd/siphash.h new file mode 100644 index 0000000..59d952d --- /dev/null +++ b/src/contrib/openbsd/siphash.h @@ -0,0 +1,83 @@ +/*- + * Copyright (c) 2013 Andre Oppermann <andre@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $OpenBSD: siphash.h,v 1.3 2015/02/20 11:51:03 tedu Exp $ + */ + +/* + * SipHash is a family of pseudorandom functions (a.k.a. keyed hash functions) + * optimized for speed on short messages returning a 64bit hash/digest value. + * + * The number of rounds is defined during the initialization: + * SipHash24_Init() for the fast and reasonable strong version + * SipHash48_Init() for the strong version (half as fast) + * + * struct SIPHASH_CTX ctx; + * SipHash24_Init(&ctx); + * SipHash_SetKey(&ctx, "16bytes long key"); + * SipHash_Update(&ctx, pointer_to_string, length_of_string); + * SipHash_End(&ctx); + */ + +#ifndef _SIPHASH_H_ +#define _SIPHASH_H_ + +#include <stddef.h> +#include <stdint.h> + +#define SIPHASH_BLOCK_LENGTH 8 +#define SIPHASH_KEY_LENGTH 16 +#define SIPHASH_DIGEST_LENGTH 8 + +typedef struct _SIPHASH_CTX { + uint64_t v[4]; + uint8_t buf[SIPHASH_BLOCK_LENGTH]; + uint32_t bytes; +} SIPHASH_CTX; + +typedef struct { + uint64_t k0; + uint64_t k1; +} SIPHASH_KEY; + +void SipHash_Init(SIPHASH_CTX *, const SIPHASH_KEY *); +void SipHash_Update(SIPHASH_CTX *, int, int, const void *, size_t); +uint64_t SipHash_End(SIPHASH_CTX *, int, int); +uint64_t SipHash(const SIPHASH_KEY *, int, int, const void *, size_t); + +#define SipHash24_Init(_c, _k) SipHash_Init((_c), (_k)) +#define SipHash24_Update(_c, _p, _l) SipHash_Update((_c), 2, 4, (_p), (_l)) +#define SipHash24_End(_d) SipHash_End((_d), 2, 4) +#define SipHash24(_k, _p, _l) SipHash((_k), 2, 4, (_p), (_l)) + +#define SipHash48_Init(_c, _k) SipHash_Init((_c), (_k)) +#define SipHash48_Update(_c, _p, _l) SipHash_Update((_c), 4, 8, (_p), (_l)) +#define SipHash48_End(_d) SipHash_End((_d), 4, 8) +#define SipHash48(_k, _p, _l) SipHash((_k), 4, 8, (_p), (_l)) + +#endif /* _SIPHASH_H_ */ diff --git a/src/contrib/openbsd/strlcat.c b/src/contrib/openbsd/strlcat.c new file mode 100644 index 0000000..1409062 --- /dev/null +++ b/src/contrib/openbsd/strlcat.c @@ -0,0 +1,48 @@ +/* + * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <string.h> + +#include "contrib/openbsd/strlcat.h" + +size_t +knot_strlcat(char *dst, const char *src, size_t siz) +{ + char *d = dst; + const char *s = src; + size_t n = siz; + size_t dlen; + + /* Find the end of dst and adjust bytes left but don't go past end */ + while (n-- != 0 && *d != '\0') + d++; + dlen = d - dst; + n = siz - dlen; + + if (n == 0) + return(dlen + strlen(s)); + while (*s != '\0') { + if (n != 1) { + *d++ = *s; + n--; + } + s++; + } + *d = '\0'; + + return(dlen + (s - src)); /* count does not include NUL */ +} diff --git a/src/contrib/openbsd/strlcat.h b/src/contrib/openbsd/strlcat.h new file mode 100644 index 0000000..7016069 --- /dev/null +++ b/src/contrib/openbsd/strlcat.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#pragma once + +#ifndef HAVE_STRLCAT +#define strlcat(dst, src, size) knot_strlcat(dst, src, size) +#endif + +/* + * Appends src to string dst of size siz (unlike strncat, siz is the + * full size of dst, not space left). At most siz-1 characters + * will be copied. Always NUL terminates (unless siz <= strlen(dst)). + * Returns strlen(src) + MIN(siz, strlen(initial dst)). + * If retval >= siz, truncation occurred. + */ +size_t +knot_strlcat(char *dst, const char *src, size_t siz); diff --git a/src/contrib/openbsd/strlcpy.c b/src/contrib/openbsd/strlcpy.c new file mode 100644 index 0000000..eafc0e4 --- /dev/null +++ b/src/contrib/openbsd/strlcpy.c @@ -0,0 +1,46 @@ +/* + * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <string.h> + +#include "contrib/openbsd/strlcpy.h" + +size_t +knot_strlcpy(char *dst, const char *src, size_t siz) +{ + char *d = dst; + const char *s = src; + size_t n = siz; + + /* Copy as many bytes as will fit */ + if (n != 0) { + while (--n != 0) { + if ((*d++ = *s++) == '\0') + break; + } + } + + /* Not enough room in dst, add NUL and traverse rest of src */ + if (n == 0) { + if (siz != 0) + *d = '\0'; /* NUL-terminate dst */ + while (*s++) + ; + } + + return(s - src - 1); /* count does not include NUL */ +} diff --git a/src/contrib/openbsd/strlcpy.h b/src/contrib/openbsd/strlcpy.h new file mode 100644 index 0000000..6421068 --- /dev/null +++ b/src/contrib/openbsd/strlcpy.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#pragma once + +#ifndef HAVE_STRLCPY +#define strlcpy(dst, src, size) knot_strlcpy(dst, src, size) +#endif + +/* + * Copy src to string dst of size siz. At most siz-1 characters + * will be copied. Always NUL terminates (unless siz == 0). + * Returns strlen(src); if retval >= siz, truncation occurred. + */ +size_t +knot_strlcpy(char *dst, const char *src, size_t siz); diff --git a/src/contrib/os.h b/src/contrib/os.h new file mode 100644 index 0000000..8d4a2e2 --- /dev/null +++ b/src/contrib/os.h @@ -0,0 +1,37 @@ +/* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stdbool.h> +#include <stdio.h> +#include <sys/utsname.h> + +inline static bool linux_at_least(unsigned version_first, unsigned version_second) +{ +#if defined(__linux__) + struct utsname info; + unsigned first, second; + if (uname(&info) != 0 || sscanf(info.release, "%u.%u.", &first, &second) != 2) { + return false; + } else { + return first > version_first || + (first = version_first && second >= version_second); + } +#else + return false; +#endif +} diff --git a/src/contrib/proxyv2/proxyv2.c b/src/contrib/proxyv2/proxyv2.c new file mode 100644 index 0000000..0e5c90b --- /dev/null +++ b/src/contrib/proxyv2/proxyv2.c @@ -0,0 +1,281 @@ +/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + Copyright (C) 2021 Fastly, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <arpa/inet.h> +#include <stdint.h> +#include <string.h> + +#include "contrib/proxyv2/proxyv2.h" +#include "contrib/sockaddr.h" +#include "libknot/errcode.h" + +/* + * Minimal implementation of the haproxy PROXY v2 protocol. + * + * Supports extracting the original client address and client port number from + * the haproxy PROXY v2 protocol's address block. + * + * See https://www.haproxy.org/download/2.5/doc/proxy-protocol.txt for the + * protocol specification. + */ + +static const char PROXYV2_SIG[12] = "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A"; + +/* + * The part of the PROXY v2 payload following the signature. + */ +struct proxyv2_hdr { + /* + * The protocol version and command. + * + * The upper four bits contain the version which must be \x2 and the + * receiver must only accept this value. + * + * The lower four bits represent the command, which is \x0 for LOCAL + * and \x1 for PROXY. + */ + uint8_t ver_cmd; + + /* + * The transport protocol and address family. The upper four bits + * contain the address family and the lower four bits contain the + * protocol. + * + * The relevant values for DNS are: + * \x11: TCP over IPv4 + * \x12: UDP over IPv4 + * \x21: TCP over IPv6 + * \x22: UDP over IPv6 + */ + uint8_t fam_addr; + + /* + * The number of PROXY v2 payload bytes following this header to skip + * to reach the proxied packet (i.e., start of the original DNS message). + */ + uint16_t len; +}; + +/* + * The PROXY v2 address block for IPv4. + */ +struct proxyv2_addr_ipv4 { + uint8_t src_addr[4]; + uint8_t dst_addr[4]; + uint16_t src_port; + uint16_t dst_port; +}; + +/* + * The PROXY v2 address block for IPv6. + */ +struct proxyv2_addr_ipv6 { + uint8_t src_addr[16]; + uint8_t dst_addr[16]; + uint16_t src_port; + uint16_t dst_port; +}; + +const size_t PROXYV2_HEADER_MAXLEN = sizeof(PROXYV2_SIG) + + sizeof(struct proxyv2_hdr) + + sizeof(struct proxyv2_addr_ipv6); + +/* + * Make sure the C compiler lays out the PROXY v2 address block structs so that + * they can be memcpy()'d off the wire. + */ +#if (__STDC_VERSION__ >= 201112L) +_Static_assert(sizeof(struct proxyv2_hdr) == 4, + "struct proxyv2_hdr is correct size"); +_Static_assert(sizeof(struct proxyv2_addr_ipv4) == 12, + "struct proxyv2_addr_ipv4 is correct size"); +_Static_assert(sizeof(struct proxyv2_addr_ipv6) == 36, + "struct proxyv2_addr_ipv6 is correct size"); +#endif + +int proxyv2_header_offset(void *base, size_t len_base) +{ + /* + * Check that 'base' has enough bytes to read the PROXY v2 signature + * and header, and if so whether the PROXY v2 signature is present. + */ + if (len_base < (sizeof(PROXYV2_SIG) + sizeof(struct proxyv2_hdr)) || + memcmp(base, PROXYV2_SIG, sizeof(PROXYV2_SIG)) != 0) + { + /* Failure. */ + return KNOT_EMALF; + } + + /* Read the PROXY v2 header. */ + struct proxyv2_hdr *hdr = base + sizeof(PROXYV2_SIG); + + /* + * Check that this is a version 2, command "PROXY" payload. + * + * XXX: The PROXY v2 spec mandates support for the "LOCAL" command + * (byte 0x20). + */ + if (hdr->ver_cmd != 0x21) { + /* Failure. */ + return KNOT_EMALF; + } + + /* + * Calculate the offset of the original DNS message inside the packet. + * This needs to account for the length of the PROXY v2 signature, + * PROXY v2 header, and the bytes of variable length PROXY v2 data + * following the PROXY v2 header. + */ + const size_t offset_dns = sizeof(PROXYV2_SIG) + + sizeof(struct proxyv2_hdr) + ntohs(hdr->len); + if (offset_dns < len_base) { + return offset_dns; + } + + return KNOT_EMALF; +} + +int proxyv2_addr_store(void *base, size_t len_base, struct sockaddr_storage *ss) +{ + /* + * Calculate the offset of the PROXY v2 address block. This is the data + * immediately following the PROXY v2 header. + */ + const size_t offset_proxy_addr = sizeof(PROXYV2_SIG) + + sizeof(struct proxyv2_hdr); + struct proxyv2_hdr *hdr = base + sizeof(PROXYV2_SIG); + + /* + * Handle proxied UDP-over-IPv4 and UDP-over-IPv6 packets. + */ + //TODO What about TCP? + if (hdr->fam_addr == 0x12) { + /* This is a proxied UDP-over-IPv4 packet. */ + struct proxyv2_addr_ipv4 *addr; + + /* + * Check that the packet is large enough to contain the IPv4 + * address block. + */ + if (offset_proxy_addr + sizeof(*addr) < len_base) { + /* Read the PROXY v2 address block. */ + addr = base + offset_proxy_addr; + + /* Copy the client's IPv4 address to the caller. */ + sockaddr_set_raw(ss, AF_INET, addr->src_addr, + sizeof(addr->src_addr)); + + /* Copy the client's port to the caller. */ + sockaddr_port_set(ss, ntohs(addr->src_port)); + + /* Success. */ + return KNOT_EOK; + } + } else if (hdr->fam_addr == 0x22) { + /* This is a proxied UDP-over-IPv6 packet. */ + struct proxyv2_addr_ipv6 *addr; + + /* + * Check that the packet is large enough to contain the IPv6 + * address block. + */ + if (offset_proxy_addr + sizeof(*addr) < len_base) { + /* Read the PROXY v2 address block. */ + addr = base + offset_proxy_addr; + + /* Copy the client's IPv6 address to the caller. */ + sockaddr_set_raw(ss, AF_INET6, addr->src_addr, + sizeof(addr->src_addr)); + + /* Copy the client's port to the caller. */ + sockaddr_port_set(ss, ntohs(addr->src_port)); + + /* Success. */ + return KNOT_EOK; + } + } + + /* Failure. */ + return KNOT_EMALF; +} + +int proxyv2_write_header(char *buf, size_t buflen, int socktype, const struct sockaddr *src, + const struct sockaddr *dst) +{ + if (buflen < PROXYV2_HEADER_MAXLEN) { + return KNOT_EINVAL; + } + + uint8_t fam_addr = 0; + int family = src->sa_family; + if (socktype == SOCK_DGRAM) { + fam_addr += 0x2; + } else if (socktype == SOCK_STREAM) { + fam_addr += 0x1; + } else { + return KNOT_EINVAL; + } + if (family == AF_INET) { + fam_addr += 0x10; + } else if (family == AF_INET6) { + fam_addr += 0x20; + } else { + return KNOT_EINVAL; + } + + struct proxyv2_hdr hdr = { + .ver_cmd = 0x21, + .fam_addr = fam_addr, + .len = (family == AF_INET) + ? htons(sizeof(struct proxyv2_addr_ipv4)) + : htons(sizeof(struct proxyv2_addr_ipv6)) + }; + + size_t offset = 0; + memcpy(buf, PROXYV2_SIG, sizeof(PROXYV2_SIG)); + offset += sizeof(PROXYV2_SIG); + memcpy(buf + offset, &hdr, sizeof(hdr)); + offset += sizeof(hdr); + + if (family == AF_INET) { + struct proxyv2_addr_ipv4 ipv4 = { 0 }; + struct sockaddr_in *p_src = (struct sockaddr_in *)src; + struct sockaddr_in *p_dst = (struct sockaddr_in *)dst; + memcpy(ipv4.src_addr, &p_src->sin_addr, sizeof(p_src->sin_addr)); + memcpy(ipv4.dst_addr, &p_dst->sin_addr, sizeof(p_dst->sin_addr)); + ipv4.src_port = p_src->sin_port; + ipv4.dst_port = p_dst->sin_port; + + // Store in buffer + memcpy(buf + offset, &ipv4, sizeof(ipv4)); + offset += sizeof(ipv4); + } else { + struct proxyv2_addr_ipv6 ipv6 = { 0 }; + struct sockaddr_in6 *p_src = (struct sockaddr_in6 *)src; + struct sockaddr_in6 *p_dst = (struct sockaddr_in6 *)dst; + memcpy(ipv6.src_addr, &p_src->sin6_addr, sizeof(p_src->sin6_addr)); + memcpy(ipv6.dst_addr, &p_dst->sin6_addr, sizeof(p_dst->sin6_addr)); + ipv6.src_port = p_src->sin6_port; + ipv6.dst_port = p_dst->sin6_port; + + // Store in buffer + memcpy(buf + offset, &ipv6, sizeof(ipv6)); + offset += sizeof(ipv6); + } + + return offset; +} diff --git a/src/contrib/proxyv2/proxyv2.h b/src/contrib/proxyv2/proxyv2.h new file mode 100644 index 0000000..8fd8299 --- /dev/null +++ b/src/contrib/proxyv2/proxyv2.h @@ -0,0 +1,29 @@ +/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stddef.h> +#include <sys/socket.h> + +extern const size_t PROXYV2_HEADER_MAXLEN; + +int proxyv2_header_offset(void *base, size_t len_base); + +int proxyv2_addr_store(void *base, size_t len_base, struct sockaddr_storage *ss); + +int proxyv2_write_header(char *buf, size_t buflen, int socktype, const struct sockaddr *src, + const struct sockaddr *dst); diff --git a/src/contrib/qp-trie/trie.c b/src/contrib/qp-trie/trie.c new file mode 100644 index 0000000..30dcb42 --- /dev/null +++ b/src/contrib/qp-trie/trie.c @@ -0,0 +1,1451 @@ +/* Copyright (C) 2019 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + Copyright (C) 2018 Tony Finch <dot@dotat.at> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + + The code originated from https://github.com/fanf2/qp/blob/master/qp.c + at revision 5f6d93753. + */ + +#include <assert.h> +#include <limits.h> +#include <stdlib.h> +#include <string.h> + +#include "contrib/qp-trie/trie.h" +#include "contrib/macros.h" +#include "contrib/mempattern.h" +#include "libknot/errcode.h" + +typedef unsigned int uint; +typedef uint64_t trie_index_t; /*!< nibble index into a key */ +typedef uint64_t word; /*!< A type-punned word */ +typedef uint bitmap_t; /*!< Bit-maps, using the range of 1<<0 to 1<<16 (inclusive). */ + +typedef char static_assert_pointer_fits_in_word + [sizeof(word) >= sizeof(uintptr_t) ? 1 : -1]; + +#define KEYLENBITS 31 + +/*! \brief trie keys have lengths + * + * 32 bits are enough for key lengths; probably even 16 bits would be. + * However, a 32 bit length means the alignment will be a multiple of + * 4, allowing us to stash the COW and BRANCH flags in the bottom bits + * of a pointer to a key. + * + * We need to steal a couple of bits from the length to keep the COW + * state of key allocations. + */ +typedef struct { + uint32_t cow:1, len:KEYLENBITS; + trie_key_t chars[]; +} tkey_t; + +/*! \brief A trie node is a pair of words. + * + * Each word is type-punned, depending on whether this is a branch + * node or a leaf node. We'll define some accessor functions to wrap + * this up into something reasonably safe. + * + * We aren't using a union to avoid problems with strict aliasing, and + * we aren't using bitfields because we want to control exactly which + * bits in the word are used by each field (in particular the flags). + * + * Branch nodes are never allocated individually: they are always part + * of either the root node or the twigs array of their parent branch. + * + * In a branch: + * + * `i` contains flags, bitmap, and index, explained in more detail below. + * + * `p` is a pointer to the "twigs", an array of child nodes. + * + * In a leaf: + * + * `i` is cast from a pointer to a tkey_t, with flags in the bottom bits. + * + * `p` is a trie_val_t. + */ +typedef struct node { + word i; + void *p; +} node_t; + +struct trie { + node_t root; // undefined when weight == 0, see empty_root() + size_t weight; + knot_mm_t mm; +}; + +/*! \brief size (in bits) of nibble (half-byte) indexes into keys + * + * The bottom bit is clear for the upper nibble, and set for the lower + * nibble, big-endian style, since the tree has to be in lexicographic + * order. The index increases from one branch node to the next as you + * go deeper into the trie. All the keys below a branch are identical + * up to the nibble identified by the branch. + * + * (see also tkey_t.len above) + */ +#define TWIDTH_INDEX 33 + +/*! \brief exclusive limit on indexes */ +#define TMAX_INDEX (BIG1 << TWIDTH_INDEX) + +/*! \brief size (in bits) of branch bitmap + * + * The bitmap indicates which subtries are present. The present child + * nodes are stored in the twigs array (with no holes between them). + * + * To simplify storing keys that are prefixes of each other, the + * end-of-string position is treated as an extra nibble value, ordered + * before all others. So there are 16 possible real nibble values, + * plus one value for nibbles past the end of the key. + */ +#define TWIDTH_BMP 17 + +/* + * We're constructing the layout of the branch `i` field in a careful + * way to avoid mistakes, getting the compiler to calculate values + * rather than typing them in by hand. + */ +enum { + TSHIFT_BRANCH = 0, + TSHIFT_COW, + TSHIFT_BMP, + TOP_BMP = TSHIFT_BMP + TWIDTH_BMP, + TSHIFT_INDEX = TOP_BMP, + TOP_INDEX = TSHIFT_INDEX + TWIDTH_INDEX, +}; + +typedef char static_assert_fields_fit_in_word + [TOP_INDEX <= sizeof(word) * CHAR_BIT ? 1 : -1]; + +typedef char static_assert_bmp_fits + [TOP_BMP <= sizeof(bitmap_t) * CHAR_BIT ? 1 : -1]; + +#define BIG1 ((word)1) +#define TMASK(width, shift) (((BIG1 << (width)) - BIG1) << (shift)) + +/*! \brief is this node a branch or a leaf? */ +#define TFLAG_BRANCH (BIG1 << TSHIFT_BRANCH) + +/*! \brief copy-on-write flag, used in both leaves and branches */ +#define TFLAG_COW (BIG1 << TSHIFT_COW) + +/*! \brief for extracting pointer to key */ +#define TMASK_LEAF (~(word)(TFLAG_BRANCH | TFLAG_COW)) + +/*! \brief mask for extracting nibble index */ +#define TMASK_INDEX TMASK(TWIDTH_INDEX, TSHIFT_INDEX) + +/*! \brief mask for extracting bitmap */ +#define TMASK_BMP TMASK(TWIDTH_BMP, TSHIFT_BMP) + +/*! \brief bitmap entry for NOBYTE */ +#define BMP_NOBYTE (BIG1 << TSHIFT_BMP) + +/*! \brief Initialize a new leaf, copying the key, and returning failure code. */ +static int mkleaf(node_t *leaf, const trie_key_t *key, uint32_t len, knot_mm_t *mm) +{ + if (unlikely((word)len > (BIG1 << KEYLENBITS))) + return KNOT_ENOMEM; + tkey_t *lkey = mm_alloc(mm, sizeof(tkey_t) + len); + if (unlikely(!lkey)) + return KNOT_ENOMEM; + lkey->cow = 0; + lkey->len = len; + memcpy(lkey->chars, key, len); + word i = (uintptr_t)lkey; + assert((i & TFLAG_BRANCH) == 0); + *leaf = (node_t){ .i = i, .p = NULL }; + return KNOT_EOK; +} + +/*! \brief construct a branch node */ +static node_t mkbranch(trie_index_t index, bitmap_t bmp, node_t *twigs) +{ + word i = TFLAG_BRANCH | bmp + | (index << TSHIFT_INDEX); + assert(index < TMAX_INDEX); + assert((bmp & ~TMASK_BMP) == 0); + return (node_t){ .i = i, .p = twigs }; +} + +/*! \brief Make an empty root node. */ +static node_t empty_root(void) +{ + return mkbranch(TMAX_INDEX-1, 0, NULL); +} + +/*! \brief Propagate error codes. */ +#define ERR_RETURN(x) \ + do { \ + int err_code_ = x; \ + if (unlikely(err_code_ != KNOT_EOK)) \ + return err_code_; \ + } while (false) + + +/*! \brief Test flags to determine type of this node. */ +static bool isbranch(const node_t *t) +{ + return t->i & TFLAG_BRANCH; +} + +static tkey_t *tkey(const node_t *t) +{ + assert(!isbranch(t)); + return (tkey_t *)(uintptr_t)(t->i & TMASK_LEAF); +} + +static trie_val_t *tvalp(node_t *t) +{ + assert(!isbranch(t)); + return &t->p; +} + +/*! \brief Given a branch node, return the index of the corresponding nibble in the key. */ +static trie_index_t branch_index(const node_t *t) +{ + assert(isbranch(t)); + return (t->i & TMASK_INDEX) >> TSHIFT_INDEX; +} + +static bitmap_t branch_bmp(const node_t *t) +{ + assert(isbranch(t)); + return (t->i & TMASK_BMP); +} + +/*! + * \brief Count the number of set bits. + * + * \TODO This implementation may be relatively slow on some HW. + */ +static uint branch_weight(const node_t *t) +{ + assert(isbranch(t)); + uint n = __builtin_popcount(t->i & TMASK_BMP); + assert(n > 1 && n <= TWIDTH_BMP); + return n; +} + +/*! \brief Compute offset of an existing child in a branch node. */ +static uint twigoff(const node_t *t, bitmap_t bit) +{ + assert(isbranch(t)); + assert(__builtin_popcount(bit) == 1); + return __builtin_popcount(t->i & TMASK_BMP & (bit - 1)); +} + +/*! \brief Extract a nibble from a key and turn it into a bitmask. */ +static bitmap_t keybit(trie_index_t ni, const trie_key_t *key, uint32_t len) +{ + trie_index_t bytei = ni >> 1; + + if (bytei >= len) + return BMP_NOBYTE; + + uint8_t ki = (uint8_t)key[bytei]; + uint nibble = (ni & 1) ? (ki & 0xf) : (ki >> 4); + + // skip one for NOBYTE nibbles after the end of the key + return BIG1 << (nibble + 1 + TSHIFT_BMP); +} + +/*! \brief Extract a nibble from a key and turn it into a bitmask. */ +static bitmap_t twigbit(const node_t *t, const trie_key_t *key, uint32_t len) +{ + assert(isbranch(t)); + return keybit(branch_index(t), key, len); +} + +/*! \brief Test if a branch node has a child indicated by a bitmask. */ +static bool hastwig(const node_t *t, bitmap_t bit) +{ + assert(isbranch(t)); + assert((bit & ~TMASK_BMP) == 0); + assert(__builtin_popcount(bit) == 1); + return t->i & bit; +} + +/*! \brief Get pointer to packed array of child nodes. */ +static node_t* twigs(node_t *t) +{ + assert(isbranch(t)); + return t->p; +} + +/*! \brief Get pointer to a particular child of a branch node. */ +static node_t* twig(node_t *t, uint i) +{ + assert(i < branch_weight(t)); + return twigs(t) + i; +} + +/*! \brief Get twig number of a child node TODO: better description. */ +static uint twig_number(node_t *child, node_t *parent) +{ + // twig array index using pointer arithmetic + ptrdiff_t num = child - twigs(parent); + assert(num >= 0 && num < branch_weight(parent)); + return (uint)num; +} + +/*! \brief Simple string comparator. */ +static int key_cmp(const trie_key_t *k1, uint32_t k1_len, + const trie_key_t *k2, uint32_t k2_len) +{ + int ret = memcmp(k1, k2, MIN(k1_len, k2_len)); + if (ret != 0) { + return ret; + } + + /* Key string is equal, compare lengths. */ + if (k1_len == k2_len) { + return 0; + } else if (k1_len < k2_len) { + return -1; + } else { + return 1; + } +} + +trie_t* trie_create(knot_mm_t *mm) +{ + trie_t *trie = mm_alloc(mm, sizeof(trie_t)); + if (trie != NULL) { + trie->root = empty_root(); + trie->weight = 0; + if (mm != NULL) + trie->mm = *mm; + else + mm_ctx_init(&trie->mm); + } + return trie; +} + +/*! \brief Free anything under the trie node, except for the passed pointer itself. */ +static void clear_trie(node_t *trie, knot_mm_t *mm) +{ + if (!isbranch(trie)) { + mm_free(mm, tkey(trie)); + } else { + uint n = branch_weight(trie); + for (uint i = 0; i < n; ++i) + clear_trie(twig(trie, i), mm); + mm_free(mm, twigs(trie)); + } +} + +void trie_free(trie_t *tbl) +{ + if (tbl == NULL) + return; + if (tbl->weight) + clear_trie(&tbl->root, &tbl->mm); + mm_free(&tbl->mm, tbl); +} + +void trie_clear(trie_t *tbl) +{ + assert(tbl); + if (!tbl->weight) + return; + clear_trie(&tbl->root, &tbl->mm); + tbl->root = empty_root(); + tbl->weight = 0; +} + +static bool dup_trie(node_t *copy, const node_t *orig, trie_dup_cb dup_cb, knot_mm_t *mm) +{ + if (isbranch(orig)) { + uint n = branch_weight(orig); + node_t *cotw = mm_alloc(mm, n * sizeof(*cotw)); + if (cotw == NULL) { + return NULL; + } + const node_t *ortw = twigs((node_t *)orig); + for (uint i = 0; i < n; ++i) { + if (!dup_trie(cotw + i, ortw + i, dup_cb, mm)) { + while (i-- > 0) { + clear_trie(cotw + i, mm); + } + mm_free(mm, cotw); + return false; + } + } + *copy = mkbranch(branch_index(orig), branch_bmp(orig), cotw); + } else { + tkey_t *key = tkey(orig); + if (mkleaf(copy, key->chars, key->len, mm) != KNOT_EOK) { + return false; + } + if ((copy->p = dup_cb(orig->p, mm)) == NULL) { + mm_free(mm, tkey(copy)); + return false; + } + } + return true; +} + +trie_t* trie_dup(const trie_t *orig, trie_dup_cb dup_cb, knot_mm_t *mm) +{ + if (orig == NULL) { + return NULL; + } + trie_t *copy = mm_alloc(mm, sizeof(*copy)); + if (copy == NULL) { + return NULL; + } + copy->weight = orig->weight; + if (mm != NULL) { + copy->mm = *mm; + } else { + mm_ctx_init(©->mm); + } + if (copy->weight) { + if (!dup_trie(©->root, &orig->root, dup_cb, mm)) { + mm_free(mm, copy); + return NULL; + } + } + return copy; +} + +size_t trie_weight(const trie_t *tbl) +{ + assert(tbl); + return tbl->weight; +} + +trie_val_t* trie_get_try(trie_t *tbl, const trie_key_t *key, uint32_t len) +{ + assert(tbl); + if (!tbl->weight) + return NULL; + node_t *t = &tbl->root; + while (isbranch(t)) { + __builtin_prefetch(twigs(t)); + bitmap_t b = twigbit(t, key, len); + if (!hastwig(t, b)) + return NULL; + t = twig(t, twigoff(t, b)); + } + tkey_t *lkey = tkey(t); + if (key_cmp(key, len, lkey->chars, lkey->len) != 0) + return NULL; + return tvalp(t); +} + +/* Optimization: the approach isn't ideal, as e.g. walking through the prefix + * is duplicated and we explicitly construct the wildcard key. Still, it's close + * to optimum which would be significantly more complicated and error-prone to write. */ +trie_val_t* trie_get_try_wildcard(trie_t *tbl, const trie_key_t *key, uint32_t len) +{ + assert(tbl); + if (!tbl->weight) + return NULL; + // Find leaf sharing the longest common prefix; see ns_find_branch() for explanation. + node_t *t = &tbl->root; + while (isbranch(t)) { + __builtin_prefetch(twigs(t)); + bitmap_t b = twigbit(t, key, len); + uint i = hastwig(t, b) ? twigoff(t, b) : 0; + t = twig(t, i); + } + const tkey_t * const lcp_key = tkey(t); + + // Find the last matching zero byte or -1 (source of synthesis) + int i_lmz = -1; + for (int i = 0; i < len && i < lcp_key->len && key[i] == lcp_key->chars[i]; ++i) { + if (key[i] == '\0' && i < len - 1) // do not count the terminating zero + i_lmz = i; + // Shortcut: we may have found an exact match. + if (i == len - 1 && len == lcp_key->len) + return tvalp(t); + } + if (len == 0) // The empty name needs separate handling. + return lcp_key->len == 0 ? tvalp(t) : NULL; + + // Construct the key of the wildcard we need and look it up. + const int wild_len = i_lmz + 3; + uint8_t wild_key[wild_len]; + memcpy(wild_key, key, wild_len - 2); + wild_key[wild_len - 2] = '*'; + wild_key[wild_len - 1] = '\0'; // LF is always 0-terminated ATM + return trie_get_try(tbl, wild_key, wild_len); +} + +/*! \brief Delete leaf t with parent p; b is the bit for t under p. + * Optionally return the deleted value via val. The function can't fail. */ +static void del_found(trie_t *tbl, node_t *t, node_t *p, bitmap_t b, trie_val_t *val) +{ + assert(!tkey(t)->cow); + mm_free(&tbl->mm, tkey(t)); + if (val != NULL) + *val = *tvalp(t); // we return trie_val_t directly when deleting + --tbl->weight; + if (unlikely(!p)) { // whole trie was a single leaf + assert(tbl->weight == 0); + tbl->root = empty_root(); + return; + } + // remove leaf t as child of p + node_t *tp = twigs(p); + uint ci = twig_number(t, p); + uint cc = branch_weight(p); // child count + + if (cc == 2) { + // collapse binary node p: move the other child to the parent + *p = tp[1 - ci]; + mm_free(&tbl->mm, tp); + return; + } + memmove(tp + ci, tp + ci + 1, sizeof(node_t) * (cc - ci - 1)); + p->i &= ~b; + node_t *newt = mm_realloc(&tbl->mm, tp, sizeof(node_t) * (cc - 1), + sizeof(node_t) * cc); + if (likely(newt != NULL)) + p->p = newt; + // We can ignore mm_realloc failure because an oversized twig + // array is OK - only beware that next time the prev_size + // passed to mm_realloc will not be correct; TODO? +} + +int trie_del(trie_t *tbl, const trie_key_t *key, uint32_t len, trie_val_t *val) +{ + assert(tbl); + if (!tbl->weight) + return KNOT_ENOENT; + node_t *t = &tbl->root; // current and parent node + node_t *p = NULL; + bitmap_t b = 0; + while (isbranch(t)) { + __builtin_prefetch(twigs(t)); + b = twigbit(t, key, len); + if (!hastwig(t, b)) + return KNOT_ENOENT; + p = t; + t = twig(t, twigoff(t, b)); + } + tkey_t *lkey = tkey(t); + if (key_cmp(key, len, lkey->chars, lkey->len) != 0) + return KNOT_ENOENT; + del_found(tbl, t, p, b, val); + return KNOT_EOK; +} + +/*! + * \brief Stack of nodes, storing a path down a trie. + * + * The structure also serves directly as the public trie_it_t type, + * in which case it always points to the current leaf, unless we've finished + * (i.e. it->len == 0). + * stack[0] is always a valid pointer to the root -> ns_gettrie() + */ +typedef struct trie_it { + node_t* *stack; /*!< The stack; malloc is used directly instead of mm. */ + uint32_t len; /*!< Current length of the stack. */ + uint32_t alen; /*!< Allocated/available length of the stack. */ + /*! \brief Initial storage for \a stack; it should fit in most use cases. */ + node_t* stack_init[250]; +} nstack_t; + +/*! \brief Create a node stack containing just the root (or empty). */ +static void ns_init(nstack_t *ns, trie_t *tbl) +{ + assert(tbl); + ns->stack = ns->stack_init; + ns->alen = sizeof(ns->stack_init) / sizeof(ns->stack_init[0]); + ns->stack[0] = &tbl->root; + ns->len = (tbl->weight > 0); +} + +static inline trie_t * ns_gettrie(nstack_t *ns) +{ + assert(ns && ns->stack && ns->stack[0]); + return (struct trie *)ns->stack[0]; +} + +/*! \brief Free inside of the stack, i.e. not the passed pointer itself. */ +static void ns_cleanup(nstack_t *ns) +{ + assert(ns && ns->stack); + if (likely(ns->stack == ns->stack_init)) + return; + free(ns->stack); + #ifndef NDEBUG + ns->stack = NULL; + ns->alen = 0; + #endif +} + +/*! \brief Allocate more space for the stack. */ +static int ns_longer_alloc(nstack_t *ns) +{ + ns->alen *= 2; + size_t new_size = ns->alen * sizeof(node_t *); + node_t **st; + if (ns->stack == ns->stack_init) { + st = malloc(new_size); + if (st != NULL) + memcpy(st, ns->stack, ns->len * sizeof(node_t *)); + } else { + st = realloc(ns->stack, new_size); + } + if (st == NULL) + return KNOT_ENOMEM; + ns->stack = st; + return KNOT_EOK; +} + +/*! \brief Ensure the node stack can be extended by one. */ +static inline int ns_longer(nstack_t *ns) +{ + // get a longer stack if needed + if (likely(ns->len < ns->alen)) + return KNOT_EOK; + return ns_longer_alloc(ns); // hand-split the part suitable for inlining +} + +/*! + * \brief Find the "branching point" as if searching for a key. + * + * The whole path to the point is kept on the passed stack; + * always at least the root will remain on the top of it. + * Beware: the precise semantics of this function is rather tricky. + * The top of the stack will contain: the corresponding leaf if exact + * match is found; or the immediate node below a + * branching-point-on-edge or the branching-point itself. + * + * \param idiff Set the index of first differing nibble, or TMAX_INDEX for an exact match + * \param tbit Set the bit of the closest leaf's nibble at index idiff + * \param kbit Set the bit of the key's nibble at index idiff + * + * \return KNOT_EOK or KNOT_ENOMEM. + */ +static int ns_find_branch(nstack_t *ns, const trie_key_t *key, uint32_t len, + trie_index_t *idiff, bitmap_t *tbit, bitmap_t *kbit) +{ + assert(ns && ns->len && idiff); + // First find some leaf with longest matching prefix. + while (isbranch(ns->stack[ns->len - 1])) { + ERR_RETURN(ns_longer(ns)); + node_t *t = ns->stack[ns->len - 1]; + __builtin_prefetch(twigs(t)); + bitmap_t b = twigbit(t, key, len); + // Even if our key is missing from this branch we need to + // keep iterating down to a leaf. It doesn't matter which + // twig we choose since the keys are all the same up to this + // index. Note that blindly using twigoff(t, b) can cause + // an out-of-bounds index if it equals twigmax(t). + uint i = hastwig(t, b) ? twigoff(t, b) : 0; + ns->stack[ns->len++] = twig(t, i); + } + tkey_t *lkey = tkey(ns->stack[ns->len-1]); + // Find index of the first char that differs. + size_t bytei = 0; + uint32_t klen = lkey->len; + for (bytei = 0; bytei < MIN(len,klen); bytei++) { + if (key[bytei] != lkey->chars[bytei]) + break; + } + // Find which half-byte has matched. + trie_index_t index = bytei << 1; + if (bytei == len && len == lkey->len) { // found equivalent key + index = TMAX_INDEX; + goto success; + } + if (likely(bytei < MIN(len,klen))) { + uint8_t k2 = (uint8_t)lkey->chars[bytei]; + uint8_t k1 = (uint8_t)key[bytei]; + if (((k1 ^ k2) & 0xf0) == 0) + index += 1; + } + // now go up the trie from the current leaf + node_t *t; + do { + if (unlikely(ns->len == 1)) + goto success; // only the root stays on the stack + t = ns->stack[ns->len - 2]; + if (branch_index(t) < index) + goto success; + --ns->len; + } while (true); +success: + #ifndef NDEBUG // invariants on successful return + assert(ns->len); + if (isbranch(ns->stack[ns->len - 1])) { + t = ns->stack[ns->len - 1]; + assert(branch_index(t) >= index); + } + if (ns->len > 1) { + t = ns->stack[ns->len - 2]; + assert(branch_index(t) < index || index == TMAX_INDEX); + } + #endif + *idiff = index; + *tbit = keybit(index, lkey->chars, lkey->len); + *kbit = keybit(index, key, len); + return KNOT_EOK; +} + +/*! + * \brief Advance the node stack to the last leaf in the subtree. + * + * \return KNOT_EOK or KNOT_ENOMEM. + */ +static int ns_last_leaf(nstack_t *ns) +{ + assert(ns); + do { + ERR_RETURN(ns_longer(ns)); + node_t *t = ns->stack[ns->len - 1]; + if (!isbranch(t)) + return KNOT_EOK; + uint lasti = branch_weight(t) - 1; + ns->stack[ns->len++] = twig(t, lasti); + } while (true); +} + +/*! + * \brief Advance the node stack to the first leaf in the subtree. + * + * \return KNOT_EOK or KNOT_ENOMEM. + */ +static int ns_first_leaf(nstack_t *ns) +{ + assert(ns && ns->len); + do { + ERR_RETURN(ns_longer(ns)); + node_t *t = ns->stack[ns->len - 1]; + if (!isbranch(t)) + return KNOT_EOK; + ns->stack[ns->len++] = twig(t, 0); + } while (true); +} + +/*! + * \brief Advance the node stack to the leaf that is previous to the current node. + * + * \note Prefix leaf under the current node DOES count (if present; perhaps questionable). + * \return KNOT_EOK on success, KNOT_ENOENT on not-found, or possibly KNOT_ENOMEM. + */ +static int ns_prev_leaf(nstack_t *ns) +{ + assert(ns && ns->len > 0); + + node_t *t = ns->stack[ns->len - 1]; + // Beware: BMP_NOBYTE child is ordered *before* its parent. + if (isbranch(t) && hastwig(t, BMP_NOBYTE)) { + ERR_RETURN(ns_longer(ns)); + ns->stack[ns->len++] = twig(t, 0); + return KNOT_EOK; + } + + for (; ns->len >= 2; --ns->len) { + t = ns->stack[ns->len - 1]; + node_t *p = ns->stack[ns->len - 2]; + uint ci = twig_number(t, p); + if (ci == 0) // we've got to go up again + continue; + // t isn't the first child -> go down the previous one + ns->stack[ns->len - 1] = twig(p, ci - 1); + return ns_last_leaf(ns); + } + return KNOT_ENOENT; // root without empty key has no previous leaf +} + +/*! + * \brief Advance the node stack to the leaf that is successor to the current node. + * + * \param skip_prefixed skip any nodes whose key is a prefix of the current one. + * If false, prefix leaf or anything else under the current node DOES count. + * \return KNOT_EOK on success, KNOT_ENOENT on not-found, or possibly KNOT_ENOMEM. + */ +static int ns_next_leaf(nstack_t *ns, const bool skip_pefixed) +{ + assert(ns && ns->len > 0); + + node_t *t = ns->stack[ns->len - 1]; + if (!skip_pefixed && isbranch(t)) + return ns_first_leaf(ns); + for (; ns->len >= 2; --ns->len) { + t = ns->stack[ns->len - 1]; + node_t *p = ns->stack[ns->len - 2]; + uint ci = twig_number(t, p); + if (skip_pefixed && ci == 0 && hastwig(t, BMP_NOBYTE)) { + // Keys in the subtree of p are suffixes of the key of t, + // so we've got to go one level higher + // (this can't happen more than once) + continue; + } + uint cc = branch_weight(p); + assert(ci + 1 <= cc); + if (ci + 1 == cc) { + // t is the last child of p, so we need to keep climbing + continue; + } + // go down the next child of p + ns->stack[ns->len - 1] = twig(p, ci + 1); + return ns_first_leaf(ns); + } + return KNOT_ENOENT; // not found, as no more parent is available +} + +/*! \brief Advance the node stack to leaf with longest prefix of the current key. */ +static int ns_prefix(nstack_t *ns) +{ + assert(ns && ns->len > 0); + const node_t *start = ns->stack[ns->len - 1]; + // Walk up the trie until we find a BMP_NOBYTE child. + while (--ns->len > 0) { + node_t *p = ns->stack[ns->len - 1]; + if (!hastwig(p, BMP_NOBYTE)) + continue; + node_t *end = twig(p, 0); + // In case we started in a BMP_NOBYTE leaf, the first step up + // did NOT shorten the key and we would get back into the same + // node again. + if (end == start) + continue; + ns->stack[ns->len++] = end; + return KNOT_EOK; + } + return KNOT_ENOENT; // not found, as no more parent is available +} + +/*! \brief less-or-equal search. + * + * \return KNOT_EOK for exact match, 1 for previous, KNOT_ENOENT for not-found, + * or KNOT_E*. + */ +static int ns_get_leq(nstack_t *ns, const trie_key_t *key, uint32_t len) +{ + // First find the key with longest-matching prefix + trie_index_t idiff; + bitmap_t tbit, kbit; + ERR_RETURN(ns_find_branch(ns, key, len, &idiff, &tbit, &kbit)); + node_t *t = ns->stack[ns->len - 1]; + if (idiff == TMAX_INDEX) // found exact match + return KNOT_EOK; + // Get t: the last node on matching path + bitmap_t b; + if (isbranch(t) && branch_index(t) == idiff) { + // t is OK + b = kbit; + } else { + // the top of the stack was the first unmatched node -> step up + if (ns->len == 1) { + // root was unmatched already + if (kbit < tbit) + return KNOT_ENOENT; + ERR_RETURN(ns_last_leaf(ns)); + return 1; + } + --ns->len; + t = ns->stack[ns->len - 1]; + b = twigbit(t, key, len); + } + // Now we re-do the first "non-matching" step in the trie + // but try the previous child if key was less (it may not exist) + int i = hastwig(t, b) + ? (int)twigoff(t, b) - (kbit < tbit) + : (int)twigoff(t, b) - 1 /* twigoff returns successor when !hastwig */; + if (i >= 0) { + ERR_RETURN(ns_longer(ns)); + ns->stack[ns->len++] = twig(t, i); + ERR_RETURN(ns_last_leaf(ns)); + } else { + ERR_RETURN(ns_prev_leaf(ns)); + } + return 1; +} + +int trie_get_leq(trie_t *tbl, const trie_key_t *key, uint32_t len, trie_val_t **val) +{ + assert(tbl && val); + if (tbl->weight == 0) { + if (val) *val = NULL; + return KNOT_ENOENT; + } + // We try to do without malloc. + nstack_t ns_local; + ns_init(&ns_local, tbl); + nstack_t *ns = &ns_local; + + int ret = ns_get_leq(ns, key, len); + if (ret == KNOT_EOK || ret == 1) { + assert(!isbranch(ns->stack[ns->len - 1])); + if (val) *val = tvalp(ns->stack[ns->len - 1]); + } else { + if (val) *val = NULL; + } + ns_cleanup(ns); + return ret; +} + +int trie_it_get_leq(trie_it_t *it, const trie_key_t *key, uint32_t len) +{ + assert(it && it->stack[0] && it->alen); + const trie_t *tbl = ns_gettrie(it); + if (tbl->weight == 0) { + it->len = 0; + return KNOT_ENOENT; + } + it->len = 1; + int ret = ns_get_leq(it, key, len); + if (ret == KNOT_EOK || ret == 1) { + assert(trie_it_key(it, NULL)); + } else { + it->len = 0; + } + return ret; +} + +/* see below */ +static int cow_pushdown(trie_cow_t *cow, nstack_t *ns); + +/*! \brief implementation of trie_get_ins() and trie_get_cow() */ +static trie_val_t* cow_get_ins(trie_cow_t *cow, trie_t *tbl, + const trie_key_t *key, uint32_t len) +{ + assert(tbl); + // First leaf in an empty tbl? + if (unlikely(!tbl->weight)) { + if (unlikely(mkleaf(&tbl->root, key, len, &tbl->mm))) + return NULL; + ++tbl->weight; + return tvalp(&tbl->root); + } + { // Intentionally un-indented; until end of function, to bound cleanup attr. + // Find the branching-point + __attribute__((cleanup(ns_cleanup))) + nstack_t ns_local; + ns_init(&ns_local, tbl); + nstack_t *ns = &ns_local; + trie_index_t idiff; + bitmap_t tbit, kbit; + if (unlikely(ns_find_branch(ns, key, len, &idiff, &tbit, &kbit))) + return NULL; + if (unlikely(cow && cow_pushdown(cow, ns) != KNOT_EOK)) + return NULL; + node_t *t = ns->stack[ns->len - 1]; + if (idiff == TMAX_INDEX) // the same key was already present + return tvalp(t); + node_t leaf, *leafp; + if (unlikely(mkleaf(&leaf, key, len, &tbl->mm))) + return NULL; + + if (isbranch(t) && branch_index(t) == idiff) { + // The node t needs a new leaf child. + assert(!hastwig(t, kbit)); + // new child position and original child count + uint s = twigoff(t, kbit); + uint m = branch_weight(t); + node_t *nt = mm_realloc(&tbl->mm, twigs(t), + sizeof(node_t) * (m + 1), sizeof(node_t) * m); + if (unlikely(!nt)) + goto err_leaf; + memmove(nt + s + 1, nt + s, sizeof(node_t) * (m - s)); + leafp = nt + s; + *t = mkbranch(idiff, branch_bmp(t) | kbit, nt); + } else { + // We need to insert a new binary branch with leaf at *t. + // Note: it works the same for the case where we insert above root t. + #ifndef NDEBUG + if (ns->len > 1) { + node_t *pt = ns->stack[ns->len - 2]; + assert(hastwig(pt, twigbit(pt, key, len))); + } + #endif + node_t *nt = mm_alloc(&tbl->mm, sizeof(node_t) * 2); + if (unlikely(!nt)) + goto err_leaf; + node_t t2 = *t; // Save before overwriting t. + *t = mkbranch(idiff, tbit | kbit, nt); + *twig(t, twigoff(t, tbit)) = t2; + leafp = twig(t, twigoff(t, kbit)); + }; + *leafp = leaf; + ++tbl->weight; + return tvalp(leafp); +err_leaf: + mm_free(&tbl->mm, tkey(&leaf)); + return NULL; + } +} + +trie_val_t* trie_get_ins(trie_t *tbl, const trie_key_t *key, uint32_t len) +{ + return cow_get_ins(NULL, tbl, key, len); +} + +/*! \brief Apply a function to every trie_val_t*, in order; a recursive solution. */ +static int apply_nodes(node_t *t, int (*f)(trie_val_t *, void *), void *d) +{ + assert(t); + if (!isbranch(t)) + return f(tvalp(t), d); + uint n = branch_weight(t); + for (uint i = 0; i < n; ++i) + ERR_RETURN(apply_nodes(twig(t, i), f, d)); + return KNOT_EOK; +} + +int trie_apply(trie_t *tbl, int (*f)(trie_val_t *, void *), void *d) +{ + assert(tbl && f); + if (!tbl->weight) + return KNOT_EOK; + return apply_nodes(&tbl->root, f, d); +} + +/* These are all thin wrappers around static Tns* functions. */ +trie_it_t* trie_it_begin(trie_t *tbl) +{ + assert(tbl); + trie_it_t *it = malloc(sizeof(nstack_t)); + if (!it) + return NULL; + ns_init(it, tbl); + if (it->len == 0) // empty tbl + return it; + if (ns_first_leaf(it)) { + ns_cleanup(it); + free(it); + return NULL; + } + return it; +} + +bool trie_it_finished(trie_it_t *it) +{ + assert(it); + return it->len == 0; +} + +void trie_it_free(trie_it_t *it) +{ + if (!it) + return; + ns_cleanup(it); + free(it); +} + +trie_it_t *trie_it_clone(const trie_it_t *it) +{ + if (!it) // TODO: or should that be an assertion? + return NULL; + trie_it_t *it2 = malloc(sizeof(nstack_t)); + if (!it2) + return NULL; + it2->len = it->len; + it2->alen = it->alen; // we _might_ change it in the rare malloc case, but... + if (likely(it->stack == it->stack_init)) { + it2->stack = it2->stack_init; + assert(it->alen == sizeof(it->stack_init) / sizeof(it->stack_init[0])); + } else { + it2->stack = malloc(it2->alen * sizeof(it2->stack[0])); + if (!it2->stack) { + free(it2); + return NULL; + } + } + memcpy(it2->stack, it->stack, it->len * sizeof(it->stack[0])); + return it2; +} + +const trie_key_t* trie_it_key(trie_it_t *it, size_t *len) +{ + assert(it && it->len); + node_t *t = it->stack[it->len - 1]; + assert(!isbranch(t)); + tkey_t *key = tkey(t); + if (len) + *len = key->len; + return key->chars; +} + +trie_val_t* trie_it_val(trie_it_t *it) +{ + assert(it && it->len); + node_t *t = it->stack[it->len - 1]; + assert(!isbranch(t)); + return tvalp(t); +} + +void trie_it_next(trie_it_t *it) +{ + assert(it && it->len); + if (ns_next_leaf(it, false) != KNOT_EOK) + it->len = 0; +} + +void trie_it_next_loop(trie_it_t *it) +{ + assert(it && it->len); + int ret = ns_next_leaf(it, false); + if (ret == KNOT_ENOENT) { + it->len = 1; + ret = ns_first_leaf(it); + } + if (ret) + it->len = 0; +} + +void trie_it_next_nosuffix(trie_it_t *it) +{ + assert(it && it->len); + if (ns_next_leaf(it, true) != KNOT_EOK) + it->len = 0; +} + +void trie_it_prev(trie_it_t *it) +{ + assert(it && it->len); + if (ns_prev_leaf(it) != KNOT_EOK) + it->len = 0; +} + +void trie_it_prev_loop(trie_it_t *it) +{ + assert(it && it->len); + int ret = ns_prev_leaf(it); + if (ret == KNOT_ENOENT) { + it->len = 1; + ret = ns_last_leaf(it); + } + if (ret) + it->len = 0; +} + +void trie_it_parent(trie_it_t *it) +{ + assert(it && it->len); + if (ns_prefix(it)) + it->len = 0; +} + +void trie_it_del(trie_it_t *it) +{ + assert(it && it->len); + if (it->len == 0) + return; + node_t *t = it->stack[it->len - 1]; + assert(!isbranch(t)); + bitmap_t b; // del_found() needs to know which bit to zero in the bitmap + node_t *p; + if (it->len == 1) { // deleting the root + p = NULL; + b = 0; // unused + } else { + p = it->stack[it->len - 2]; + assert(isbranch(p)); + size_t len; + const trie_key_t *key = trie_it_key(it, &len); + b = twigbit(p, key, len); + } + // We could trie_it_{next,prev,...}(it) now, in case we wanted that semantics. + it->len = 0; + del_found(ns_gettrie(it), t, p, b, NULL); +} + + +/*!\file + * + * \section About copy-on-write + * + * In these notes I'll use the term "object" to refer to either the + * twig array of a branch, or the application's data that is referred + * to by a leaf's trie_val_t pointer. Note that for COW we don't care + * about trie node_t structs themselves, but the objects that they + * point to. + * + * \subsection COW states + * + * During a COW transaction an object can be in one of three states: + * shared, only in the old trie, or only in the new trie. When a + * transaction is rolled back, the only-new objects are freed; when a + * transaction is committed the new trie takes the place of the old + * one and only-old objects are freed. + * + * \subsection branch marks and regions + * + * A branch object can be marked by setting the COW flag in the first + * element of its twig array. Marked branches partition the trie into + * regions; an object's state depends on its region. + * + * The unmarked branch objects between a trie's root and the marked + * branches (excluding the marked branches themselves) is exclusively + * owned: either old-only (if you started from the old root) or + * new-only (if you started from the new root). + * + * Marked branch objects, and all objects reachable from marked branch + * objects, are in the shared region accessible from both old and new + * roots. All branch objects below a marked branch must be unmarked. + * (That is, there is at most one marked branch object on any path + * from the root of a trie.) + * + * Branch nodes in the new-only region can be modified in place, in + * the same way as an original qp trie. Branch nodes in the old-only + * or shared regions must not be modified. + * + * \subsection app object states + * + * The app objects reachable from the new-only and old-only regions + * explicitly record their state in a way determined by the + * application. (These app objects are reachable from the old and new + * roots by traversing only unmarked branch objects.) + * + * The app objects reachable from marked branch objects are implicitly + * shared, but their state field has an indeterminate value. If an app + * object was previously touched by a rolled-back transaction it may + * be marked shared or old-only; if it was previously touched by a + * committed transaction it may be marked shared or new-only. + * + * \subsection key states + * + * The memory allocated for tkey_t objects also needs to track its + * sharing state. They have a "cow" flag to mark when they are shared. + * Keys are relatively lazily copied (to make them exclusive) when + * their leaf node is touched by a COW mutation. + * + * [An alternative technique might be to copy them more eagerly, in + * cow_pushdown(), which would avoid the need for a flag bit at the + * cost of more allocator churn in a transaction.] + * + * \subsection outside COW + * + * When a COW transaction is not in progress, there are no marked + * branch objects, so everything is exclusively owned. When a COW + * transaction is finished (committed or rolled back), the branch + * marks are removed. Since they are in the shared region, this branch + * cleanup is visible to both old and new tries. + * + * However the state of app objects is not clean between COW + * transactions. When a COW transaction is committed, we traverse the + * old-only region to find old-only app objects that should be freed + * (and vice versa for rollback). In general, there will be app + * objects that are only reachable from the new-only region, and that + * have a mixture of shared and new states. + */ + +/*! \brief Trie copy-on-write state */ +struct trie_cow { + trie_t *old; + trie_t *new; + trie_cb *mark_shared; + void *d; +}; + +/*! \brief is this a marked branch object */ +static bool cow_marked(node_t *t) +{ + return isbranch(t) && (twigs(t)->i & TFLAG_COW); +} + +/*! \brief is this a leaf with a marked key */ +static bool cow_key(node_t *t) +{ + return !isbranch(t) && tkey(t)->cow; +} + +/*! \brief remove mark from a branch object */ +static void clear_cow(node_t *t) +{ + assert(isbranch(t)); + twigs(t)->i &= ~TFLAG_COW; +} + +/*! \brief mark a node as shared + * + * For branches this marks the twig array (in COW terminology, the + * branch object); for leaves it uses the callback to mark the app + * object. + */ +static void mark_cow(trie_cow_t *cow, node_t *t) +{ + if (isbranch(t)) { + node_t *object = twigs(t); + object->i |= TFLAG_COW; + } else { + tkey_t *lkey = tkey(t); + lkey->cow = 1; + if (cow->mark_shared != NULL) { + trie_val_t *valp = tvalp(t); + cow->mark_shared(*valp, lkey->chars, lkey->len, cow->d); + } + } +} + +/*! \brief push exclusive COW region down one node */ +static int cow_pushdown_one(trie_cow_t *cow, node_t *t) +{ + uint cc = branch_weight(t); + node_t *nt = mm_alloc(&cow->new->mm, sizeof(node_t) * cc); + if (nt == NULL) + return KNOT_ENOMEM; + /* mark all the children */ + for (uint ci = 0; ci < cc; ++ci) + mark_cow(cow, twig(t, ci)); + /* this node must be unmarked in both old and new versions */ + clear_cow(t); + t->p = memcpy(nt, twigs(t), sizeof(node_t) * cc); + return KNOT_EOK; +} + +/*! \brief push exclusive COW region to cover a whole node stack */ +static int cow_pushdown(trie_cow_t *cow, nstack_t *ns) +{ + node_t *new_twigs = NULL; + node_t *old_twigs = NULL; + for (uint i = 0; i < ns->len; i++) { + /* if we did a pushdown on the previous iteration, we + need to update this stack entry so it points into + the parent's new twigs instead of the old ones */ + if (new_twigs != old_twigs) + ns->stack[i] = new_twigs + (ns->stack[i] - old_twigs); + if (cow_marked(ns->stack[i])) { + old_twigs = twigs(ns->stack[i]); + if (cow_pushdown_one(cow, ns->stack[i])) + return KNOT_ENOMEM; + new_twigs = twigs(ns->stack[i]); + } else { + new_twigs = NULL; + old_twigs = NULL; + /* ensure key is exclusively owned */ + if (cow_key(ns->stack[i])) { + node_t oleaf = *ns->stack[i]; + tkey_t *okey = tkey(&oleaf); + if(mkleaf(ns->stack[i], okey->chars, okey->len, + &cow->new->mm)) + return KNOT_ENOMEM; + ns->stack[i]->p = oleaf.p; + okey->cow = 0; + } + } + } + return KNOT_EOK; +} + +trie_cow_t* trie_cow(trie_t *old, trie_cb *mark_shared, void *d) +{ + knot_mm_t *mm = &old->mm; + trie_t *new = mm_alloc(mm, sizeof(trie_t)); + trie_cow_t *cow = mm_alloc(mm, sizeof(trie_cow_t)); + if (new == NULL || cow == NULL) { + mm_free(mm, new); + mm_free(mm, cow); + return NULL; + } + new->mm = old->mm; + new->root = old->root; + new->weight = old->weight; + cow->old = old; + cow->new = new; + cow->mark_shared = mark_shared; + cow->d = d; + if (old->weight) + mark_cow(cow, &old->root); + return cow; +} + +trie_t* trie_cow_new(trie_cow_t *cow) +{ + assert(cow != NULL); + return cow->new; +} + +trie_val_t* trie_get_cow(trie_cow_t *cow, const trie_key_t *key, uint32_t len) +{ + return cow_get_ins(cow, cow->new, key, len); +} + +int trie_del_cow(trie_cow_t *cow, const trie_key_t *key, uint32_t len, trie_val_t *val) +{ + trie_t *tbl = cow->new; + if (unlikely(!tbl->weight)) + return KNOT_ENOENT; + { // Intentionally un-indented; until end of function, to bound cleanup attr. + // Find the branching-point + __attribute__((cleanup(ns_cleanup))) + nstack_t ns_local; + ns_init(&ns_local, tbl); + nstack_t *ns = &ns_local; + trie_index_t idiff; + bitmap_t tbit, kbit; + ERR_RETURN(ns_find_branch(ns, key, len, &idiff, &tbit, &kbit)); + if (idiff != TMAX_INDEX) + return KNOT_ENOENT; + ERR_RETURN(cow_pushdown(cow, ns)); + node_t *t = ns->stack[ns->len - 1]; + node_t *p = ns->len >= 2 ? ns->stack[ns->len - 2] : NULL; + del_found(tbl, t, p, p ? twigbit(p, key, len) : 0, val); + } + return KNOT_EOK; +} + +/*! \brief clean up after a COW transaction, recursively */ +static void cow_cleanup(trie_cow_t *cow, node_t *t, trie_cb *cb, void *d) +{ + if (cow_marked(t)) { + // we have hit the shared region, so just reset the mark + clear_cow(t); + return; + } else if (isbranch(t)) { + // traverse and free the exclusive region + uint cc = branch_weight(t); + for (uint ci = 0; ci < cc; ++ci) + cow_cleanup(cow, twig(t, ci), cb, d); + mm_free(&cow->new->mm, twigs(t)); + return; + } else { + // application must decide how to clean up its values + tkey_t *lkey = tkey(t); + if (cb != NULL) { + trie_val_t *valp = tvalp(t); + cb(*valp, lkey->chars, lkey->len, d); + } + // clean up exclusively-owned keys + if (lkey->cow) + lkey->cow = 0; + else + mm_free(&cow->new->mm, lkey); + return; + } +} + +trie_t* trie_cow_commit(trie_cow_t *cow, trie_cb *cb, void *d) +{ + trie_t *ret = cow->new; + if (cow->old->weight) + cow_cleanup(cow, &cow->old->root, cb, d); + mm_free(&ret->mm, cow->old); + mm_free(&ret->mm, cow); + return ret; +} + +trie_t* trie_cow_rollback(trie_cow_t *cow, trie_cb *cb, void *d) +{ + trie_t *ret = cow->old; + if (cow->new->weight) + cow_cleanup(cow, &cow->new->root, cb, d); + mm_free(&ret->mm, cow->new); + mm_free(&ret->mm, cow); + return ret; +} diff --git a/src/contrib/qp-trie/trie.h b/src/contrib/qp-trie/trie.h new file mode 100644 index 0000000..d479d3e --- /dev/null +++ b/src/contrib/qp-trie/trie.h @@ -0,0 +1,280 @@ +/* Copyright (C) 2019 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + Copyright (C) 2018 Tony Finch <dot@dotat.at> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stdbool.h> +#include <stdint.h> + +#include "libknot/mm_ctx.h" + +/*! + * \brief Native API of QP-tries: + * + * - keys are uint8_t strings, not necessarily zero-terminated, + * the structure copies the contents of the passed keys + * - values are void* pointers, typically you get an ephemeral pointer to it + * - key lengths are limited by 2^32-1 ATM + */ + +/*! \brief Element value. */ +typedef void* trie_val_t; +/*! \brief Key for indexing tries. Sign could be flipped easily. */ +typedef uint8_t trie_key_t; + +/*! \brief Opaque structure holding a QP-trie. */ +typedef struct trie trie_t; + +/*! \brief Opaque type for holding a QP-trie iterator. */ +typedef struct trie_it trie_it_t; + +/*! \brief Callback for cloning trie values. */ +typedef trie_val_t (*trie_dup_cb)(const trie_val_t val, knot_mm_t *mm); + +/*! \brief Callback for performing actions on a trie leaf + * + * Used during copy-on-write transactions + * + * \param val The value of the element to be altered + * \param key The key of the element to be altered + * \param len The length of key + * \param d Additional user data + */ +typedef void trie_cb(trie_val_t val, const trie_key_t *key, size_t len, void *d); + +/*! \brief Opaque type for holding the copy-on-write state for a QP-trie. */ +typedef struct trie_cow trie_cow_t; + +/*! \brief Create a trie instance. */ +trie_t* trie_create(knot_mm_t *mm); + +/*! \brief Free a trie instance. */ +void trie_free(trie_t *tbl); + +/*! \brief Clear a trie instance (make it empty). */ +void trie_clear(trie_t *tbl); + +/*! \brief Create a clone of existing trie. */ +trie_t* trie_dup(const trie_t *orig, trie_dup_cb dup_cb, knot_mm_t *mm); + +/*! \brief Return the number of keys in the trie. */ +size_t trie_weight(const trie_t *tbl); + +/*! \brief Search the trie, returning NULL on failure. */ +trie_val_t* trie_get_try(trie_t *tbl, const trie_key_t *key, uint32_t len); + +/*! \brief Search the trie including DNS wildcard semantics, returning NULL on failure. + * + * \note We assume the key is in knot_dname_lf() format, i.e. labels are ordered + * from root to leaf and separated by zero bytes (and no other zeros are allowed). + * \note Beware that DNS wildcard matching is not exactly what normal people would expect. + */ +trie_val_t* trie_get_try_wildcard(trie_t *tbl, const trie_key_t *key, uint32_t len); + +/*! \brief Search the trie, inserting NULL trie_val_t on failure. */ +trie_val_t* trie_get_ins(trie_t *tbl, const trie_key_t *key, uint32_t len); + +/*! + * \brief Search for less-or-equal element. + * + * \param tbl Trie. + * \param key Searched key. + * \param len Key length. + * \param val (optional) Value found; it will be set to NULL if not found or errored. + * \return KNOT_EOK for exact match, 1 for previous, KNOT_ENOENT for not-found, + * or KNOT_E*. + */ +int trie_get_leq(trie_t *tbl, const trie_key_t *key, uint32_t len, trie_val_t **val); + +/*! + * \brief Apply a function to every trie_val_t, in order. + * + * \return KNOT_EOK if success or KNOT_E* if error. + */ +int trie_apply(trie_t *tbl, int (*f)(trie_val_t *, void *), void *d); + +/*! + * \brief Remove an item, returning KNOT_EOK if succeeded or KNOT_ENOENT if not found. + * + * If val!=NULL and deletion succeeded, the deleted value is set. + */ +int trie_del(trie_t *tbl, const trie_key_t *key, uint32_t len, trie_val_t *val); + + +/*! \brief Create a new iterator pointing to the first element (if any). + * + * trie_it_* functions deal with these iterators capable of walking and jumping + * over the trie. Note that any modification to key-set stored by the trie + * will in general invalidate all iterators and you will need to begin anew. + * (It won't be detected - you may end up reading freed memory, etc.) + */ +trie_it_t* trie_it_begin(trie_t *tbl); + +/*! \brief Test if the iterator has gone "past the end" (and points nowhere). */ +bool trie_it_finished(trie_it_t *it); + +/*! \brief Free any resources of the iterator. It's OK to call it on NULL. */ +void trie_it_free(trie_it_t *it); + +/*! \brief Copy the iterator. See the warning in trie_it_begin(). */ +trie_it_t *trie_it_clone(const trie_it_t *it); + +/*! + * \brief Return pointer to the key of the current element. + * + * \note The len is uint32_t internally but size_t is better for our usage + * as it is without an additional type conversion. + */ +const trie_key_t* trie_it_key(trie_it_t *it, size_t *len); + +/*! \brief Return pointer to the value of the current element (writable). */ +trie_val_t* trie_it_val(trie_it_t *it); + +/*! + * \brief Advance the iterator to the next element. + * + * Iteration is in ascending lexicographical order. + * In particular, the empty string would be considered as the very first. + * + * \TODO: in most iterator operations, ENOMEM is very unlikely + * but it leads to a _finished() iterator (silently). + * Perhaps the functions should simply return KNOT_E* + */ +void trie_it_next(trie_it_t *it); +/*! \brief Advance the iterator to the previous element. See trie_it_next(). */ +void trie_it_prev(trie_it_t *it); + +/*! \brief Advance iterator to the next element, looping to first after last. */ +void trie_it_next_loop(trie_it_t *it); +/*! \brief Advance iterator to the previous element, looping to last after first. */ +void trie_it_prev_loop(trie_it_t *it); + +/*! \brief Advance iterator to the next element while ignoring the subtree. + * + * \note Another formulation: skip keys that are prefixed by the current key. + * \TODO: name, maybe _unprefixed? The thing is that in the "subtree" meaning + * doesn't correspond to how the pointers go in the implementation, + * but we may not care much for implementation in the API... + */ +void trie_it_next_nosub(trie_it_t *it); + +/*! \brief Advance iterator to the longest prefix of the current key. + * + * \TODO: name, maybe _prefix? Arguments similar to _nosub vs. _unprefixed. + */ +void trie_it_parent(trie_it_t *it); + +/*! \brief trie_get_leq() but with an iterator. */ +int trie_it_get_leq(trie_it_t *it, const trie_key_t *key, uint32_t len); + +/*! \brief Remove the current element. The iterator will get trie_it_finished() */ +void trie_it_del(trie_it_t *it); + + +/*! \brief Start a COW transaction + * + * A copy-on-write transaction starts by obtaining a write lock (in + * your application code) followed by a call to trie_cow(). This + * creates a shared clone of the trie and saves both old and new roots + * in the COW context. + * + * During the COW transaction, you call trie_cow_ins() or + * trie_cow_del() as necessary. These calls ensure that the relevant + * parts of the (new) trie are copied so that they can be modified + * freely. + * + * Your trie_val_t objects must be able to distinguish their + * reachability, either shared, or old-only, or new-only. Before a COW + * transaction the reachability of your objects is indeterminate. + * During a transaction, any trie_val_t objects that might be affected + * (because they are adjacent to a trie_get_cow() or trie_del_cow()) + * are first marked as shared using the callback you pass to + * trie_cow(). + * + * When the transaction is complete, to commit, call trie_cow_new() to + * get the new root, swap the old and new trie roots (e.g. with + * rcu_xchg_pointer()), wait for readers to finish with the old trie + * (e.g. using synchronize_rcu()), then call trie_cow_commit(). For a + * rollback, you can just call trie_cow_rollback() without waiting + * since that doesn't conflict with readers. After trie_cow_commit() + * or trie_cow_rollback() have finished, you can release your write + * lock. + * + * Concurrent reading of the old trie is allowed during a transaction + * provided that it is known when all readers have finished with the + * old version, e.g. using rcu_read_lock() and rcu_read_unlock(). + * There must be only one write transaction at a time. + * + * \param old the old trie + * \param mark_shared callback to mark a leaf as shared (can be NULL) + * \param d extra data for the callback + * \return a pointer to a COW context, + * or NULL if there was a failure + */ +trie_cow_t* trie_cow(trie_t *old, trie_cb *mark_shared, void *d); + +/*! \brief get the new trie from a COW context */ +trie_t* trie_cow_new(trie_cow_t *cow); + +/*! \brief variant of trie_get_ins() for use during COW transactions + * + * As necessary, this copies path from the root of the trie to the + * leaf, so that it is no longer shared. Any leaves adjacent to this + * path are marked as shared using the mark_shared callback passed to + * trie_cow(). + * + * It is your responsibility to COW your trie_val_t objects. If you copy an + * object you must change the original's reachability from shared to old-only. + * New objects (including copies) must have new-only reachability. + */ +trie_val_t* trie_get_cow(trie_cow_t *cow, const trie_key_t *key, uint32_t len); + +/*! + * \brief variant of trie_del() for use during COW transactions + * + * The mark_shared callback is invoked as necessary, in the same way + * as trie_get_cow(). + * + * Returns KNOT_EOK if the key was removed or KNOT_ENOENT if not found. + * If val!=NULL and deletion succeeded, the *val is set to the deleted + * value pointer. + */ +int trie_del_cow(trie_cow_t *cow, const trie_key_t *key, uint32_t len, trie_val_t *val); + +/*! \brief clean up the old trie after committing a COW transaction + * + * Your callback is invoked for any trie_val_t objects that might need + * cleaning up; you must free any objects you have marked as old-only + * and retain objects with shared reachability. + * + * \note The callback can be NULL. + * + * The cow object is free()d, and the new trie root is returned. + */ +trie_t* trie_cow_commit(trie_cow_t *cow, trie_cb *cb, void *d); + +/*! \brief clean up the new trie after rolling back a COW transaction + * + * Your callback is invoked for any trie_val_t objects that might need + * cleaning up; you must free any objects you have marked as new-only + * and retain objects with shared reachability. + * + * \note The callback can be NULL. + * + * The cow object is free()d, and the old trie root is returned. + */ +trie_t* trie_cow_rollback(trie_cow_t *cow, trie_cb *cb, void *d); diff --git a/src/contrib/semaphore.c b/src/contrib/semaphore.c new file mode 100644 index 0000000..1953fc3 --- /dev/null +++ b/src/contrib/semaphore.c @@ -0,0 +1,134 @@ +/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "semaphore.h" + +#include <assert.h> +#include <limits.h> +#include <stdlib.h> + +#if defined(__APPLE__) +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + +#define SEM_STATUS_POSIX INT_MIN + +void knot_sem_init(knot_sem_t *sem, int value) +{ + assert((sem != NULL) && (value != SEM_STATUS_POSIX)); + if (value < 0) { + goto nonposix; + } + int ret = sem_init(&sem->semaphore, 1, value); + if (ret == 0) { + sem->status = SEM_STATUS_POSIX; + return; + } +nonposix: + knot_sem_init_nonposix(sem, value); +} + +void knot_sem_init_nonposix(knot_sem_t *sem, int value) +{ + assert((sem != NULL) && (value != SEM_STATUS_POSIX)); + sem->status = value; + sem->status_lock = malloc(sizeof(*sem->status_lock)); + pthread_mutex_init(&sem->status_lock->mutex, NULL); + pthread_cond_init(&sem->status_lock->cond, NULL); +} + +void knot_sem_reset(knot_sem_t *sem, int value) +{ + assert((sem != NULL) && (value != SEM_STATUS_POSIX) && (sem->status != SEM_STATUS_POSIX)); + pthread_mutex_lock(&sem->status_lock->mutex); + sem->status = value; + pthread_cond_signal(&sem->status_lock->cond); + pthread_mutex_unlock(&sem->status_lock->mutex); +} + +void knot_sem_wait(knot_sem_t *sem) +{ + assert(sem != NULL); + if (sem->status == SEM_STATUS_POSIX) { + int semret; + do { + semret = sem_wait(&sem->semaphore); + } while (semret != 0); // repeat wait as it might be interrupted by a signal + } else { + pthread_mutex_lock(&sem->status_lock->mutex); + while (sem->status <= 0) { + pthread_cond_wait(&sem->status_lock->cond, &sem->status_lock->mutex); + } + sem->status--; + pthread_mutex_unlock(&sem->status_lock->mutex); + } +} + +void knot_sem_wait_post(knot_sem_t *sem) +{ + assert((sem != NULL) && (sem->status != SEM_STATUS_POSIX)); + pthread_mutex_lock(&sem->status_lock->mutex); + while (sem->status <= 0) { + pthread_cond_wait(&sem->status_lock->cond, &sem->status_lock->mutex); + } + pthread_cond_signal(&sem->status_lock->cond); + pthread_mutex_unlock(&sem->status_lock->mutex); +} + +void knot_sem_get_ahead(knot_sem_t *sem) +{ + assert((sem != NULL) && (sem->status != SEM_STATUS_POSIX)); + pthread_mutex_lock(&sem->status_lock->mutex); + sem->status--; + pthread_mutex_unlock(&sem->status_lock->mutex); +} + +void knot_sem_get_assert(knot_sem_t *sem) +{ + assert((sem != NULL) && (sem->status != SEM_STATUS_POSIX)); + pthread_mutex_lock(&sem->status_lock->mutex); + assert(sem->status > 0); + sem->status--; + pthread_mutex_unlock(&sem->status_lock->mutex); +} + +void knot_sem_post(knot_sem_t *sem) +{ + assert(sem != NULL); + if (sem->status == SEM_STATUS_POSIX) { + int semret = sem_post(&sem->semaphore); + (void)semret; + assert(semret == 0); + } else { + pthread_mutex_lock(&sem->status_lock->mutex); + sem->status++; + pthread_cond_signal(&sem->status_lock->cond); + pthread_mutex_unlock(&sem->status_lock->mutex); + } +} + +void knot_sem_destroy(knot_sem_t *sem) +{ + assert(sem != NULL); + knot_sem_wait(sem); // NOTE this is questionable if the initial value was > 1 + if (sem->status == SEM_STATUS_POSIX) { + sem_destroy(&sem->semaphore); + } else { + pthread_cond_destroy(&sem->status_lock->cond); + pthread_mutex_destroy(&sem->status_lock->mutex); + free(sem->status_lock); + } +} diff --git a/src/contrib/semaphore.h b/src/contrib/semaphore.h new file mode 100644 index 0000000..5eadecf --- /dev/null +++ b/src/contrib/semaphore.h @@ -0,0 +1,82 @@ +/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <pthread.h> +#include <semaphore.h> + +typedef struct { + pthread_mutex_t mutex; + pthread_cond_t cond; +} knot_sem_nonposix_t; + +typedef struct { + int status; + union { + sem_t semaphore; + knot_sem_nonposix_t *status_lock; + }; +} knot_sem_t; + +/*! + * \brief Initialize semaphore by using POSIX sem_t if possible, custom value/mutex/cond structure otherwise. + */ +void knot_sem_init(knot_sem_t *sem, int value); + +/*! + * \brief Initialize semaphore, force using nonposix variant. + */ +void knot_sem_init_nonposix(knot_sem_t *sem, int value); + +/*! + * \brief Set semaphore value to specified value. + * \note This can be only used with nonposix semaphore. + */ +void knot_sem_reset(knot_sem_t *sem, int value); + +/*! + * \brief Lock the semaphore (decrement), block until it's non-negative. + */ +void knot_sem_wait(knot_sem_t *sem); + +/*! + * \brief Block until the semaphore could decrement, but keep the value unchanged. + * \note This can be only used with nonposix semaphore. + */ +void knot_sem_wait_post(knot_sem_t *sem); + +/*! + * \brief Force-lock the semaphore without blocking, it might get negative. + * \note This can be only used with nonposix semaphore. + */ +void knot_sem_get_ahead(knot_sem_t *sem); + +/*! + * \brief Lock the semaphore, abort() if it would block. + * \note This can be only used with nonposix semaphore. + */ +void knot_sem_get_assert(knot_sem_t *sem); + +/*! + * \brief Unlock the semaphore (increment). + */ +void knot_sem_post(knot_sem_t *sem); + +/*! + * \brief Uninitialize the semaphore. Calls knot_sem_wait once at the beginning. + */ +void knot_sem_destroy(knot_sem_t *sem); diff --git a/src/contrib/sockaddr.c b/src/contrib/sockaddr.c new file mode 100644 index 0000000..90e3785 --- /dev/null +++ b/src/contrib/sockaddr.c @@ -0,0 +1,370 @@ +/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <stdio.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <netdb.h> + +#include "libknot/errcode.h" +#include "contrib/sockaddr.h" +#include "contrib/openbsd/strlcpy.h" +#include "contrib/macros.h" +#include "contrib/musl/inet_ntop.h" + +int sockaddr_len(const struct sockaddr_storage *ss) +{ + if (ss == NULL) { + return 0; + } + + switch(ss->ss_family) { + case AF_INET: + return sizeof(struct sockaddr_in); + case AF_INET6: + return sizeof(struct sockaddr_in6); + case AF_UNIX: + ; const struct sockaddr_un *un = (const struct sockaddr_un *)ss; + return offsetof(struct sockaddr_un, sun_path) + strlen(un->sun_path) + 1; + default: + return 0; + } +} + +static int cmp_ipv4(const struct sockaddr_in *a, const struct sockaddr_in *b, + bool ignore_port) +{ + if (a->sin_addr.s_addr < b->sin_addr.s_addr) { + return -1; + } else if (a->sin_addr.s_addr > b->sin_addr.s_addr) { + return 1; + } else { + return ignore_port ? 0 : a->sin_port - b->sin_port; + } +} + +static int cmp_ipv6(const struct sockaddr_in6 *a, const struct sockaddr_in6 *b, + bool ignore_port) +{ + int ret = memcmp(&a->sin6_addr, &b->sin6_addr, sizeof(struct in6_addr)); + if (ret == 0) { + ret = ignore_port ? 0 : a->sin6_port - b->sin6_port; + } + + return ret; +} + +static int cmp_unix(const struct sockaddr_un *a, const struct sockaddr_un *b) +{ + int len_a = strnlen(a->sun_path, sizeof(a->sun_path)); + int len_b = strnlen(b->sun_path, sizeof(b->sun_path)); + int len_min = len_a <= len_b ? len_a : len_b; + + int ret = strncmp(a->sun_path, b->sun_path, len_min); + if (ret == 0) { + ret = len_a - len_b; + } + + return ret; +} + +int sockaddr_cmp(const struct sockaddr_storage *a, const struct sockaddr_storage *b, + bool ignore_port) +{ + assert(a); + assert(b); + if (a->ss_family != b->ss_family) { + return (int)a->ss_family - (int)b->ss_family; + } + + switch (a->ss_family) { + case AF_UNSPEC: + return 0; + case AF_INET: + return cmp_ipv4((struct sockaddr_in *)a, (struct sockaddr_in *)b, + ignore_port); + case AF_INET6: + return cmp_ipv6((struct sockaddr_in6 *)a, (struct sockaddr_in6 *)b, + ignore_port); + case AF_UNIX: + return cmp_unix((struct sockaddr_un *)a, (struct sockaddr_un *)b); + default: + return 1; + } +} + +int sockaddr_set(struct sockaddr_storage *ss, int family, const char *straddr, int port) +{ + if (ss == NULL || straddr == NULL) { + return KNOT_EINVAL; + } + + /* Set family and port. */ + memset(ss, 0, sizeof(*ss)); + ss->ss_family = family; + sockaddr_port_set(ss, port); + + /* Initialize address depending on address family. */ + if (family == AF_INET6) { + struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)ss; + if (inet_pton(family, straddr, &ipv6->sin6_addr) < 1) { + return KNOT_ERROR; + } + return KNOT_EOK; + } else if (family == AF_INET) { + struct sockaddr_in *ipv4 = (struct sockaddr_in *)ss; + if (inet_pton(family, straddr, &ipv4->sin_addr) < 1) { + return KNOT_ERROR; + } + return KNOT_EOK; + } else if (family == AF_UNIX) { + struct sockaddr_un *un = (struct sockaddr_un *)ss; + size_t ret = strlcpy(un->sun_path, straddr, sizeof(un->sun_path)); + if (ret >= sizeof(un->sun_path)) { + return KNOT_ESPACE; + } + return KNOT_EOK; + } + + return KNOT_EINVAL; +} + +void *sockaddr_raw(const struct sockaddr_storage *ss, size_t *addr_size) +{ + if (ss == NULL || addr_size == NULL) { + return NULL; + } + + if (ss->ss_family == AF_INET) { + struct sockaddr_in *ipv4 = (struct sockaddr_in *)ss; + *addr_size = sizeof(ipv4->sin_addr); + return &ipv4->sin_addr; + } else if (ss->ss_family == AF_INET6) { + struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)ss; + *addr_size = sizeof(ipv6->sin6_addr); + return &ipv6->sin6_addr; + } else if (ss->ss_family == AF_UNIX) { + struct sockaddr_un *un = (struct sockaddr_un *)ss; + *addr_size = strlen(un->sun_path) + 1; + return un->sun_path; + } else { + return NULL; + } +} + +int sockaddr_set_raw(struct sockaddr_storage *ss, int family, + const uint8_t *raw_addr, size_t raw_addr_size) +{ + if (ss == NULL || raw_addr == NULL) { + return KNOT_EINVAL; + } + + memset(ss, 0, sizeof(*ss)); + ss->ss_family = family; + + size_t ss_size = 0; + void *ss_data = sockaddr_raw(ss, &ss_size); + if (ss_data == NULL || + (family != AF_UNIX && ss_size != raw_addr_size) || + (family == AF_UNIX && raw_addr_size >= sizeof((struct sockaddr_un *)ss)->sun_path)) { + return KNOT_EINVAL; + } + + memcpy(ss_data, raw_addr, raw_addr_size); + + return KNOT_EOK; +} + +int sockaddr_tostr(char *buf, size_t maxlen, const struct sockaddr_storage *ss) +{ + if (ss == NULL || buf == NULL) { + return KNOT_EINVAL; + } + + const char *out = NULL; + + /* Convert network address string. */ + if (ss->ss_family == AF_INET6) { + const struct sockaddr_in6 *s = (const struct sockaddr_in6 *)ss; + out = knot_inet_ntop(ss->ss_family, &s->sin6_addr, buf, maxlen); + } else if (ss->ss_family == AF_INET) { + const struct sockaddr_in *s = (const struct sockaddr_in *)ss; + out = knot_inet_ntop(ss->ss_family, &s->sin_addr, buf, maxlen); + } else if (ss->ss_family == AF_UNIX) { + const struct sockaddr_un *s = (const struct sockaddr_un *)ss; + const char *path = (s->sun_path[0] != '\0' ? s->sun_path : "UNIX socket"); + size_t ret = strlcpy(buf, path, maxlen); + out = (ret < maxlen) ? buf : NULL; + } else { + *buf = '\0'; + return KNOT_EINVAL; + } + + if (out == NULL) { + *buf = '\0'; + return KNOT_ESPACE; + } + + /* Write separator and port. */ + int written = strlen(buf); + int port = sockaddr_port(ss); + if (port > 0) { + int ret = snprintf(&buf[written], maxlen - written, "@%d", port); + if (ret <= 0 || (size_t)ret >= maxlen - written) { + *buf = '\0'; + return KNOT_ESPACE; + } + + written += ret; + } + + return written; +} + +int sockaddr_port(const struct sockaddr_storage *ss) +{ + if (ss == NULL) { + return KNOT_EINVAL; + } + + if (ss->ss_family == AF_INET6) { + return ntohs(((struct sockaddr_in6 *)ss)->sin6_port); + } else if (ss->ss_family == AF_INET) { + return ntohs(((struct sockaddr_in *)ss)->sin_port); + } else { + return KNOT_EINVAL; + } +} + +void sockaddr_port_set(struct sockaddr_storage *ss, uint16_t port) +{ + if (ss == NULL) { + return; + } + + if (ss->ss_family == AF_INET6) { + ((struct sockaddr_in6 *)ss)->sin6_port = htons(port); + } else if (ss->ss_family == AF_INET) { + ((struct sockaddr_in *)ss)->sin_port = htons(port); + } +} + +char *sockaddr_hostname(void) +{ + /* Fetch hostname. */ + char host[256] = ""; + if (gethostname(host, sizeof(host)) != 0) { + return NULL; + } + /* Just to be sure. */ + host[sizeof(host) - 1] = '\0'; + + char *canon = NULL; + + /* Fetch first canonical name for this address/DNS if available. */ + struct addrinfo hints, *info = NULL; + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_CANONNAME; + if (getaddrinfo(host, "domain", &hints, &info) == 0) { + struct addrinfo *p = NULL; + for (p = info; p != NULL; p = p->ai_next) { + if (p->ai_canonname) { + canon = strdup(p->ai_canonname); + break; + } + } + freeaddrinfo(info); + } + + return (canon != NULL) ? canon : strdup(host); +} + +bool sockaddr_is_any(const struct sockaddr_storage *ss) +{ + if (ss == NULL) { + return false; + } + + if (ss->ss_family == AF_INET) { + const struct sockaddr_in *ipv4 = (struct sockaddr_in *)ss; + return ipv4->sin_addr.s_addr == INADDR_ANY; + } + + if (ss->ss_family == AF_INET6) { + const struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)ss; + return memcmp(&ipv6->sin6_addr, &in6addr_any, sizeof(ipv6->sin6_addr)) == 0; + } + + return false; +} + +bool sockaddr_net_match(const struct sockaddr_storage *ss1, + const struct sockaddr_storage *ss2, + unsigned prefix) +{ + if (ss1 == NULL || ss2 == NULL) { + return false; + } + + if (ss1->ss_family != ss2->ss_family) { + return false; + } + + if (ss1->ss_family == AF_UNIX) { + const struct sockaddr_un *un1 = (const struct sockaddr_un *)ss1; + const struct sockaddr_un *un2 = (const struct sockaddr_un *)ss2; + return strcmp(un1->sun_path, un2->sun_path) == 0; + } else { + size_t raw_len = 0; + const uint8_t *raw_1 = sockaddr_raw(ss1, &raw_len); + const uint8_t *raw_2 = sockaddr_raw(ss2, &raw_len); + + prefix = MIN(prefix, raw_len * 8); + unsigned bytes = prefix / 8; + unsigned bits = prefix % 8; + + /* Compare full bytes. */ + if (memcmp(raw_1, raw_2, bytes) != 0) { + return false; + } + + /* Compare last partial byte. */ + return bits == 0 || + (raw_1[bytes] >> (8 - bits) == raw_2[bytes] >> (8 - bits)); + } +} + +bool sockaddr_range_match(const struct sockaddr_storage *ss, + const struct sockaddr_storage *ss_min, + const struct sockaddr_storage *ss_max) +{ + if (ss == NULL || ss_min == NULL || ss_max == NULL) { + return false; + } + + if (ss_min->ss_family != ss_max->ss_family || + ss_min->ss_family != ss->ss_family || + ss->ss_family == AF_UNIX) { + return false; + } + + return sockaddr_cmp(ss, ss_min, true) >= 0 && + sockaddr_cmp(ss, ss_max, true) <= 0; +} diff --git a/src/contrib/sockaddr.h b/src/contrib/sockaddr.h new file mode 100644 index 0000000..bddc8da --- /dev/null +++ b/src/contrib/sockaddr.h @@ -0,0 +1,181 @@ +/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stdbool.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/un.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <stdint.h> +#include <unistd.h> + +/* Subnet maximum prefix length. */ +#define IPV4_PREFIXLEN 32 +#define IPV6_PREFIXLEN 128 + +/* Address string "address[@port]" maximum length. */ +#define SOCKADDR_STRLEN_EXT (1 + 6) /* '@', 5 digits number, \0 */ +#define SOCKADDR_STRLEN (sizeof(struct sockaddr_un) + SOCKADDR_STRLEN_EXT) + +/* + * A convenient replacement of `struct sockaddr_storage` with smaller AF_UNIX storage. + * + * Ensure this structure isn't accessed at full `struct sockaddr_storage` range! + * + * The alignment is needed when this structure is an array type and a pointer to this + * array is casted to `struct sockaddr_storage *` and accessed. UBSAN complains otherwise. + * + * The `sun_path` size is a result of: + * `sizeof(struct sockaddr_in6) - sizeof(sa_family_t) + 4B padding`. + */ +typedef union __attribute__ ((aligned (8))) { + struct sockaddr ip; + struct sockaddr_in ip4; + struct sockaddr_in6 ip6; + struct { + sa_family_t sun_family; + char sun_path[30]; + } un; +} sockaddr_t; + +/*! + * \brief Calculate current structure length based on its content. + * + * \param ss Socket address (can be sockaddr_t). + * + * \return Number of bytes or error code. + */ +int sockaddr_len(const struct sockaddr_storage *ss); + +/*! + * \brief Compare addresses. + * + * \param a First address (can be sockaddr_t). + * \param b Second address (can be sockaddr_t). + * \param ignore_port Ignore port indication. + * + * \return like memcmp(3) + */ +int sockaddr_cmp(const struct sockaddr_storage *a, const struct sockaddr_storage *b, + bool ignore_port); + +/*! + * \brief Set address and port. + * + * \param ss Socket address (CANNOT be sockaddr_t!). + * \param family Address family. + * \param straddr IP address in string format. + * \param port Port. + * + * \return KNOT_EOK on success or an error code. + */ +int sockaddr_set(struct sockaddr_storage *ss, int family, const char *straddr, int port); + +/*! + * \brief Return raw network address in network byte order. + * + * \param[in] ss Socket address (can be sockaddr_t). + * \param[out] addr_size Address length. + * + * \return Pointer to binary buffer of size addr_size. + */ +void *sockaddr_raw(const struct sockaddr_storage *ss, size_t *addr_size); + +/*! + * \brief Set raw address. + * + * \param ss Socket address (CANNOT be sockaddr_t!). + * \param family Address family. + * \param raw_addr IP address in binary format. + * \param raw_addr_size Size of the binary address. + * + * \return KNOT_EOK on success or an error code. + */ +int sockaddr_set_raw(struct sockaddr_storage *ss, int family, + const uint8_t *raw_addr, size_t raw_addr_size); + +/*! + * \brief Return string representation of socket address. + * + * \note String format: \<address>[@<port>], f.e. '127.0.0.1@53' + * + * \param buf Destination for string representation. + * \param maxlen Maximum number of written bytes. + * \param ss Socket address (can be sockaddr_t). + * + * \return Number of bytes written on success, error code on failure. + */ +int sockaddr_tostr(char *buf, size_t maxlen, const struct sockaddr_storage *ss); + +/*! + * \brief Return port number from address. + * + * \param ss Socket address (can be sockaddr_t). + * + * \return Port number or error code. + */ +int sockaddr_port(const struct sockaddr_storage *ss); + +/*! + * \brief Set port number. + * + * \param ss Socket address (can be sockaddr_t). + * \param port Port to set. + */ +void sockaddr_port_set(struct sockaddr_storage *ss, uint16_t port); + +/*! + * \brief Get host FQDN address. + * + * \return Hostname string or NULL. + */ +char *sockaddr_hostname(void); + +/*! + * \brief Check if address is ANY address. + * + * \param ss Socket address (can be sockaddr_t). + */ +bool sockaddr_is_any(const struct sockaddr_storage *ss); + +/*! + * \brief Check if two addresses match the given network prefix. + * + * \param ss1 First address (can be sockaddr_t). + * \param ss2 Second address (can be sockaddr_t). + * \param prefix Prefix length. + * + * \return True on match. + */ +bool sockaddr_net_match(const struct sockaddr_storage *ss1, + const struct sockaddr_storage *ss2, + unsigned prefix); + +/*! + * \brief Check if the address is within the given address range (inclusive). + * + * \param ss Address to check (can be sockaddr_t). + * \param ss_min Minimum address (can be sockaddr_t). + * \param ss_max Maximum address (can be sockaddr_t). + * + * \return True on match. + */ +bool sockaddr_range_match(const struct sockaddr_storage *ss, + const struct sockaddr_storage *ss_min, + const struct sockaddr_storage *ss_max); diff --git a/src/contrib/spinlock.h b/src/contrib/spinlock.h new file mode 100644 index 0000000..837f500 --- /dev/null +++ b/src/contrib/spinlock.h @@ -0,0 +1,133 @@ +/* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +/*! + * \brief Multiplatform spinlock. + */ + +#pragma once + +#if (__STDC_VERSION__ >= 201112L) && !defined(__STDC_NO_ATOMICS__) +/* Not tested and activated yet. */ +/* #define HAVE_STDATOMIC */ +#endif + +#if defined(__APPLE__) +# if defined(MAC_OS_X_VERSION_10_12) || \ + MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12 +# define APPLE_SPIN_NEW +# else +# define APPLE_SPIN_OLD +# endif /* MAC_OS_X_VERSION_10_12 ... */ +#endif /* __APPLE__ */ + +#if defined(HAVE_SYNC_ATOMIC) || defined(HAVE_ATOMIC) +# include <stdbool.h> +#elif defined(HAVE_STDATOMIC) +# include <stdbool.h> +# include <stdatomic.h> +#elif defined(APPLE_SPIN_NEW) +# include <os/lock.h> +#elif defined(APPLE_SPIN_OLD) +# include <libkern/OSAtomic.h> +#else /* POSIX pthread spinlock. */ +# include <pthread.h> +#endif + +/*! \brief Spinlock lock variable type. */ +typedef +#if defined(HAVE_SYNC_ATOMIC) || defined(HAVE_ATOMIC) + bool /*!< Spinlock lock - a simple & fast atomic version. */ +#elif defined(HAVE_STDATOMIC) + atomic_bool /*!< Spinlock lock - a simple & fast atomic version, C11 */ +#elif defined(APPLE_SPIN_NEW) + os_unfair_lock /*!< Spinlock lock - a newer macOS version (macOS >= 10.12). */ +#elif defined(APPLE_SPIN_OLD) + OSSpinLock /*!< Spinlock lock - an older macOS version (macOS < 10.12). */ +#else /* POSIX */ + pthread_spinlock_t /*!< Spinlock lock - a POSIX pthread version. */ +#endif + knot_spin_t; + +/*! \brief Initialize the spinlock pointed to by the parameter "lock". */ +static inline void knot_spin_init(knot_spin_t *lock) +{ +#if defined(HAVE_SYNC_ATOMIC) || defined(HAVE_ATOMIC) + *lock = false; +#elif defined(HAVE_STDATOMIC) + atomic_init(lock, false); +#elif defined(APPLE_SPIN_NEW) + *lock = OS_UNFAIR_LOCK_INIT; +#elif defined(APPLE_SPIN_OLD) + *lock = OS_SPINLOCK_INIT; +#else /* POSIX */ + pthread_spin_init(lock, PTHREAD_PROCESS_PRIVATE); +#endif +} + +/*! \brief Destroy the spinlock pointed to by the parameter "lock". */ +static inline void knot_spin_destroy(knot_spin_t *lock) +{ +#if defined(HAVE_SYNC_ATOMIC) || defined(HAVE_ATOMIC) || defined(HAVE_STDATOMIC) || \ + defined(APPLE_SPIN_NEW) || defined(APPLE_SPIN_OLD) + /* Nothing needed here. */ +#else /* POSIX */ + pthread_spin_destroy(lock); +#endif +} + +/*! \brief Lock the spinlock pointed to by the parameter "lock". */ +static inline void knot_spin_lock(knot_spin_t *lock) +{ +#if defined(HAVE_SYNC_ATOMIC) + while (__sync_lock_test_and_set(lock, 1)) { + } +#elif defined(HAVE_ATOMIC) + int expected = 0; + while (!__atomic_compare_exchange_n(lock, &expected, 1, false, __ATOMIC_RELAXED, __ATOMIC_RELAXED)) { + expected = 0; + } +#elif defined(HAVE_STDATOMIC) + int expected = 0; + while (!atomic_compare_exchange_strong(lock, &expected, false)) { + expected = 0; + } +#elif defined(APPLE_SPIN_NEW) + os_unfair_lock_lock(lock); +#elif defined(APPLE_SPIN_OLD) + OSSpinLockLock(lock); +#else /* POSIX */ + pthread_spin_lock(lock); +#endif +} + +/*! \brief Unlock the spinlock pointed to by the parameter "lock". */ +static inline void knot_spin_unlock(knot_spin_t *lock) +{ +#if defined(HAVE_SYNC_ATOMIC) + __sync_lock_release(lock); +#elif defined(HAVE_ATOMIC) + __atomic_clear(lock, __ATOMIC_RELAXED); +#elif defined(HAVE_STDATOMIC) + atomic_store(lock, false); +#elif defined(APPLE_SPIN_NEW) + os_unfair_lock_unlock(lock); +#elif defined(APPLE_SPIN_OLD) + OSSpinLockUnlock(lock); +#else /* POSIX */ + pthread_spin_unlock(lock); +#endif +} diff --git a/src/contrib/string.c b/src/contrib/string.c new file mode 100644 index 0000000..272116e --- /dev/null +++ b/src/contrib/string.c @@ -0,0 +1,247 @@ +/* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#if defined(HAVE_EXPLICIT_BZERO) + #if defined(HAVE_BSD_STRING_H) + #include <bsd/string.h> + #endif + /* #include <string.h> is needed. */ +#elif defined(HAVE_EXPLICIT_MEMSET) + /* #include <string.h> is needed. */ +#elif defined(HAVE_GNUTLS_MEMSET) + #include <gnutls/gnutls.h> +#else + #define USE_CUSTOM_MEMSET +#endif + +#include "contrib/string.h" +#include "contrib/ctype.h" +#include "contrib/tolower.h" + +uint8_t *memdup(const uint8_t *data, size_t data_size) +{ + uint8_t *result = (uint8_t *)malloc(data_size); + if (!result) { + return NULL; + } + + return memcpy(result, data, data_size); +} + +int strmemcmp(const char *str, const uint8_t *mem, size_t mem_size) +{ + if (mem_size == 0) { + return 1; + } + size_t cmp_len = strnlen(str, mem_size - 1) + 1; + return memcmp(str, mem, cmp_len); +} + +char *sprintf_alloc(const char *fmt, ...) +{ + char *strp = NULL; + va_list ap; + + va_start(ap, fmt); + int ret = vasprintf(&strp, fmt, ap); + va_end(ap); + + if (ret < 0) { + return NULL; + } + return strp; +} + +char *strcdup(const char *s1, const char *s2) +{ + if (!s1 || !s2) { + return NULL; + } + + size_t s1len = strlen(s1); + size_t s2len = strlen(s2); + size_t nlen = s1len + s2len + 1; + + char* dst = malloc(nlen); + if (dst == NULL) { + return NULL; + } + + memcpy(dst, s1, s1len); + memcpy(dst + s1len, s2, s2len + 1); + return dst; +} + +char *strstrip(const char *str) +{ + // leading white-spaces + const char *scan = str; + while (is_space(scan[0])) { + scan += 1; + } + + // trailing white-spaces + size_t len = strlen(scan); + while (len > 0 && is_space(scan[len - 1])) { + len -= 1; + } + + char *trimmed = malloc(len + 1); + if (!trimmed) { + return NULL; + } + + memcpy(trimmed, scan, len); + trimmed[len] = '\0'; + + return trimmed; +} + +void strtolower(char *str) +{ + if (str == NULL) { + return; + } + + for (char *it = str; *it != '\0'; ++it) { + *it = knot_tolower(*it); + } +} + +int const_time_memcmp(const void *s1, const void *s2, size_t n) +{ + volatile uint8_t equal = 0; + + for (size_t i = 0; i < n; i++) { + equal |= ((uint8_t *)s1)[i] ^ ((uint8_t *)s2)[i]; + } + + return equal; +} + +#if defined(USE_CUSTOM_MEMSET) +typedef void *(*memset_t)(void *, int, size_t); +static volatile memset_t volatile_memset = memset; +#endif + +void *memzero(void *s, size_t n) +{ +#if defined(HAVE_EXPLICIT_BZERO) /* In OpenBSD since 5.5. */ + /* In FreeBSD since 11.0. */ + /* In glibc since 2.25. */ + /* In DragonFly BSD since 5.5. */ +# if defined(__has_feature) +# if __has_feature(memory_sanitizer) + #warning "Memory sanitizer detected. Using bzero() instead of explicit_bzero()." + bzero(s, n); +# else + explicit_bzero(s, n); +# endif +# else + explicit_bzero(s, n); +# endif + return s; +#elif defined(HAVE_EXPLICIT_MEMSET) /* In NetBSD since 7.0. */ + return explicit_memset(s, 0, n); +#elif defined(HAVE_GNUTLS_MEMSET) /* In GnuTLS since 3.4.0. */ + gnutls_memset(s, 0, n); + return s; +#else /* Knot custom solution as a fallback. */ + /* Warning: the use of the return value is *probably* needed + * so as to avoid the volatile_memset() to be optimized out. + */ + return volatile_memset(s, 0, n); +#endif +} + +static const char BIN_TO_HEX[] = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' +}; + +char *bin_to_hex(const uint8_t *bin, size_t bin_len, bool upper_case) +{ + if (bin == NULL) { + return NULL; + } + + size_t hex_size = bin_len * 2; + char *hex = malloc(hex_size + 1); + if (hex == NULL) { + return NULL; + } + + unsigned offset = upper_case ? 16 : 0; + for (size_t i = 0; i < bin_len; i++) { + hex[2 * i] = BIN_TO_HEX[offset + (bin[i] >> 4)]; + hex[2 * i + 1] = BIN_TO_HEX[offset + (bin[i] & 0x0f)]; + } + hex[hex_size] = '\0'; + + return hex; +} + +/*! + * Convert HEX character to numeric value (assumes valid input). + */ +static uint8_t hex_to_number(const char hex) +{ + if (hex >= '0' && hex <= '9') { + return hex - '0'; + } else if (hex >= 'a' && hex <= 'f') { + return hex - 'a' + 10; + } else { + assert(hex >= 'A' && hex <= 'F'); + return hex - 'A' + 10; + } +} + +uint8_t *hex_to_bin(const char *hex, size_t *out_len) +{ + if (hex == NULL || out_len == NULL) { + return NULL; + } + + size_t hex_len = strlen(hex); + if (hex_len % 2 != 0) { + return NULL; + } + + size_t bin_len = hex_len / 2; + uint8_t *bin = malloc(bin_len + 1); + if (bin == NULL) { + return NULL; + } + + for (size_t i = 0; i < bin_len; i++) { + if (!is_xdigit(hex[2 * i]) || !is_xdigit(hex[2 * i + 1])) { + free(bin); + return NULL; + } + uint8_t high = hex_to_number(hex[2 * i]); + uint8_t low = hex_to_number(hex[2 * i + 1]); + bin[i] = high << 4 | low; + } + + *out_len = bin_len; + + return bin; +} diff --git a/src/contrib/string.h b/src/contrib/string.h new file mode 100644 index 0000000..ad3c990 --- /dev/null +++ b/src/contrib/string.h @@ -0,0 +1,104 @@ +/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +/*! + * \brief String manipulations. + */ + +#pragma once + +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> + +/*! + * \brief Create a copy of a binary buffer. + * + * Like \c strdup, but for binary data. + */ +uint8_t *memdup(const uint8_t *data, size_t data_size); + +/*! + * \brief Compare a zero-terminated string with fixed-size memory. + */ +int strmemcmp(const char *str, const uint8_t *mem, size_t mem_size); + +/*! + * \brief Format string and take care of allocating memory. + * + * \note sprintf(3) manual page reference implementation. + * + * \param fmt Message format. + * \return formatted message or NULL. + */ +char *sprintf_alloc(const char *fmt, ...); + +/*! + * \brief Create new string from a concatenation of s1 and s2. + * + * \param s1 First string. + * \param s2 Second string. + * + * \retval Newly allocated string on success. + * \retval NULL on error. + */ +char *strcdup(const char *s1, const char *s2); + +/*! + * \brief Create a copy of a string skipping leading and trailing white spaces. + * + * \return Newly allocated string, NULL in case of error. + */ +char *strstrip(const char *str); + +/*! + * \brief Convert upper-case letters to lower-case in a string. + */ +void strtolower(char *str); + +/*! + * \brief Compare data in time based on string length. + * This function just checks for (in)equality not for relation + * + * \param s1 The first address to compare. + * \param s2 The second address to compare. + * \param n The size of memory to compare. + * + * \return Non zero on difference and zero if the buffers are identical. + */ +int const_time_memcmp(const void *s1, const void *s2, size_t n); + +/*! + * \brief Fill memory with zeroes. + * + * Inspired by OPENSSL_cleanse. Such a memset shouldn't be optimized out. + * + * \param s The address to fill. + * \param n The size of memory to fill. + * + * \return Pointer to the memory. + */ +void *memzero(void *s, size_t n); + +/*! + * \brief Convert binary data to hexadecimal string. + */ +char *bin_to_hex(const uint8_t *bin, size_t bin_len, bool upper_case); + +/*! + * \brief Convert hex encoded string to binary data. + */ +uint8_t *hex_to_bin(const char *hex, size_t *out_len); diff --git a/src/contrib/strtonum.h b/src/contrib/strtonum.h new file mode 100644 index 0000000..849f66f --- /dev/null +++ b/src/contrib/strtonum.h @@ -0,0 +1,125 @@ +/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <assert.h> +#include <errno.h> +#include <inttypes.h> +#include <limits.h> +#include <stddef.h> +#include <stdint.h> + +#include "libknot/errcode.h" +#include "contrib/ctype.h" + +inline static int intmax_from_str(const char *src, intmax_t *dst, + intmax_t min, intmax_t max) +{ + if (!is_digit(*src) && *src != '-' && *src != '+') { + return KNOT_EINVAL; + } + + errno = 0; + char *end = NULL; + intmax_t result = strtoimax(src, &end, 10); + + if (errno == ERANGE) { + return KNOT_ERANGE; + } + + if (src == end || *end != '\0') { + return KNOT_EINVAL; + } + + if (result < min || result > max) { + return KNOT_ERANGE; + } + + *dst = result; + return KNOT_EOK; +} + +inline static int uintmax_from_str(const char *src, uintmax_t *dst, + uintmax_t min, uintmax_t max) +{ + if (!is_digit(*src) && *src != '+') { + return KNOT_EINVAL; + } + + errno = 0; + char *end = NULL; + uintmax_t result = strtoumax(src, &end, 10); + + if (errno == ERANGE) { + return KNOT_ERANGE; + } + + if (src == end || *end != '\0') { + return KNOT_EINVAL; + } + + if (result < min || result > max) { + return KNOT_ERANGE; + } + + *dst = result; + return KNOT_EOK; +} + +#define CONVERT(prefix, type, min, max, src, dst) \ +{ \ + assert(src && dst); \ + prefix##max_t value; \ + int result = prefix##max_from_str(src, &value, min, max); \ + if (result != KNOT_EOK) { \ + return result; \ + } \ + *dst = (type)value; \ + return KNOT_EOK; \ +} + +inline static int str_to_int(const char *src, int *dst, int min, int max) +{ + CONVERT(int, int, min, max, src, dst); +} + +inline static int str_to_u8(const char *src, uint8_t *dst) +{ + CONVERT(uint, uint8_t, 0, UINT8_MAX, src, dst); +} + +inline static int str_to_u16(const char *src, uint16_t *dst) +{ + CONVERT(uint, uint16_t, 0, UINT16_MAX, src, dst); +} + +inline static int str_to_u32(const char *src, uint32_t *dst) +{ + CONVERT(uint, uint32_t, 0, UINT32_MAX, src, dst); +} + +inline static int str_to_u64(const char *src, uint64_t *dst) +{ + CONVERT(uint, uint64_t, 0, UINT64_MAX, src, dst); +} + +inline static int str_to_size(const char *src, size_t *dst, size_t min, size_t max) +{ + CONVERT(uint, size_t, min, max, src, dst); +} + +#undef CONVERT diff --git a/src/contrib/time.c b/src/contrib/time.c new file mode 100644 index 0000000..d29374c --- /dev/null +++ b/src/contrib/time.c @@ -0,0 +1,431 @@ +/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <inttypes.h> +#include <limits.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "contrib/time.h" +#include "contrib/ctype.h" + +struct timespec time_now(void) +{ + struct timespec result = { 0 }; + + clock_gettime(CLOCK_MONOTONIC, &result); + + return result; +} + +struct timespec time_diff(const struct timespec *begin, const struct timespec *end) +{ + struct timespec result = { 0 }; + + if (end->tv_nsec >= begin->tv_nsec) { + result.tv_sec = end->tv_sec - begin->tv_sec; + result.tv_nsec = end->tv_nsec - begin->tv_nsec; + } else { + result.tv_sec = end->tv_sec - begin->tv_sec - 1; + result.tv_nsec = 1000000000 - begin->tv_nsec + end->tv_nsec; + } + + return result; +} + +double time_diff_ms(const struct timespec *begin, const struct timespec *end) +{ + struct timespec result = time_diff(begin, end); + + return (result.tv_sec * 1e3) + (result.tv_nsec / 1e6); +} + +typedef struct { + const char *format; + const char *timespec; + const char *parsed; + knot_timediff_t offset; + char offset_sign; + char offset_unit; + struct tm calendar; + int error; +} time_ctx_t; + +// After casting (struct tm) to (int []), we can use indexes... +static int calendar_index(char ind) +{ + switch (ind) { + case 'Y': return 5; + case 'M': return 4; + case 'D': return 3; + case 'h': return 2; + case 'm': return 1; + case 's': return 0; + default: assert(0); return 6; + } +} + +static size_t calendar_digits(int index) +{ + return index == 5 ? 4 : 2; +} + +static size_t unit_value(char unit) +{ + size_t val = 1; + switch (unit) { + case 'M': + return 3600 * 24 * 30; + case 'Y': + val *= 365; + // FALLTHROUGH + case 'D': + val *= 24; + // FALLTHROUGH + case 'h': + val *= 60; + // FALLTHROUGH + case 'm': + val *= 60; + // FALLTHROUGH + case 's': + default: + return val; + } +} + +static knot_time_t time_ctx_finalize(time_ctx_t *ctx) +{ + if (ctx->offset_sign) { + ctx->offset *= unit_value(ctx->offset_unit); + return knot_time_add(knot_time(), (ctx->offset_sign == '-' ? -1 : 1) * ctx->offset); + } else if (ctx->offset) { + return (knot_time_t)ctx->offset; + } else if (ctx->calendar.tm_year != 0) { + ctx->calendar.tm_isdst = -1; + ctx->calendar.tm_year -= 1900; + ctx->calendar.tm_mon -= 1; + // Set UTC timezone before using mktime + putenv("TZ=UTC"); + tzset(); + return (knot_time_t)mktime(&ctx->calendar); + } else { + return (knot_time_t)0; + } +} + +static void time_ctx_reset(time_ctx_t *ctx) +{ + ctx->parsed = ctx->timespec; + ctx->offset = 0; + ctx->offset_sign = 0; + memset(&ctx->calendar, 0, sizeof(ctx->calendar)); + ctx->error = 0; +} + +static void parse_quote(time_ctx_t *ctx) +{ + while (*ctx->format != '|' && *ctx->format != '\0') { + if (*ctx->format == '\'') { + ctx->format++; + return; + } + if (*ctx->format++ != *ctx->parsed++) { + ctx->error = -1; + return; + } + } + ctx->error = -2; + return; +} + +static void parse_offset(time_ctx_t *ctx) +{ + ctx->offset = 0; + ctx->error = -1; + while (is_digit(*ctx->parsed)) { + ctx->offset *= 10; + ctx->offset += *ctx->parsed++ - '0'; + ctx->error = 0; + } +} + +static void parse_calendar(time_ctx_t *ctx, int index) +{ + int *cal_arr = (int *)&ctx->calendar; + cal_arr[index] = 0; + for (size_t i = 0; i < calendar_digits(index); i++) { + if (!is_digit(*ctx->parsed)) { + ctx->error = -1; + return; + } + cal_arr[index] *= 10; + cal_arr[index] += *ctx->parsed++ - '0'; + } +} + +static void parse_sign(time_ctx_t *ctx) +{ + char sign1 = *(ctx->format - 1), sign2 = *ctx->format; + + bool use_sign2 = (sign2 == '+' || sign2 == '-'); + + bool allow_plus = (sign1 == '+' || (sign1 == '-' && sign2 == '+')); + bool allow_minus = (sign1 == '-' || (sign1 == '+' && sign2 == '-')); + assert(sign1 == '+' || sign1 == '-'); + + if ((*ctx->parsed == '+' && allow_plus) || (*ctx->parsed == '-' && allow_minus)) { + ctx->offset_sign = *ctx->parsed++; + ctx->format += (use_sign2 ? 1 : 0); + } else { + ctx->error = -11; + } +} + +static void parse_unit1(time_ctx_t *ctx) +{ + char u = *ctx->parsed++; + switch (u) { + case 'Y': + case 'M': + case 'D': + case 'h': + case 'm': + case 's': + ctx->offset_unit = u; + break; + default: + ctx->error = -1; + } +} + +static void parse_unit2(time_ctx_t *ctx) +{ + char u = *ctx->parsed++; + switch (u) { + case 'y': + case 'd': + ctx->offset_unit = toupper((unsigned char)u); + break; + case 'h': + case 's': + ctx->offset_unit = u; + break; + case 'm': + switch (*ctx->parsed++) { + case 'o': + ctx->offset_unit = 'M'; + break; + case 'i': + ctx->offset_unit = 'm'; + break; + default: + ctx->error = -1; + } + break; + default: + ctx->error = -1; + } +} + +int knot_time_parse(const char *format, const char *timespec, knot_time_t *time) +{ + if (format == NULL || timespec == NULL || time == NULL) { + return -1; + } + + time_ctx_t ctx = { + .format = format, + .timespec = timespec, + .parsed = timespec, + .offset = 0, + .offset_sign = 0, + // we hope that .calendar is zeroed by default + .error = 0, + }; + + while (ctx.error == 0 && *ctx.format != '\0') { + switch (*ctx.format++) { + case '|': + if (*ctx.parsed == '\0') { + *time = time_ctx_finalize(&ctx); + return 0; + } else { + time_ctx_reset(&ctx); + } + break; + case '\'': + parse_quote(&ctx); + break; + case '#': + parse_offset(&ctx); + break; + case 'Y': + case 'M': + case 'D': + case 'h': + case 'm': + case 's': + parse_calendar(&ctx, calendar_index(*(ctx.format - 1))); + break; + case '+': + case '-': + parse_sign(&ctx); + break; + case 'U': + parse_unit1(&ctx); + break; + case 'u': + parse_unit2(&ctx); + break; + default: + return -1; + } + + if (ctx.error < 0) { + while (*ctx.format != '|' && *ctx.format != '\0') { + ctx.format++; + } + time_ctx_reset(&ctx); + ctx.error = (*ctx.format == '\0' ? -1 : 0); + } + } + + if (ctx.error == 0 && *ctx.parsed == '\0') { + *time = time_ctx_finalize(&ctx); + return 0; + } + return -1; +} + +static char *unit_names_mixed[] = { "Y", "M", "D", "h", "m", "s" }; +static char *unit_names_lower[] = { "y", "mo", "d", "h", "mi", "s" }; +static size_t unit_sizes[] = { 3600*24*365, 3600*24*30, 3600*24, 3600, 60, 1 }; +static const size_t unit_count = 6; + +static int print_unit(char *dst, size_t dst_len, char *unit_names[unit_count], + size_t max_units, knot_time_t time) +{ + int ret; + if (time == 0) { + ret = snprintf(dst, dst_len, "0"); + return (ret < 0 || ret >= dst_len ? -1 : 0); + } + knot_timediff_t diff = knot_time_diff(time, knot_time()); + if (dst_len-- < 1) { + return -1; + } + *dst++ = (diff < 0 ? '-' : '+'); + if (diff < 0) { + diff = -diff; + } else if (diff == 0) { + ret = snprintf(dst, dst_len, "0%s", unit_names[unit_count - 1]); + return (ret < 0 || ret >= dst_len ? -1 : 0); + } + size_t curr_unit = 0, used_units = 0; + while (curr_unit < unit_count && used_units < max_units) { + if (diff >= unit_sizes[curr_unit]) { + ret = snprintf(dst, dst_len, "%"KNOT_TIMEDIFF_PRINTF"%s", + diff / unit_sizes[curr_unit], + unit_names[curr_unit]); + if (ret < 0 || ret >= dst_len) { + return -1; + } + dst += ret; + dst_len -= ret; + used_units++; + diff %= unit_sizes[curr_unit]; + } + curr_unit++; + } + return 0; +} + +int knot_time_print(knot_time_print_t format, knot_time_t time, char *dst, size_t dst_len) +{ + if (dst == NULL) { + return -1; + } + + int ret; + switch (format) { + case TIME_PRINT_UNIX: + ret = snprintf(dst, dst_len, "%"KNOT_TIME_PRINTF, time); + return ((ret >= 0 && ret < dst_len) ? 0 : -1); + case TIME_PRINT_ISO8601: + if (time > LONG_MAX) { + return -1; + } + + // Set timezone to UTC before using timezone dependent functions + putenv("TZ=UTC"); + tzset(); + + struct tm lt; + time_t tt = (time_t)time; + ret = (localtime_r(&tt, <) == NULL ? -1 : + strftime(dst, dst_len, "%Y-%m-%dT%H:%M:%SZ", <)); + return (ret > 0 ? 0 : -1); + case TIME_PRINT_RELSEC: + ret = snprintf(dst, dst_len, "%+"KNOT_TIMEDIFF_PRINTF, + knot_time_diff(time, knot_time())); + return ((ret >= 0 && ret < dst_len) ? 0 : -1); + case TIME_PRINT_HUMAN_MIXED: + return print_unit(dst, dst_len, unit_names_mixed, unit_count, time); + case TIME_PRINT_HUMAN_LOWER: + return print_unit(dst, dst_len, unit_names_lower, unit_count, time); + default: + return -1; + } +} + +int knot_time_print_human(knot_time_t time, char *dst, size_t dst_len, bool condensed) +{ + int ret; + knot_time_t num; + bool empty = true; + size_t total_len = 0; + +#define tths_process(unit, unit_name, unit_size) \ + num = time / (unit_size); \ + if (num > 0) { \ + ret = snprintf(dst + total_len, dst_len - total_len, \ + "%s%"PRIu64"%s%s", \ + (!empty && !condensed ? " " : ""), \ + num, \ + (condensed ? unit : unit_name), \ + (num > 1 && !condensed ? "s" : "")); \ + if (ret <= 0 || (size_t)ret >= dst_len - total_len) { \ + return -1; \ + } \ + empty = false; \ + total_len += ret; \ + time -= num * (unit_size); \ + } + + tths_process("w", " week", 604800); + tths_process("d", " day", 86400); + tths_process("h", " hour", 3600); + tths_process("m", " minute", 60); + tths_process("s", " second", 1); + +#undef tths_process + + return total_len > 0 ? total_len : -1; +} diff --git a/src/contrib/time.h b/src/contrib/time.h new file mode 100644 index 0000000..20d241e --- /dev/null +++ b/src/contrib/time.h @@ -0,0 +1,211 @@ +/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <inttypes.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <time.h> + +#ifdef __APPLE__ + #define st_mtim st_mtimespec +#endif + +/*! + * \brief Specify output format for knot_time_print(). + */ +typedef enum { + TIME_PRINT_UNIX, // numeric UNIX time + TIME_PRINT_ISO8601, // 2016-12-31T23:59:00 + TIME_PRINT_RELSEC, // relative +6523 + TIME_PRINT_HUMAN_MIXED, // relative with mixed-case units + TIME_PRINT_HUMAN_LOWER, // relative with lower-case units +} knot_time_print_t; + +/*! + * \brief Get current time. + */ +struct timespec time_now(void); + +/*! + * \brief Get time elapsed between two events. + */ +struct timespec time_diff(const struct timespec *begin, const struct timespec *end); + +/*! + * \brief Get time elapsed between two events in milliseconds. + */ +double time_diff_ms(const struct timespec *begin, const struct timespec *end); + +/*! + * \brief Data type for keeping UNIX timestamps. + * + * This is because time_t can be 32-bit on some systems, which is bad. + * Zero value represents infinity. + */ +typedef uint64_t knot_time_t; + +/*! + * \brief Data type for keeping time differences. + */ +typedef int64_t knot_timediff_t; + +#define KNOT_TIMEDIFF_MIN INT64_MIN +#define KNOT_TIMEDIFF_MAX INT64_MAX + +#define KNOT_TIME_PRINTF PRIu64 +#define KNOT_TIMEDIFF_PRINTF PRId64 + +/*! + * \brief Returns current time sice epoch. + */ +inline static knot_time_t knot_time(void) +{ + return (knot_time_t)time(NULL); +} + +/*! + * \brief Compare two timestamps. + * + * \return 0 if equal, -1 if the former is smaller (=earlier), 1 else. + */ +inline static int knot_time_cmp(knot_time_t a, knot_time_t b) +{ + return (a == b ? 0 : 1) * ((a && b) == 0 ? -1 : 1) * (a < b ? -1 : 1); +} + +/*! + * \brief Return the smaller (=earlier) from given two timestamps. + */ +inline static knot_time_t knot_time_min(knot_time_t a, knot_time_t b) +{ + if ((a && b) == 0) { + return a + b; + } else { + return (a < b ? a : b); + } +} + +/*! + * \brief Return the difference between two timestamps (to "minus" from). + * + * \note If both are zero (=infinity), KNOT_TIMEDIFF_MAX is returned. + */ +inline static knot_timediff_t knot_time_diff(knot_time_t to, knot_time_t from) +{ + if ((to && from) == 0) { + return (to > from ? KNOT_TIMEDIFF_MIN : KNOT_TIMEDIFF_MAX); + } else { + return (knot_timediff_t)to - (knot_timediff_t)from; + } +} + +/*! + * \brief Add a time difference to timestamp. + */ +inline static knot_time_t knot_time_add(knot_time_t since, knot_timediff_t howlong) +{ + return (since != 0 ? since + howlong : since); +} + +inline static knot_time_t knot_time_plus(knot_time_t a, knot_time_t b) +{ + return ((a && b) ? a + b : 0); +} + +/*! + * \brief Convert uint32_t-encoded timestamp to knot_time_t. + * + * In RRSIG rdata, there are inception and expiration timestamps in uint32_t format. + * One shall use 'serial arithmetics' to decode them. + * + * The result of this function is a timestamp that equals to + * given 32-bit time in lower 32 bits, and does not differ from + * now by more than 2^31. + */ +inline static knot_time_t knot_time_from_u32(uint32_t u32time, knot_time_t now) +{ + if (now == 0) { + now = knot_time(); + } + + uint32_t now_lower32 = (uint32_t)now; + uint64_t now_upper32 = now >> 32; + if (now_lower32 > u32time && now_lower32 - u32time > INT32_MAX) { + now_upper32++; + } else if (now_lower32 < u32time && u32time - now_lower32 > INT32_MAX) { + now_upper32--; + } + + return (now_upper32 << 32) + u32time; +} + +/*! + * \brief Parse a text-formatted timestamp to knot_time_t using format specification. + * + * \param format The timestamp text format specification. + * \param timespec Text-formatted timestamp. + * \param time The parsed timestamp. + * + * The format specification basics: + * <format 1>|<format 2> - The pipe sign separates two time format specifications. Leftmost + * specification matching the timespec is used. + * '<a string>' - Matches exactly <a string> (not containing apostrophes) in timespec. + * # - Hashtag matches for a number in timespec, stands for either a UNIX timestamp, + * or, within a context of an unit, as a number of such units. + * Y, M, D, h, m, s - Matches a number, stands for a number of years, months, days, hours, + * minutes and seconds, respectively. + * +, - - The + and - signs declaring that following timespec is relative to "now". + * A single sign can be used to limit the timestamp being in future or in past, + * or both +- allow the timestamp to select any (just one) of them. + * U - Matches one of Y, M, D, h, m, s in the timespec standing for a time unit. + * u - Like U, but the unit in the timestamp is from: y, mo, d, h, mi, s. + * + * \retval -1 An error occurred, out_time has no sense. + * \return 0 OK, timestamp parsed successfully. + */ +int knot_time_parse(const char *format, const char *timespec, knot_time_t *time); + +/*! + * \brief Print the timestamp in specified format into a string buffer. + * + * \param format The timestamp text format specification. + * \param time The timestamp to be printed. + * \param dst The destination buffer pointer with text-formatted timestamp. + * \param dst_len The destination buffer length. + * + * \retval -1 An error occurred, the buffer may be filled with nonsense. + * \return 0 OK, timestamp printed successfully. + */ +int knot_time_print(knot_time_print_t format, knot_time_t time, char *dst, size_t dst_len); + +/*! + * \brief Print the timestamp in a predefined human format. + * + * Condensed format (zone file compatible): 1w2d3h4m5s + * Normal format: 1 week 2 days 3 hours 4 minutes 5 seconds + * + * \param time The timestamp to be printed. + * \param dst The destination buffer pointer with text-formatted timestamp. + * \param dst_len The destination buffer length. + * \param condensed Condensed format indication. + * + * \retval -1 An error occurred, the buffer may be filled with nonsense. + * \return >0 OK, timestamp printed successfully. + */ +int knot_time_print_human(knot_time_t time, char *dst, size_t dst_len, bool condensed); diff --git a/src/contrib/toeplitz.h b/src/contrib/toeplitz.h new file mode 100644 index 0000000..02974d2 --- /dev/null +++ b/src/contrib/toeplitz.h @@ -0,0 +1,122 @@ +/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +/*! + * \brief Microsoft Toeplitz-based hash implementation + */ + +#pragma once + +#include <assert.h> +#include <stdint.h> + +#include "libknot/endian.h" + +/*! + * \brief Computes a Toeplitz hash value for given key and data. + * + * \param key Key vector + * \param key_len Length of the key vector in bytes. + * \param data Input data to compute hash from. + * \param data_len Lenght of the input data in bytes. + * + * \return A Toeplitz hash value. + */ +inline static uint32_t toeplitz_hash(const uint8_t *key, const size_t key_len, + const uint8_t *data, const size_t data_len) +{ + assert(key_len >= 4 + 2 * (16 + 2)); + + uint32_t key32 = be32toh(*(const uint32_t *)key); + key += sizeof(uint32_t); + + int ret = 0; + + for (int i = 0; i < data_len; i++) { + for (int bit = 7; bit >= 0; bit--) { + if (data[i] & (1 << bit)) { + ret ^= key32; + } + + key32 <<= 1; + key32 |= !!(key[0] & (1 << bit)); + } + key++; + } + + return ret; +} + +/*! + * \brief Toeplitz hash context for divided processing. + */ +typedef struct { + const uint8_t *data; + const uint8_t *data_end; + const uint8_t *key; + uint32_t hash; + uint32_t key32; +} toeplitz_ctx_t; + +inline static void toeplitz_init(toeplitz_ctx_t *ctx, uint8_t count, + const uint8_t *key, const uint8_t key_len, + const uint8_t *data, const uint8_t data_len) +{ + assert(key_len >= 40); + + ctx->data = data; + ctx->data_end = data + data_len; + ctx->key = key + sizeof(uint32_t); + ctx->hash = 0; + ctx->key32 = be32toh(*(const uint32_t *)key); + + const uint8_t *stop = ctx->data + count; + assert(stop <= ctx->data_end); + + while (ctx->data < stop) { + for (int bit = 7; bit >= 0; bit--) { + if (*ctx->data & (1 << bit)) { + ctx->hash ^= ctx->key32; + } + + ctx->key32 <<= 1; + ctx->key32 |= !!(*ctx->key & (1 << bit)); + } + ctx->data++; + ctx->key++; + } +} + +inline static uint32_t toeplitz_finish(toeplitz_ctx_t *ctx) +{ + uint32_t hash = ctx->hash; + uint32_t key32 = ctx->key32; + const uint8_t *key = ctx->key; + + for (const uint8_t *in = ctx->data; in < ctx->data_end; in++) { + for (int bit = 7; bit >= 0; bit--) { + if (*in & (1 << bit)) { + hash ^= key32; + } + + key32 <<= 1; + key32 |= !!(*key & (1 << bit)); + } + key++; + } + + return hash; +} diff --git a/src/contrib/tolower.h b/src/contrib/tolower.h new file mode 100644 index 0000000..c8b4897 --- /dev/null +++ b/src/contrib/tolower.h @@ -0,0 +1,52 @@ +/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +/*! + * \brief Table for converting ASCII characters to lowercase. + */ + +#pragma once + +#include <stdint.h> + +/*! + * \brief Converts binary character to lowercase. + * + * \param c Character code. + * + * \return \a c converted to lowercase (or \a c if not applicable). + */ +static inline uint8_t knot_tolower(uint8_t c) { + const uint8_t *tolower_table = (uint8_t *) + "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F" + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F" + "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2A\x2B\x2C\x2D\x2E\x2F" + "\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3A\x3B\x3C\x3D\x3E\x3F" + "\x40\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6A\x6B\x6C\x6D\x6E\x6F" + "\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7A\x5B\x5C\x5D\x5E\x5F" + "\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6A\x6B\x6C\x6D\x6E\x6F" + "\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7A\x7B\x7C\x7D\x7E\x7F" + "\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8A\x8B\x8C\x8D\x8E\x8F" + "\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9A\x9B\x9C\x9D\x9E\x9F" + "\xA0\xA1\xA2\xA3\xA4\xA5\xA6\xA7\xA8\xA9\xAA\xAB\xAC\xAD\xAE\xAF" + "\xB0\xB1\xB2\xB3\xB4\xB5\xB6\xB7\xB8\xB9\xBA\xBB\xBC\xBD\xBE\xBF" + "\xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF" + "\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD7\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF" + "\xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF" + "\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF7\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xFF"; + + return tolower_table[c]; +} diff --git a/src/contrib/trim.h b/src/contrib/trim.h new file mode 100644 index 0000000..2b89249 --- /dev/null +++ b/src/contrib/trim.h @@ -0,0 +1,36 @@ +/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +/*! + * \brief Heap memory trimmer. + */ + +#pragma once + +#ifdef HAVE_MALLOC_TRIM +#include <malloc.h> +#endif + +/*! + * \brief Trim excess heap memory. + */ +static inline void mem_trim(void) +{ +#ifdef HAVE_MALLOC_TRIM + malloc_trim(0); +#endif + return; +} diff --git a/src/contrib/ucw/LICENSE b/src/contrib/ucw/LICENSE new file mode 100644 index 0000000..b463d57 --- /dev/null +++ b/src/contrib/ucw/LICENSE @@ -0,0 +1 @@ +../licenses/LGPL-2.0
\ No newline at end of file diff --git a/src/contrib/ucw/array-sort.h b/src/contrib/ucw/array-sort.h new file mode 100644 index 0000000..1ff1377 --- /dev/null +++ b/src/contrib/ucw/array-sort.h @@ -0,0 +1,195 @@ +/* + * UCW Library -- Universal Simple Array Sorter + * + * (c) 2003--2008 Martin Mares <mj@ucw.cz> + * + * This software may be freely distributed and used according to the terms + * of the GNU Lesser General Public License. + */ + +#pragma once + +#include "contrib/macros.h" + +/* + * This is not a normal header file, it's a generator of sorting + * routines. Each time you include it with parameters set in the + * corresponding preprocessor macros, it generates an array sorter + * with the parameters given. + * + * You might wonder why the heck do we implement our own array sorter + * instead of using qsort(). The primary reason is that qsort handles + * only continuous arrays, but we need to sort array-like data structures + * where the only way to access elements is by using an indexing macro. + * Besides that, we are more than 2 times faster. + * + * So much for advocacy, there are the parameters (those marked with [*] + * are mandatory): + * + * ASORT_PREFIX(x) [*] add a name prefix (used on all global names + * defined by the sorter) + * ASORT_KEY_TYPE [*] data type of a single array entry key + * ASORT_ELT(i) returns the key of i-th element; if this macro is not + * defined, the function gets a pointer to an array to be sorted + * ASORT_LT(x,y) x < y for ASORT_KEY_TYPE (default: "x<y") + * ASORT_SWAP(i,j) swap i-th and j-th element (default: assume _ELT + * is an l-value and swap just the keys) + * ASORT_THRESHOLD threshold for switching between quicksort and insertsort + * ASORT_EXTRA_ARGS extra arguments for the sort function (they are always + * visible in all the macros supplied above), starts with comma + * + * After including this file, a function ASORT_PREFIX(sort)(unsigned array_size) + * or ASORT_PREFIX(sort)(ASORT_KEY_TYPE *array, unsigned array_size) [if ASORT_ELT + * is not defined] is declared and all parameter macros are automatically + * undef'd. + */ + +#ifndef ASORT_LT +#define ASORT_LT(x,y) ((x) < (y)) +#endif + +#ifndef ASORT_SWAP +#define ASORT_SWAP(i,j) do { ASORT_KEY_TYPE tmp = ASORT_ELT(i); ASORT_ELT(i)=ASORT_ELT(j); ASORT_ELT(j)=tmp; } while (0) +#endif + +#ifndef ASORT_THRESHOLD +#define ASORT_THRESHOLD 8 /* Guesswork and experimentation */ +#endif + +#ifndef ASORT_EXTRA_ARGS +#define ASORT_EXTRA_ARGS +#endif + +#ifndef ASORT_ELT +#define ASORT_ARRAY_ARG ASORT_KEY_TYPE *array, +#define ASORT_ELT(i) array[i] +#else +#define ASORT_ARRAY_ARG +#endif + +/** + * The generated sorting function. If `ASORT_ELT` macro is not provided, the + * @ASORT_ARRAY_ARG is equal to `ASORT_KEY_TYPE *array` and is the array to be + * sorted. If the macro is provided, this parameter is omitted. In that case, + * you can sort global variables or pass your structure by @ASORT_EXTRA_ARGS. + **/ +static void ASORT_PREFIX(sort)(ASORT_ARRAY_ARG unsigned array_size ASORT_EXTRA_ARGS) +{ + struct stk { int l, r; } stack[8*sizeof(unsigned)]; + int l, r, left, right, m; + unsigned sp = 0; + ASORT_KEY_TYPE pivot; + + if (array_size <= 1) + return; + + /* QuickSort with optimizations a'la Sedgewick, but stop at ASORT_THRESHOLD */ + + left = 0; + right = array_size - 1; + for(;;) + { + l = left; + r = right; + m = (l+r)/2; + if (ASORT_LT(ASORT_ELT(m), ASORT_ELT(l))) + ASORT_SWAP(l,m); + if (ASORT_LT(ASORT_ELT(r), ASORT_ELT(m))) + { + ASORT_SWAP(m,r); + if (ASORT_LT(ASORT_ELT(m), ASORT_ELT(l))) + ASORT_SWAP(l,m); + } + pivot = ASORT_ELT(m); + do + { + while (ASORT_LT(ASORT_ELT(l), pivot)) + l++; + while (ASORT_LT(pivot, ASORT_ELT(r))) + r--; + if (l < r) + { + ASORT_SWAP(l,r); + l++; + r--; + } + else if (l == r) + { + l++; + r--; + } + } + while (l <= r); + if ((r - left) >= ASORT_THRESHOLD && (right - l) >= ASORT_THRESHOLD) + { + /* Both partitions ok => push the larger one */ + if ((r - left) > (right - l)) + { + stack[sp].l = left; + stack[sp].r = r; + left = l; + } + else + { + stack[sp].l = l; + stack[sp].r = right; + right = r; + } + sp++; + } + else if ((r - left) >= ASORT_THRESHOLD) + { + /* Left partition OK, right undersize */ + right = r; + } + else if ((right - l) >= ASORT_THRESHOLD) + { + /* Right partition OK, left undersize */ + left = l; + } + else + { + /* Both partitions undersize => pop */ + if (!sp) + break; + sp--; + left = stack[sp].l; + right = stack[sp].r; + } + } + + /* + * We have a partially sorted array, finish by insertsort. Inspired + * by qsort() in GNU libc. + */ + + /* Find minimal element which will serve as a barrier */ + r = MIN(array_size, ASORT_THRESHOLD); + m = 0; + for (l=1; l<r; l++) + if (ASORT_LT(ASORT_ELT(l),ASORT_ELT(m))) + m = l; + ASORT_SWAP(0,m); + + /* Insertion sort */ + for (m=1; m<(int)array_size; m++) + { + l=m; + while (ASORT_LT(ASORT_ELT(m),ASORT_ELT(l-1))) + l--; + while (l < m) + { + ASORT_SWAP(l,m); + l++; + } + } +} + +#undef ASORT_PREFIX +#undef ASORT_KEY_TYPE +#undef ASORT_ELT +#undef ASORT_LT +#undef ASORT_SWAP +#undef ASORT_THRESHOLD +#undef ASORT_EXTRA_ARGS +#undef ASORT_ARRAY_ARG diff --git a/src/contrib/ucw/binsearch.h b/src/contrib/ucw/binsearch.h new file mode 100644 index 0000000..b791d39 --- /dev/null +++ b/src/contrib/ucw/binsearch.h @@ -0,0 +1,50 @@ +/* + * UCW Library -- Generic Binary Search + * + * (c) 2005 Martin Mares <mj@ucw.cz> + * + * This software may be freely distributed and used according to the terms + * of the GNU Lesser General Public License. + */ + +#pragma once + +/*** + * [[defs]] + * Definitions + * ----------- + ***/ + +/** + * Find the first element not lower than \p x in the sorted array \p ary of \p N elements (non-decreasing order). + * Returns the index of the found element or \p N if no exists. Uses `ary_lt_x(ary,i,x)` to compare the i'th element with \p x. + * The time complexity is `O(log(N))`. + **/ +#define BIN_SEARCH_FIRST_GE_CMP(ary, N, ary_lt_x, x, ...) ({ \ + unsigned l = 0, r = (N); \ + while (l < r) \ + { \ + unsigned m = (l+r)/2; \ + if (ary_lt_x(ary, m, x, __VA_ARGS__)) \ + l = m+1; \ + else \ + r = m; \ + } \ + l; \ +}) + +/** + * The default comparison macro for \ref BIN_SEARCH_FIRST_GE_CMP(). + **/ +#define ARY_LT_NUM(ary,i,x) (ary)[i] < (x) + +/** + * Same as \ref BIN_SEARCH_FIRST_GE_CMP(), but uses the default `<` operator for comparisons. + **/ +#define BIN_SEARCH_FIRST_GE(ary,N,x) BIN_SEARCH_FIRST_GE_CMP(ary,N,ARY_LT_NUM,x) + +/** + * Search the sorted array \p ary of \p N elements (non-decreasing) for the first occurrence of \p x. + * Returns the index or -1 if no such element exists. Uses the `<` operator for comparisons. + **/ +#define BIN_SEARCH_EQ(ary,N,x) ({ int i = BIN_SEARCH_FIRST_GE(ary,N,x); if (i >= (N) || (ary)[i] != (x)) i=-1; i; }) diff --git a/src/contrib/ucw/heap.c b/src/contrib/ucw/heap.c new file mode 100644 index 0000000..7d74aeb --- /dev/null +++ b/src/contrib/ucw/heap.c @@ -0,0 +1,153 @@ +/* + * Binary heap + * + * (c) 2012 Ondrej Filip <feela@network.cz> + * + * This software may be freely distributed and used according to the terms + * of the GNU Lesser General Public License. + */ + +/*** + * Introduction + * ------------ + * + * Binary heap is a simple data structure, which for example supports efficient insertions, deletions + * and access to the minimal inserted item. We define several macros for such operations. + * Note that because of simplicity of heaps, we have decided to define direct macros instead + * of a <<generic:,macro generator>> as for several other data structures in the Libucw. + * + * A heap is represented by a number of elements and by an array of values. Beware that we + * index this array from one, not from zero as do the standard C arrays. + * + * Most macros use these parameters: + * + * - @num - a variable (signed or unsigned integer) with the number of elements + * - @heap - a C array of type @type; the heap is stored in `heap[1] .. heap[num]`; `heap[0]` is unused + * + * A valid heap must follow these rules: + * + * - `num >= 0` + * - `heap[i] >= heap[i / 2]` for each `i` in `[2, num]` + * + * The first element `heap[1]` is always lower or equal to all other elements. + ***/ + +#include <string.h> +#include <stdlib.h> +#include "contrib/ucw/heap.h" + +static inline void heap_swap(heap_val_t **e1, heap_val_t **e2) +{ + if (e1 == e2) return; /* Stack tmp should be faster than tmpelem. */ + heap_val_t *tmp = *e1; /* Even faster than 2-XOR nowadays. */ + *e1 = *e2; + *e2 = tmp; + int pos = (*e1)->pos; + (*e1)->pos = (*e2)->pos; + (*e2)->pos = pos; +} + +int heap_init(struct heap *h, int (*cmp)(void *, void *), int init_size) +{ + int isize = init_size ? init_size : INITIAL_HEAP_SIZE; + + h->num = 0; + h->max_size = isize; + h->cmp = cmp; + h->data = malloc((isize + 1) * sizeof(heap_val_t*)); /* Temp element unused. */ + + return h->data ? 1 : 0; +} + +void heap_deinit(struct heap *h) +{ + free(h->data); + memset(h, 0, sizeof(*h)); +} + +static inline void _heap_bubble_down(struct heap *h, int e) +{ + int e1; + for (;;) { + e1 = 2 * e; + if (e1 > h->num) break; + if ((h->cmp(*HELEMENT(h, e), *HELEMENT(h, e1)) < 0) && + (e1 == h->num || (h->cmp(*HELEMENT(h, e), *HELEMENT(h, e1 + 1)) < 0))) break; + if ((e1 != h->num) && (h->cmp(*HELEMENT(h, e1 + 1), *HELEMENT(h, e1)) < 0)) e1++; + heap_swap(HELEMENT(h, e), HELEMENT(h, e1)); + e = e1; + } +} + +static inline void _heap_bubble_up(struct heap *h, int e) +{ + int e1; + while (e > 1) { + e1 = e / 2; + if (h->cmp(*HELEMENT(h, e1), *HELEMENT(h, e)) < 0) break; + heap_swap(HELEMENT(h, e), HELEMENT(h, e1)); + e = e1; + } +} + +void heap_replace(struct heap *h, int pos, heap_val_t *e) +{ + *HELEMENT(h, pos) = e; + e->pos = pos; + + if (pos == 1 || h->cmp(*HELEMENT(h, pos / 2), e) < 0) { + _heap_bubble_down(h, pos); + } else { + _heap_bubble_up(h, pos); + } +} + +void heap_delmin(struct heap *h) +{ + if (h->num == 0) return; + if (h->num > 1) { + heap_swap(HHEAD(h), HELEMENT(h, h->num)); + } + (*HELEMENT(h, h->num))->pos = 0; + h->num--; + _heap_bubble_down(h, 1); +} + +int heap_insert(struct heap *h, heap_val_t *e) +{ + if (h->num == h->max_size) { + h->max_size = h->max_size * HEAP_INCREASE_STEP; + h->data = realloc(h->data, (h->max_size + 1) * sizeof(heap_val_t*)); + if (!h->data) { + return 0; + } + } + + h->num++; + *HELEMENT(h, h->num) = e; + e->pos = h->num; + _heap_bubble_up(h, h->num); + return 1; +} + +int heap_find(struct heap *h, heap_val_t *elm) +{ + return ((struct heap_val *) elm)->pos; +} + +void heap_delete(struct heap *h, int e) +{ + heap_swap(HELEMENT(h, e), HELEMENT(h, h->num)); + (*HELEMENT(h, h->num))->pos = 0; + h->num--; + if (h->cmp(*HELEMENT(h, e), *HELEMENT(h, h->num + 1)) < 0) { + _heap_bubble_up(h, e); + } else { + _heap_bubble_down(h, e); + } + + if ((h->num > INITIAL_HEAP_SIZE) && (h->num < h->max_size / HEAP_DECREASE_THRESHOLD)) { + h->max_size = h->max_size / HEAP_INCREASE_STEP; + h->data = realloc(h->data, (h->max_size + 1) * sizeof(heap_val_t*)); + } +} diff --git a/src/contrib/ucw/heap.h b/src/contrib/ucw/heap.h new file mode 100644 index 0000000..f85bb82 --- /dev/null +++ b/src/contrib/ucw/heap.h @@ -0,0 +1,46 @@ +/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#pragma once + +struct heap_val { + int pos; +}; + +typedef struct heap_val heap_val_t; + +struct heap { + int num; /* Number of elements */ + int max_size; /* Size of allocated memory */ + int (*cmp)(void *, void *); + heap_val_t **data; +}; /* Array follows */ + +#define INITIAL_HEAP_SIZE 512 /* initial heap size */ +#define HEAP_INCREASE_STEP 2 /* multiplier for each inflation, keep conservative */ +#define HEAP_DECREASE_THRESHOLD 2 /* threshold for deflation, keep conservative */ +#define HELEMENT(h,num) ((h)->data + (num)) +#define HHEAD(h) HELEMENT((h), 1) +#define EMPTY_HEAP(h) ((h)->num == 0) + +int heap_init(struct heap *, int (*cmp)(void *, void *), int); +void heap_deinit(struct heap *); + +void heap_delmin(struct heap *); +int heap_insert(struct heap *, heap_val_t *); +int heap_find(struct heap *, heap_val_t *); +void heap_delete(struct heap *, int); +void heap_replace(struct heap *, int, heap_val_t *); diff --git a/src/contrib/ucw/lists.c b/src/contrib/ucw/lists.c new file mode 100644 index 0000000..01af28f --- /dev/null +++ b/src/contrib/ucw/lists.c @@ -0,0 +1,264 @@ +/* + * BIRD Library -- Linked Lists + * + * (c) 1998 Martin Mares <mj@ucw.cz> + * (c) 2015, 2020-2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +/** + * DOC: Linked lists + * + * The BIRD library provides a set of functions for operating on linked + * lists. The lists are internally represented as standard doubly linked + * lists with synthetic head and tail which makes all the basic operations + * run in constant time and contain no extra end-of-list checks. Each list + * is described by a &list structure, nodes can have any format as long + * as they start with a &node structure. If you want your nodes to belong + * to multiple lists at once, you can embed multiple &node structures in them + * and use the SKIP_BACK() macro to calculate a pointer to the start of the + * structure from a &node pointer, but beware of obscurity. + * + * There also exist safe linked lists (&slist, &snode and all functions + * being prefixed with |s_|) which support asynchronous walking very + * similar to that used in the &fib structure. + */ + +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +#include "contrib/ucw/lists.h" +#include "contrib/mempattern.h" + +/** + * add_tail - append a node to a list + * \p l: linked list + * \p n: list node + * + * add_tail() takes a node \p n and appends it at the end of the list \p l. + */ +void +add_tail(list_t *l, node_t *n) +{ + node_t *z = &l->tail; + + n->next = z; + n->prev = z->prev; + z->prev->next = n; + z->prev = n; + assert(z->next == NULL); +} + +/** + * add_head - prepend a node to a list + * \p l: linked list + * \p n: list node + * + * add_head() takes a node \p n and prepends it at the start of the list \p l. + */ +void +add_head(list_t *l, node_t *n) +{ + node_t *z = &l->head; + + n->next = z->next; + n->prev = z; + z->next->prev = n; + z->next = n; + assert(z->prev == NULL); +} + +/** + * insert_node - insert a node to a list + * \p n: a new list node + * \p after: a node of a list + * + * Inserts a node \p n to a linked list after an already inserted + * node \p after. + */ +void +insert_node(node_t *n, node_t *after) +{ + node_t *z = after->next; + + n->next = z; + n->prev = after; + after->next = n; + z->prev = n; +} + +/** + * rem_node - remove a node from a list + * \p n: node to be removed + * + * Removes a node \p n from the list it's linked in. + */ +void +rem_node(node_t *n) +{ + node_t *z = n->prev; + node_t *x = n->next; + + z->next = x; + x->prev = z; + n->prev = 0; + n->next = 0; +} + +/** + * init_list - create an empty list + * \p l: list + * + * init_list() takes a &list structure and initializes its + * fields, so that it represents an empty list. + */ +void +init_list(list_t *l) +{ + l->head.next = &l->tail; + l->head.prev = NULL; + l->tail.next = NULL; + l->tail.prev = &l->head; +} + +/** + * add_tail_list - concatenate two lists + * \p to: destination list + * \p l: source list + * + * This function appends all elements of the list \p l to + * the list \p to in constant time. + */ +void +add_tail_list(list_t *to, list_t *l) +{ + node_t *p = to->tail.prev; + node_t *q = l->head.next; + + p->next = q; + q->prev = p; + to->tail.prev = l->tail.prev; +} + +/** + * list_dup - duplicate list + * \p to: destination list + * \p l: source list + * + * This function duplicates all elements of the list \p l to + * the list \p to in linear time. + * + * This function only works with a homogenous item size. + */ +void list_dup(list_t *dst, list_t *src, size_t itemsz) +{ + node_t *n; + WALK_LIST(n, *src) { + node_t *i = malloc(itemsz); + memcpy(i, n, itemsz); + add_tail(dst, i); + } +} + +/** + * list_size - gets number of nodes + * \p l: list + * + * This function counts nodes in list \p l and returns this number. + */ +size_t list_size(const list_t *l) +{ + size_t count = 0; + + node_t *n; + WALK_LIST(n, *l) { + count++; + } + + return count; +} + +/** + * fix_list - correction of head/tail pointers when list had been memmove'd + * \p l: list + * + * WARNING: must not be called on empty list + */ +void fix_list(list_t *l) +{ + node_t *n = HEAD(*l); + assert(n->next != NULL); + n->prev = &l->head; + + n = TAIL(*l); + assert(n->prev != NULL); + n->next = &l->tail; +} + +/** + * ptrlist_add - add pointer to pointer list + * \p to: destination list + * \p val: added pointer + * \p mm: memory context + */ +ptrnode_t *ptrlist_add(list_t *to, void *val, knot_mm_t *mm) +{ + ptrnode_t *node = mm_alloc(mm , sizeof(ptrnode_t)); + if (node == NULL) { + return NULL; + } else { + node->d = val; + } + add_tail(to, &node->n); + return node; +} + +/** + * ptrlist_free - free all nodes in pointer list + * \p list: list nodes + * \p mm: memory context + */ +void ptrlist_free(list_t *list, knot_mm_t *mm) +{ + node_t *n, *nxt; + WALK_LIST_DELSAFE(n, nxt, *list) { + mm_free(mm, n); + } + init_list(list); +} + +/** + * ptrlist_rem - remove pointer node + * \p val: pointer to remove + * \p mm: memory context + */ +void ptrlist_rem(ptrnode_t *node, knot_mm_t *mm) +{ + rem_node(&node->n); + mm_free(mm, node); +} + +/** + * ptrlist_deep_free - free all nodes incl referenced data + * \p list: list nodes + * \p mm: memory context + */ +void ptrlist_deep_free(list_t *l, knot_mm_t *mm) +{ + ptrnode_t *n; + WALK_LIST(n, *l) { + mm_free(mm, n->d); + } + ptrlist_free(l, mm); +} + +void ptrlist_free_custom(list_t *l, knot_mm_t *mm, ptrlist_free_cb free_cb) +{ + ptrnode_t *n; + WALK_LIST(n, *l) { + free_cb(n->d); + } + ptrlist_free(l, mm); +} diff --git a/src/contrib/ucw/lists.h b/src/contrib/ucw/lists.h new file mode 100644 index 0000000..1a3ca95 --- /dev/null +++ b/src/contrib/ucw/lists.h @@ -0,0 +1,74 @@ +/* + * BIRD Library -- Linked Lists + * + * (c) 1998 Martin Mares <mj@ucw.cz> + * (c) 2015, 2020-2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#pragma once + +#include <string.h> +#include "libknot/mm_ctx.h" + +typedef struct node { + struct node *next, *prev; +} node_t; + +typedef struct list { + node_t head, tail; +} list_t; + +#define NODE (node_t *) +#define HEAD(list) ((void *)((list).head.next)) +#define TAIL(list) ((void *)((list).tail.prev)) +#define WALK_LIST(n,list) for(n=HEAD(list);(NODE (n))->next; \ + n=(void *)((NODE (n))->next)) +#define WALK_LIST_DELSAFE(n,nxt,list) \ + for(n=HEAD(list); (nxt=(void *)((NODE (n))->next)); n=(void *) nxt) +/* WALK_LIST_FIRST supposes that called code removes each processed node */ +#define WALK_LIST_FIRST(n,list) \ + while(n=HEAD(list), (NODE (n))->next) +#define WALK_LIST_BACKWARDS(n,list) for(n=TAIL(list);(NODE (n))->prev; \ + n=(void *)((NODE (n))->prev)) +#define WALK_LIST_BACKWARDS_DELSAFE(n,prv,list) \ + for(n=TAIL(list); prv=(void *)((NODE (n))->prev); n=(void *) prv) + +#define EMPTY_LIST(list) (!(NODE HEAD(list))->next) + +/*! \brief Free every node in the list. */ +#define WALK_LIST_FREE(list) \ + do { \ + node_t *n=0,*nxt=0; \ + WALK_LIST_DELSAFE(n,nxt,list) { \ + free(n); \ + } \ + init_list(&list); \ + } while(0) + +void add_tail(list_t *, node_t *); +void add_head(list_t *, node_t *); +void rem_node(node_t *); +void add_tail_list(list_t *, list_t *); +void init_list(list_t *); +void insert_node(node_t *, node_t *); +void list_dup(list_t *dst, list_t *src, size_t itemsz); +size_t list_size(const list_t *); +void fix_list(list_t *); + +/*! + * \brief Generic pointer list implementation. + */ +typedef struct ptrnode { + node_t n; + void *d; +} ptrnode_t; + +ptrnode_t *ptrlist_add(list_t *, void *, knot_mm_t *); +void ptrlist_free(list_t *, knot_mm_t *); +void ptrlist_rem(ptrnode_t *node, knot_mm_t *mm); +void ptrlist_deep_free(list_t *, knot_mm_t *); + +typedef void (*ptrlist_free_cb)(void *); +void ptrlist_free_custom(list_t *l, knot_mm_t *mm, ptrlist_free_cb free_cb); diff --git a/src/contrib/ucw/mempool.c b/src/contrib/ucw/mempool.c new file mode 100644 index 0000000..8e835c1 --- /dev/null +++ b/src/contrib/ucw/mempool.c @@ -0,0 +1,323 @@ +/* + * UCW Library -- Memory Pools (One-Time Allocation) + * + * (c) 1997--2001 Martin Mares <mj@ucw.cz> + * (c) 2007 Pavel Charvat <pchar@ucw.cz> + * (c) 2015, 2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + * + * This software may be freely distributed and used according to the terms + * of the GNU Lesser General Public License. + */ + +#undef LOCAL_DEBUG + +#include <string.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> +#include <assert.h> +#include "contrib/asan.h" +#include "contrib/macros.h" +#include "contrib/ucw/mempool.h" + +/** \todo This shouldn't be precalculated, but computed on load. */ +#define CPU_PAGE_SIZE 4096 + +/** Align an integer \p s to the nearest higher multiple of \p a (which should be a power of two) **/ +#define ALIGN_TO(s, a) (((s)+a-1)&~(a-1)) +#define MP_CHUNK_TAIL ALIGN_TO(sizeof(struct mempool_chunk), CPU_STRUCT_ALIGN) +#define MP_SIZE_MAX (~0U - MP_CHUNK_TAIL - CPU_PAGE_SIZE) +#define DBG(s, ...) + +/** \note Imported MMAP backend from bigalloc.c */ +#define CONFIG_UCW_POOL_IS_MMAP +#ifdef CONFIG_UCW_POOL_IS_MMAP +#include <sys/mman.h> +static void * +page_alloc(uint64_t len) +{ + if (!len) { + return NULL; + } + if (len > SIZE_MAX) { + return NULL; + } + assert(!(len & (CPU_PAGE_SIZE-1))); + uint8_t *p = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); + if (p == (uint8_t*) MAP_FAILED) { + return NULL; + } + return p; +} + +static void +page_free(void *start, uint64_t len) +{ + assert(!(len & (CPU_PAGE_SIZE-1))); + assert(!((uintptr_t) start & (CPU_PAGE_SIZE-1))); + munmap(start, len); +} +#endif + +struct mempool_chunk { + struct mempool_chunk *next; + unsigned size; +}; + +static unsigned +mp_align_size(unsigned size) +{ +#ifdef CONFIG_UCW_POOL_IS_MMAP + return ALIGN_TO(size + MP_CHUNK_TAIL, CPU_PAGE_SIZE) - MP_CHUNK_TAIL; +#else + return ALIGN_TO(size, CPU_STRUCT_ALIGN); +#endif +} + +void +mp_init(struct mempool *pool, unsigned chunk_size) +{ + chunk_size = mp_align_size(MAX(sizeof(struct mempool), chunk_size)); + *pool = (struct mempool) { + .chunk_size = chunk_size, + .threshold = chunk_size >> 1, + .last_big = &pool->last_big + }; +} + +static void * +mp_new_big_chunk(unsigned size) +{ + uint8_t *data = malloc(size + MP_CHUNK_TAIL); + if (!data) { + return NULL; + } + ASAN_POISON_MEMORY_REGION(data, size); + struct mempool_chunk *chunk = (struct mempool_chunk *)(data + size); + chunk->size = size; + return chunk; +} + +static void +mp_free_big_chunk(struct mempool_chunk *chunk) +{ + void *ptr = (uint8_t *)chunk - chunk->size; + ASAN_UNPOISON_MEMORY_REGION(ptr, chunk->size); + free(ptr); +} + +static void * +mp_new_chunk(unsigned size) +{ +#ifdef CONFIG_UCW_POOL_IS_MMAP + uint8_t *data = page_alloc(size + MP_CHUNK_TAIL); + if (!data) { + return NULL; + } + ASAN_POISON_MEMORY_REGION(data, size); + struct mempool_chunk *chunk = (struct mempool_chunk *)(data + size); + chunk->size = size; + return chunk; +#else + return mp_new_big_chunk(size); +#endif +} + +static void +mp_free_chunk(struct mempool_chunk *chunk) +{ +#ifdef CONFIG_UCW_POOL_IS_MMAP + uint8_t *data = (uint8_t *)chunk - chunk->size; + ASAN_UNPOISON_MEMORY_REGION(data, chunk->size); + page_free(data, chunk->size + MP_CHUNK_TAIL); +#else + mp_free_big_chunk(chunk); +#endif +} + +struct mempool * +mp_new(unsigned chunk_size) +{ + chunk_size = mp_align_size(MAX(sizeof(struct mempool), chunk_size)); + struct mempool_chunk *chunk = mp_new_chunk(chunk_size); + struct mempool *pool = (void *)chunk - chunk_size; + ASAN_UNPOISON_MEMORY_REGION(pool, sizeof(*pool)); + DBG("Creating mempool %p with %u bytes long chunks", pool, chunk_size); + chunk->next = NULL; + ASAN_POISON_MEMORY_REGION(chunk, sizeof(struct mempool_chunk)); + *pool = (struct mempool) { + .state = { .free = { chunk_size - sizeof(*pool) }, .last = { chunk } }, + .chunk_size = chunk_size, + .threshold = chunk_size >> 1, + .last_big = &pool->last_big + }; + return pool; +} + +static void +mp_free_chain(struct mempool_chunk *chunk) +{ + while (chunk) { + ASAN_UNPOISON_MEMORY_REGION(chunk, sizeof(struct mempool_chunk)); + struct mempool_chunk *next = chunk->next; + mp_free_chunk(chunk); + chunk = next; + } +} + +static void +mp_free_big_chain(struct mempool_chunk *chunk) +{ + while (chunk) { + ASAN_UNPOISON_MEMORY_REGION(chunk, sizeof(struct mempool_chunk)); + struct mempool_chunk *next = chunk->next; + mp_free_big_chunk(chunk); + chunk = next; + } +} + +void +mp_delete(struct mempool *pool) +{ + if (pool == NULL) { + return; + } + DBG("Deleting mempool %p", pool); + mp_free_big_chain(pool->state.last[1]); + mp_free_chain(pool->unused); + mp_free_chain(pool->state.last[0]); // can contain the mempool structure +} + +void +mp_flush(struct mempool *pool) +{ + mp_free_big_chain(pool->state.last[1]); + struct mempool_chunk *chunk = pool->state.last[0], *next; + while (chunk) { + ASAN_UNPOISON_MEMORY_REGION(chunk, sizeof(struct mempool_chunk)); + if ((uint8_t *)chunk - chunk->size == (uint8_t *)pool) { + break; + } + next = chunk->next; + chunk->next = pool->unused; + ASAN_POISON_MEMORY_REGION(chunk, sizeof(struct mempool_chunk)); + pool->unused = chunk; + chunk = next; + } + pool->state.last[0] = chunk; + if (chunk) { + pool->state.free[0] = chunk->size - sizeof(*pool); + ASAN_POISON_MEMORY_REGION(chunk, sizeof(struct mempool_chunk)); + } else { + pool->state.free[0] = 0; + } + pool->state.last[1] = NULL; + pool->state.free[1] = 0; + pool->last_big = &pool->last_big; +} + +static void +mp_stats_chain(struct mempool_chunk *chunk, struct mempool_stats *stats, unsigned idx) +{ + struct mempool_chunk *next; + while (chunk) { + ASAN_UNPOISON_MEMORY_REGION(chunk, sizeof(struct mempool_chunk)); + stats->chain_size[idx] += chunk->size + sizeof(*chunk); + stats->chain_count[idx]++; + next = chunk->next; + ASAN_POISON_MEMORY_REGION(chunk, sizeof(struct mempool_chunk)); + chunk = next; + } + stats->total_size += stats->chain_size[idx]; +} + +void +mp_stats(struct mempool *pool, struct mempool_stats *stats) +{ + bzero(stats, sizeof(*stats)); + mp_stats_chain(pool->state.last[0], stats, 0); + mp_stats_chain(pool->state.last[1], stats, 1); + mp_stats_chain(pool->unused, stats, 2); +} + +uint64_t +mp_total_size(struct mempool *pool) +{ + struct mempool_stats stats; + mp_stats(pool, &stats); + return stats.total_size; +} + +static void * +mp_alloc_internal(struct mempool *pool, unsigned size) +{ + struct mempool_chunk *chunk; + if (size <= pool->threshold) { + pool->idx = 0; + if (pool->unused) { + chunk = pool->unused; + ASAN_UNPOISON_MEMORY_REGION(chunk, sizeof(struct mempool_chunk)); + pool->unused = chunk->next; + } else { + chunk = mp_new_chunk(pool->chunk_size); + } + chunk->next = pool->state.last[0]; + ASAN_POISON_MEMORY_REGION(chunk, sizeof(struct mempool_chunk)); + pool->state.last[0] = chunk; + pool->state.free[0] = pool->chunk_size - size; + return (uint8_t *)chunk - pool->chunk_size; + } else if (size <= MP_SIZE_MAX) { + pool->idx = 1; + unsigned aligned = ALIGN_TO(size, CPU_STRUCT_ALIGN); + chunk = mp_new_big_chunk(aligned); + if (!chunk) { + return NULL; + } + chunk->next = pool->state.last[1]; + ASAN_POISON_MEMORY_REGION(chunk, sizeof(struct mempool_chunk)); + pool->state.last[1] = chunk; + pool->state.free[1] = aligned - size; + return pool->last_big = (uint8_t *)chunk - aligned; + } else { + fprintf(stderr, "Cannot allocate %u bytes from a mempool", size); + assert(0); + return NULL; + } +} + +void * +mp_alloc(struct mempool *pool, unsigned size) +{ + unsigned avail = pool->state.free[0] & ~(CPU_STRUCT_ALIGN - 1); + void *ptr = NULL; + if (size <= avail) { + pool->state.free[0] = avail - size; + ptr = (uint8_t*)pool->state.last[0] - avail; + } else { + ptr = mp_alloc_internal(pool, size); + } + ASAN_UNPOISON_MEMORY_REGION(ptr, size); + return ptr; +} + +void * +mp_alloc_noalign(struct mempool *pool, unsigned size) +{ + void *ptr = NULL; + if (size <= pool->state.free[0]) { + ptr = (uint8_t*)pool->state.last[0] - pool->state.free[0]; + pool->state.free[0] -= size; + } else { + ptr = mp_alloc_internal(pool, size); + } + ASAN_UNPOISON_MEMORY_REGION(ptr, size); + return ptr; +} + +void * +mp_alloc_zero(struct mempool *pool, unsigned size) +{ + void *ptr = mp_alloc(pool, size); + bzero(ptr, size); + return ptr; +} diff --git a/src/contrib/ucw/mempool.h b/src/contrib/ucw/mempool.h new file mode 100644 index 0000000..c5a4fa8 --- /dev/null +++ b/src/contrib/ucw/mempool.h @@ -0,0 +1,124 @@ +/* + * UCW Library -- Memory Pools + * + * (c) 1997--2005 Martin Mares <mj@ucw.cz> + * (c) 2007 Pavel Charvat <pchar@ucw.cz> + * (c) 2015, 2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + * + * This software may be freely distributed and used according to the terms + * of the GNU Lesser General Public License. + */ + +#pragma once + +#include <string.h> +#include <stdint.h> + +#define CPU_STRUCT_ALIGN (sizeof(void*)) + +/*** + * [[defs]] + * Definitions + * ----------- + ***/ + +/** + * Memory pool state (see mp_push(), ...). + * You should use this one as an opaque handle only, the insides are internal. + **/ +struct mempool_state { + unsigned free[2]; + void *last[2]; +}; + +/** + * Memory pool. + * You should use this one as an opaque handle only, the insides are internal. + **/ +struct mempool { + struct mempool_state state; + void *unused, *last_big; + unsigned chunk_size, threshold, idx; +}; + +struct mempool_stats { /** Mempool statistics. See mp_stats(). **/ + uint64_t total_size; /** Real allocated size in bytes. */ + unsigned chain_count[3]; /** Number of allocated chunks in small/big/unused chains. */ + unsigned chain_size[3]; /** Size of allocated chunks in small/big/unused chains. */ +}; + +/*** + * [[basic]] + * Basic manipulation + * ------------------ + ***/ + +/** + * Initialize a given mempool structure. + * \p chunk_size must be in the interval `[1, UINT_MAX / 2]`. + * It will allocate memory by this large chunks and take + * memory to satisfy requests from them. + * + * Memory pools can be treated as <<trans:respools,resources>>, see <<trans:res_mempool()>>. + **/ +void mp_init(struct mempool *pool, unsigned chunk_size); + +/** + * Allocate and initialize a new memory pool. + * See \ref mp_init() for \p chunk_size limitations. + * + * The new mempool structure is allocated on the new mempool. + * + * Memory pools can be treated as <<trans:respools,resources>>, see <<trans:res_mempool()>>. + **/ +struct mempool *mp_new(unsigned chunk_size); + +/** + * Cleanup mempool initialized by mp_init or mp_new. + * Frees all the memory allocated by this mempool and, + * if created by \ref mp_new(), the \p pool itself. + **/ +void mp_delete(struct mempool *pool); + +/** + * Frees all data on a memory pool, but leaves it working. + * It can keep some of the chunks allocated to serve + * further allocation requests. Leaves the \p pool alive, + * even if it was created with \ref mp_new(). + **/ +void mp_flush(struct mempool *pool); + +/** + * Compute some statistics for debug purposes. + * See the definition of the <<struct_mempool_stats,mempool_stats structure>>. + **/ +void mp_stats(struct mempool *pool, struct mempool_stats *stats); +uint64_t mp_total_size(struct mempool *pool); /** How many bytes were allocated by the pool. **/ + +/*** + * [[alloc]] + * Allocation routines + * ------------------- + ***/ + +/** + * The function allocates new \p size bytes on a given memory pool. + * If the \p size is zero, the resulting pointer is undefined, + * but it may be safely reallocated or used as the parameter + * to other functions below. + * + * The resulting pointer is always aligned to a multiple of + * `CPU_STRUCT_ALIGN` bytes and this condition remains true also + * after future reallocations. + **/ +void *mp_alloc(struct mempool *pool, unsigned size); + +/** + * The same as \ref mp_alloc(), but the result may be unaligned. + **/ +void *mp_alloc_noalign(struct mempool *pool, unsigned size); + +/** + * The same as \ref mp_alloc(), but fills the newly allocated memory with zeroes. + **/ +void *mp_alloc_zero(struct mempool *pool, unsigned size); diff --git a/src/contrib/url-parser/LICENSE b/src/contrib/url-parser/LICENSE new file mode 100644 index 0000000..0161703 --- /dev/null +++ b/src/contrib/url-parser/LICENSE @@ -0,0 +1 @@ +../licenses/MIT
\ No newline at end of file diff --git a/src/contrib/url-parser/README.md b/src/contrib/url-parser/README.md new file mode 100644 index 0000000..c3b1919 --- /dev/null +++ b/src/contrib/url-parser/README.md @@ -0,0 +1,14 @@ +url-parser +========== + +This is Joyent's [URL parser][original-source] from their +[http-parser][joyent-http-parser] library. + +## LICENSE + +This derivative is licensed under the same terms as NGINX and copyright Joyent, +Inc. and other Node contributors as outlined in their [LICENSE][]. + +[original-source]: https://github.com/joyent/http-parser/blob/e01811e7f4894d7f0f7f4bd8492cccec6f6b4038/http_parser.c#L2343 +[joyent-http-parser]: https://github.com/joyent/http-parser +[LICENSE]: https://github.com/joyent/http-parser/blob/master/LICENSE-MIT diff --git a/src/contrib/url-parser/url_parser.c b/src/contrib/url-parser/url_parser.c new file mode 100644 index 0000000..5084740 --- /dev/null +++ b/src/contrib/url-parser/url_parser.c @@ -0,0 +1,635 @@ +/* Based on src/http/ngx_http_parse.c from NGINX copyright Igor Sysoev + * + * Additional changes are licensed under the same terms as NGINX and + * copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * 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. + */ + +/** + * This is the http_parser_parse_url function extracted from joyent's + * http-parser library. + */ + +#include <assert.h> +#include <stdint.h> +#include <stdlib.h> + +#include "url_parser.h" + +/* Macros for character classes; depends on strict-mode */ +#define CR '\r' +#define LF '\n' +#define LOWER(c) (unsigned char)(c | 0x20) +#define IS_ALPHA(c) (LOWER(c) >= 'a' && LOWER(c) <= 'z') +#define IS_NUM(c) ((c) >= '0' && (c) <= '9') +#define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_NUM(c)) +#define IS_HEX(c) (IS_NUM(c) || (LOWER(c) >= 'a' && LOWER(c) <= 'f')) +#define IS_MARK(c) ((c) == '-' || (c) == '_' || (c) == '.' || \ + (c) == '!' || (c) == '~' || (c) == '*' || (c) == '\'' || (c) == '(' || \ + (c) == ')') +#define IS_USERINFO_CHAR(c) (IS_ALPHANUM(c) || IS_MARK(c) || (c) == '%' || \ + (c) == ';' || (c) == ':' || (c) == '&' || (c) == '=' || (c) == '+' || \ + (c) == '$' || (c) == ',') + +#define STRICT_TOKEN(c) (tokens[(unsigned char)c]) + +#ifndef BIT_AT +# define BIT_AT(a, i) \ + (!!((unsigned int) (a)[(unsigned int) (i) >> 3] & \ + (1 << ((unsigned int) (i) & 7)))) +#endif + +#if HTTP_PARSER_STRICT +# define T(v) 0 +#else +# define T(v) v +#endif + +static const uint8_t normal_url_char[32] = { +/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, +/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ + 0 | T(2) | 0 | 0 | T(16) | 0 | 0 | 0, +/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, +/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, +/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ + 0 | 2 | 4 | 0 | 16 | 32 | 64 | 128, +/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, +/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, }; + +#if HTTP_PARSER_STRICT +#define TOKEN(c) (tokens[(unsigned char)c]) +#define IS_URL_CHAR(c) (BIT_AT(normal_url_char, (unsigned char)c)) +#define IS_HOST_CHAR(c) (IS_ALPHANUM(c) || (c) == '.' || (c) == '-') +#else +#define TOKEN(c) ((c == ' ') ? ' ' : tokens[(unsigned char)c]) +#define IS_URL_CHAR(c) \ + (BIT_AT(normal_url_char, (unsigned char)c) || ((c) & 0x80)) +#define IS_HOST_CHAR(c) \ + (IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_') +#endif + + + +enum state + { s_dead = 1 /* important that this is > 0 */ + + , s_start_req_or_res + , s_res_or_resp_H + , s_start_res + , s_res_H + , s_res_HT + , s_res_HTT + , s_res_HTTP + , s_res_first_http_major + , s_res_http_major + , s_res_first_http_minor + , s_res_http_minor + , s_res_first_status_code + , s_res_status_code + , s_res_status_start + , s_res_status + , s_res_line_almost_done + + , s_start_req + + , s_req_method + , s_req_spaces_before_url + , s_req_schema + , s_req_schema_slash + , s_req_schema_slash_slash + , s_req_server_start + , s_req_server + , s_req_server_with_at + , s_req_path + , s_req_query_string_start + , s_req_query_string + , s_req_fragment_start + , s_req_fragment + , s_req_http_start + , s_req_http_H + , s_req_http_HT + , s_req_http_HTT + , s_req_http_HTTP + , s_req_first_http_major + , s_req_http_major + , s_req_first_http_minor + , s_req_http_minor + , s_req_line_almost_done + + , s_header_field_start + , s_header_field + , s_header_value_discard_ws + , s_header_value_discard_ws_almost_done + , s_header_value_discard_lws + , s_header_value_start + , s_header_value + , s_header_value_lws + + , s_header_almost_done + + , s_chunk_size_start + , s_chunk_size + , s_chunk_parameters + , s_chunk_size_almost_done + + , s_headers_almost_done + , s_headers_done + + /* Important: 's_headers_done' must be the last 'header' state. All + * states beyond this must be 'body' states. It is used for overflow + * checking. See the PARSING_HEADER() macro. + */ + + , s_chunk_data + , s_chunk_data_almost_done + , s_chunk_data_done + + , s_body_identity + , s_body_identity_eof + + , s_message_done + }; + + +enum http_host_state + { + s_http_host_dead = 1 + , s_http_userinfo_start + , s_http_userinfo + , s_http_host_start + , s_http_host_v6_start + , s_http_host + , s_http_host_v6 + , s_http_host_v6_end + , s_http_host_v6_zone_start + , s_http_host_v6_zone + , s_http_host_port_start + , s_http_host_port +}; + +/* Our URL parser. + * + * This is designed to be shared by http_parser_execute() for URL validation, + * hence it has a state transition + byte-for-byte interface. In addition, it + * is meant to be embedded in http_parser_parse_url(), which does the dirty + * work of turning state transitions URL components for its API. + * + * This function should only be invoked with non-space characters. It is + * assumed that the caller cares about (and can detect) the transition between + * URL and non-URL states by looking for these. + */ +static enum state +parse_url_char(enum state s, const char ch) +{ + if (ch == ' ' || ch == '\r' || ch == '\n') { + return s_dead; + } + +#if HTTP_PARSER_STRICT + if (ch == '\t' || ch == '\f') { + return s_dead; + } +#endif + + switch (s) { + case s_req_spaces_before_url: + /* Proxied requests are followed by scheme of an absolute URI (alpha). + * All methods except CONNECT are followed by '/' or '*'. + */ + + if (ch == '/' || ch == '*') { + return s_req_path; + } + + if (IS_ALPHA(ch)) { + return s_req_schema; + } + + break; + + case s_req_schema: + if (IS_ALPHA(ch)) { + return s; + } + + if (ch == ':') { + return s_req_schema_slash; + } + + break; + + case s_req_schema_slash: + if (ch == '/') { + return s_req_schema_slash_slash; + } + + break; + + case s_req_schema_slash_slash: + if (ch == '/') { + return s_req_server_start; + } + + break; + + case s_req_server_with_at: + if (ch == '@') { + return s_dead; + } + + /* FALLTHROUGH */ + case s_req_server_start: + case s_req_server: + if (ch == '/') { + return s_req_path; + } + + if (ch == '?') { + return s_req_query_string_start; + } + + if (ch == '@') { + return s_req_server_with_at; + } + + if (IS_USERINFO_CHAR(ch) || ch == '[' || ch == ']') { + return s_req_server; + } + + break; + + case s_req_path: + if (IS_URL_CHAR(ch)) { + return s; + } + + switch (ch) { + case '?': + return s_req_query_string_start; + + case '#': + return s_req_fragment_start; + } + + break; + + case s_req_query_string_start: + case s_req_query_string: + if (IS_URL_CHAR(ch)) { + return s_req_query_string; + } + + switch (ch) { + case '?': + /* allow extra '?' in query string */ + return s_req_query_string; + + case '#': + return s_req_fragment_start; + } + + break; + + case s_req_fragment_start: + if (IS_URL_CHAR(ch)) { + return s_req_fragment; + } + + switch (ch) { + case '?': + return s_req_fragment; + + case '#': + return s; + } + + break; + + case s_req_fragment: + if (IS_URL_CHAR(ch)) { + return s; + } + + switch (ch) { + case '?': + case '#': + return s; + } + + break; + + default: + break; + } + + /* We should never fall out of the switch above unless there's an error */ + return s_dead; +} + +static enum http_host_state +http_parse_host_char(enum http_host_state s, const char ch) { + switch(s) { + case s_http_userinfo: + case s_http_userinfo_start: + if (ch == '@') { + return s_http_host_start; + } + + if (IS_USERINFO_CHAR(ch)) { + return s_http_userinfo; + } + break; + + case s_http_host_start: + if (ch == '[') { + return s_http_host_v6_start; + } + + if (IS_HOST_CHAR(ch)) { + return s_http_host; + } + + break; + + case s_http_host: + if (IS_HOST_CHAR(ch)) { + return s_http_host; + } + + /* FALLTHROUGH */ + case s_http_host_v6_end: + if (ch == ':') { + return s_http_host_port_start; + } + + break; + + case s_http_host_v6: + if (ch == ']') { + return s_http_host_v6_end; + } + + /* FALLTHROUGH */ + case s_http_host_v6_start: + if (IS_HEX(ch) || ch == ':' || ch == '.') { + return s_http_host_v6; + } + + if (s == s_http_host_v6 && ch == '%') { + return s_http_host_v6_zone_start; + } + break; + + case s_http_host_v6_zone: + if (ch == ']') { + return s_http_host_v6_end; + } + + /* FALLTHROUGH */ + case s_http_host_v6_zone_start: + /* RFC 6874 Zone ID consists of 1*( unreserved / pct-encoded) */ + if (IS_ALPHANUM(ch) || ch == '%' || ch == '.' || ch == '-' || ch == '_' || + ch == '~') { + return s_http_host_v6_zone; + } + break; + + case s_http_host_port: + case s_http_host_port_start: + if (IS_NUM(ch)) { + return s_http_host_port; + } + + break; + + default: + break; + } + return s_http_host_dead; +} + +static int +http_parse_host(const char * buf, struct http_parser_url *u, int found_at) { + assert(u->field_set & (1 << UF_HOST)); + enum http_host_state s; + + const char *p; + size_t buflen = u->field_data[UF_HOST].off + u->field_data[UF_HOST].len; + + u->field_data[UF_HOST].len = 0; + + s = found_at ? s_http_userinfo_start : s_http_host_start; + + for (p = buf + u->field_data[UF_HOST].off; p < buf + buflen; p++) { + enum http_host_state new_s = http_parse_host_char(s, *p); + + if (new_s == s_http_host_dead) { + return 1; + } + + switch(new_s) { + case s_http_host: + if (s != s_http_host) { + u->field_data[UF_HOST].off = p - buf; + } + u->field_data[UF_HOST].len++; + break; + + case s_http_host_v6: + if (s != s_http_host_v6) { + u->field_data[UF_HOST].off = p - buf; + } + u->field_data[UF_HOST].len++; + break; + + case s_http_host_v6_zone_start: + case s_http_host_v6_zone: + u->field_data[UF_HOST].len++; + break; + + case s_http_host_port: + if (s != s_http_host_port) { + u->field_data[UF_PORT].off = p - buf; + u->field_data[UF_PORT].len = 0; + u->field_set |= (1 << UF_PORT); + } + u->field_data[UF_PORT].len++; + break; + + case s_http_userinfo: + if (s != s_http_userinfo) { + u->field_data[UF_USERINFO].off = p - buf ; + u->field_data[UF_USERINFO].len = 0; + u->field_set |= (1 << UF_USERINFO); + } + u->field_data[UF_USERINFO].len++; + break; + + default: + break; + } + s = new_s; + } + + /* Make sure we don't end somewhere unexpected */ + switch (s) { + case s_http_host_start: + case s_http_host_v6_start: + case s_http_host_v6: + case s_http_host_v6_zone_start: + case s_http_host_v6_zone: + case s_http_host_port_start: + case s_http_userinfo: + case s_http_userinfo_start: + return 1; + default: + break; + } + + return 0; +} + + +int +http_parser_parse_url(const char *buf, size_t buflen, int is_connect, + struct http_parser_url *u) +{ + enum state s; + const char *p; + enum http_parser_url_fields uf, old_uf; + int found_at = 0; + + u->port = u->field_set = 0; + s = is_connect ? s_req_server_start : s_req_spaces_before_url; + old_uf = UF_MAX; + + for (p = buf; p < buf + buflen; p++) { + s = parse_url_char(s, *p); + + /* Figure out the next field that we're operating on */ + switch (s) { + case s_dead: + return 1; + + /* Skip delimiters */ + case s_req_schema_slash: + case s_req_schema_slash_slash: + case s_req_server_start: + case s_req_query_string_start: + case s_req_fragment_start: + continue; + + case s_req_schema: + uf = UF_SCHEMA; + break; + + case s_req_server_with_at: + found_at = 1; + + /* FALLTHROUGH */ + case s_req_server: + uf = UF_HOST; + break; + + case s_req_path: + uf = UF_PATH; + break; + + case s_req_query_string: + uf = UF_QUERY; + break; + + case s_req_fragment: + uf = UF_FRAGMENT; + break; + + default: + assert(!"Unexpected state"); + return 1; + } + + /* Nothing's changed; soldier on */ + if (uf == old_uf) { + u->field_data[uf].len++; + continue; + } + + u->field_data[uf].off = p - buf; + u->field_data[uf].len = 1; + + u->field_set |= (1 << uf); + old_uf = uf; + } + + /* host must be present if there is a schema */ + /* parsing http:///toto will fail */ + if ((u->field_set & (1 << UF_SCHEMA)) && + (u->field_set & (1 << UF_HOST)) == 0) { + return 1; + } + + if (u->field_set & (1 << UF_HOST)) { + if (http_parse_host(buf, u, found_at) != 0) { + return 1; + } + } + + /* CONNECT requests can only contain "hostname:port" */ + if (is_connect && u->field_set != ((1 << UF_HOST)|(1 << UF_PORT))) { + return 1; + } + + if (u->field_set & (1 << UF_PORT)) { + /* Don't bother with endp; we've already validated the string */ + unsigned long v = strtoul(buf + u->field_data[UF_PORT].off, NULL, 10); + + /* Ports have a max value of 2^16 */ + if (v > 0xffff) { + return 1; + } + + u->port = (uint16_t) v; + } + + return 0; +} diff --git a/src/contrib/url-parser/url_parser.h b/src/contrib/url-parser/url_parser.h new file mode 100644 index 0000000..0f916e0 --- /dev/null +++ b/src/contrib/url-parser/url_parser.h @@ -0,0 +1,64 @@ +/* Based on src/http/ngx_http_parse.c from NGINX copyright Igor Sysoev + * + * Additional changes are licensed under the same terms as NGINX and + * copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * 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. + */ + +/** + * This is the http_parser_parse_url function extracted from joyent's + * http-parser library. + */ + +#pragma once + +#include <stdint.h> + +enum http_parser_url_fields + { UF_SCHEMA = 0 + , UF_HOST = 1 + , UF_PORT = 2 + , UF_PATH = 3 + , UF_QUERY = 4 + , UF_FRAGMENT = 5 + , UF_USERINFO = 6 + , UF_MAX = 7 + }; + +/* Result structure for http_parser_parse_url(). + * + * Callers should index into field_data[] with UF_* values iff field_set + * has the relevant (1 << UF_*) bit set. As a courtesy to clients (and + * because we probably have padding left over), we convert any port to + * a uint16_t. + */ +struct http_parser_url { + uint16_t field_set; /* Bitmask of (1 << UF_*) values */ + uint16_t port; /* Converted UF_PORT string */ + + struct { + uint16_t off; /* Offset into buffer in which field starts */ + uint16_t len; /* Length of run in buffer */ + } field_data[UF_MAX]; +}; + +int +http_parser_parse_url(const char *buf, size_t buflen, int is_connect, + struct http_parser_url *u); diff --git a/src/contrib/vpool/vpool.c b/src/contrib/vpool/vpool.c new file mode 100644 index 0000000..f130a47 --- /dev/null +++ b/src/contrib/vpool/vpool.c @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2006, 2008 Alexey Vatchenko <av@bsdua.org> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> + +#include <errno.h> +#include <limits.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include "contrib/vpool/vpool.h" + +static void vpool_shift(struct vpool *pool); +static int vpool_new_size(struct vpool *pool, size_t datsize, + size_t *size); +static int vpool_resize(struct vpool *pool, size_t datsize); + +static void +vpool_shift(struct vpool *pool) +{ + if (pool->v_buf != pool->v_basebuf) { + memmove(pool->v_basebuf, pool->v_buf, pool->v_off); + pool->v_buf = pool->v_basebuf; + } +} + +static int +vpool_new_size(struct vpool *pool, size_t datsize, size_t *size) +{ + size_t need; + size_t rem; + + if (datsize <= pool->v_size - pool->v_off) { + *size = pool->v_size; + return (0); + } + + /* Check limit of new requested size */ + if (pool->v_limit - pool->v_off < datsize) { + return (EFBIG); + } + need = pool->v_off + datsize; + + /* Check limit of new size aligned to block size */ + rem = need % pool->v_blksize; + if (rem != 0) { + if (pool->v_limit - pool->v_off >= + datsize + (pool->v_blksize - rem)) { + need += pool->v_blksize - rem; + } else { + need = pool->v_limit; + } + } + + *size = need; + return (0); +} + +static int +vpool_resize(struct vpool *pool, size_t datsize) +{ + void *ret; + size_t size; + int error; + + error = vpool_new_size(pool, datsize, &size); + if (error != 0) { + return (error); + } + + if (size > pool->v_size) { + ret = malloc(size); + if (ret == NULL) { + return (ENOMEM); + } + + if (pool->v_off > 0) { + memcpy(ret, pool->v_buf, pool->v_off); + } + free(pool->v_basebuf); + pool->v_basebuf = pool->v_buf = ret; + pool->v_size = size; + } else if ((pool->v_size - pool->v_off) - + (pool->v_buf - pool->v_basebuf) < datsize) { + vpool_shift(pool); + } + + return (0); +} + +void +vpool_init(struct vpool *pool, size_t blksize, size_t limit) +{ + + pool->v_basebuf = pool->v_buf = NULL; + pool->v_off = pool->v_size = 0; + + pool->v_blksize = (blksize == 0) ? 4096 : blksize; /* XXX */ + pool->v_limit = (limit == 0) ? SIZE_MAX : limit; + + pool->v_lasterr = 0; +} + +void +vpool_final(struct vpool *pool) +{ + free(pool->v_basebuf); +} + +void +vpool_reset(struct vpool *pool) +{ + free(pool->v_basebuf); + pool->v_basebuf = pool->v_buf = NULL; + pool->v_off = pool->v_size = 0; + pool->v_lasterr = 0; +} + +void +vpool_wipe(struct vpool *pool) +{ + pool->v_off = 0; + pool->v_lasterr = 0; +} + +void * +vpool_insert(struct vpool *pool, size_t where, void *data, size_t datsize) +{ + void *ret; + int error; + + error = vpool_resize(pool, datsize); + if (error != 0) { + pool->v_lasterr = error; + return (NULL); + } + + /* + * If ``where'' is greater than or equal to offset then + * we are appending data to the end of the buffer. + */ + if (where > pool->v_off) { + where = pool->v_off; + } + + ret = (uint8_t *)pool->v_buf + where; + if (pool->v_off - where > 0) { + memmove(ret + datsize, ret, pool->v_off - where); + } + memcpy(ret, data, datsize); + pool->v_off += datsize; + pool->v_lasterr = 0; + + return (ret); +} + +void * +vpool_expand(struct vpool *pool, size_t where, size_t size) +{ + void *ret; + int error; + + error = vpool_resize(pool, size); + if (error != 0) { + pool->v_lasterr = error; + return (NULL); + } + + /* + * If ``where'' is greater than or equal to offset then + * we are appending data to the end of the buffer. + */ + if (where > pool->v_off) { + where = pool->v_off; + } + + ret = (uint8_t *)pool->v_buf + where; + if (pool->v_off - where > 0) { + memmove(ret + size, ret, pool->v_off - where); + } + pool->v_off += size; + pool->v_lasterr = 0; + + return (ret); +} + +int +vpool_truncate(struct vpool *pool, + size_t where, size_t size, enum vpool_trunc how) +{ + /* Check if caller wants to remove more data than we have */ + if (where >= pool->v_off || + size > pool->v_off || pool->v_off - size < where) { + pool->v_lasterr = ERANGE; + return (pool->v_lasterr); + } + + if (how == VPOOL_EXCLUDE) { + if (where == 0) { + /* + * Optimization. + * Don't move data, just adjust pointer. + */ + pool->v_buf = (uint8_t *)pool->v_buf + size; + } else { + memmove((uint8_t *)pool->v_buf + where, + (uint8_t *)pool->v_buf + where + size, + pool->v_off - size - where); + } + pool->v_off -= size; + } else { + pool->v_buf = (uint8_t *)pool->v_buf + where; + pool->v_off = size; + } + + pool->v_lasterr = 0; + return (0); +} + +void +vpool_export(struct vpool *pool, void **buf, size_t *size) +{ + vpool_shift(pool); + *buf = pool->v_buf; + *size = pool->v_off; + pool->v_basebuf = pool->v_buf = NULL; + pool->v_off = pool->v_size = 0; + pool->v_lasterr = 0; +} diff --git a/src/contrib/vpool/vpool.h b/src/contrib/vpool/vpool.h new file mode 100644 index 0000000..82e3d66 --- /dev/null +++ b/src/contrib/vpool/vpool.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2006, 2008 Alexey Vatchenko <av@bsdua.org> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * VPool: implementation of pool of data with a variable size. + */ +#ifndef _VPOOL_H_ +#define _VPOOL_H_ + +#include <stddef.h> +#include <limits.h> + +struct vpool { + void *v_basebuf; /* pointer returned by (re|m)alloc() */ + void *v_buf; /* actual data starts here */ + size_t v_off; + size_t v_size; + + size_t v_blksize; + size_t v_limit; + int v_lasterr; +}; + +enum vpool_trunc {VPOOL_EXCLUDE, VPOOL_INCLUDE}; +#define VPOOL_TAIL UINT_MAX + +void vpool_init(struct vpool *pool, size_t blksize, size_t limit); +void vpool_final(struct vpool *pool); + +void vpool_reset(struct vpool *pool); +void vpool_wipe(struct vpool *pool); + +void * vpool_insert(struct vpool *pool, + size_t where, void *data, size_t datsize); +void * vpool_expand(struct vpool *pool, size_t where, size_t size); + +int vpool_truncate(struct vpool *pool, + size_t where, size_t size, enum vpool_trunc how); + +#define vpool_is_empty(pool) ((pool)->v_off == 0) +#define vpool_get_buf(pool) ((pool)->v_buf) +#define vpool_get_length(pool) ((pool)->v_off) +#define vpool_get_error(pool) ((pool)->v_lasterr) + +void vpool_export(struct vpool *pool, void **buf, size_t *size); + +#endif /* !_VPOOL_H_ */ diff --git a/src/contrib/wire_ctx.h b/src/contrib/wire_ctx.h new file mode 100644 index 0000000..f4f1789 --- /dev/null +++ b/src/contrib/wire_ctx.h @@ -0,0 +1,355 @@ +/* Copyright (C) 2019 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <assert.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <string.h> +#include <sys/types.h> + +#include "contrib/string.h" +#include "libknot/endian.h" +#include "libknot/errcode.h" + +/*! + * \brief Struct to keep the wire context. + */ +typedef struct wire_ctx { + size_t size; + uint8_t *wire; + uint8_t *position; + int error; + bool readonly; +} wire_ctx_t; + +/*! + * \brief Initialize wire context. + */ +static inline wire_ctx_t wire_ctx_init(uint8_t *data, size_t size) +{ + assert(data); + + wire_ctx_t result = { + .size = size, + .wire = data, + .position = data, + .error = KNOT_EOK, + .readonly = false + }; + + return result; +} + +/*! + * \brief Initialize read only wire context. + * + * \note No write is performed, and error is set to KNOT_EACCES. + * + */ +static inline wire_ctx_t wire_ctx_init_const(const uint8_t *data, size_t size) +{ + assert(data); + + wire_ctx_t result = wire_ctx_init((uint8_t *)data, size); + result.readonly = true; + + return result; +} + +/*! + * \brief Gets actual position. + * + * \return position from the begin. + */ +static inline size_t wire_ctx_offset(wire_ctx_t *ctx) +{ + assert(ctx); + + return ctx->position - ctx->wire; +} + +/*! + * \brief Set position offset from the begin. + * + * \param offset Wire offset (starts from 0). + * + * \note Noop if previous error. + */ +static inline void wire_ctx_set_offset(wire_ctx_t *ctx, size_t offset) +{ + assert(ctx); + + if (ctx->error != KNOT_EOK) { + return; + } + + if (offset > ctx->size) { + ctx->error = KNOT_ERANGE; + return; + } + + ctx->position = ctx->wire + offset; +} + +/*! + * \brief Gets available bytes. + * + * \return Number of bytes to end. + */ +static inline size_t wire_ctx_available(wire_ctx_t *ctx) +{ + assert(ctx); + + return ctx->size - wire_ctx_offset(ctx); +} + +/*! + * \brief Add offset to the current position. + * + * \note Noop if previous error. + */ +static inline void wire_ctx_skip(wire_ctx_t *ctx, ssize_t offset) +{ + assert(ctx); + + if (ctx->error != KNOT_EOK) { + return; + } + + // Check for out of scope skip. + if (offset >= 0) { + if (offset > wire_ctx_available(ctx)) { + ctx->error = KNOT_ERANGE; + return; + } + } else { + if (-offset > wire_ctx_offset(ctx)) { + ctx->error = KNOT_ERANGE; + return; + } + } + + ctx->position += offset; +} + +/*! + * \brief Check the context if reading is possible. + */ +static inline int wire_ctx_can_read(wire_ctx_t *ctx, size_t size) +{ + assert(ctx); + + if (ctx->error != KNOT_EOK) { + return ctx->error; + } + + if (wire_ctx_available(ctx) < size) { + return KNOT_EFEWDATA; + } + + return KNOT_EOK; +} + +/*! + * \brief Check the context if writing is possible. + */ +static inline int wire_ctx_can_write(wire_ctx_t *ctx, size_t size) +{ + assert(ctx); + + if (ctx->error != KNOT_EOK) { + return ctx->error; + } + + if (ctx->readonly) { + return KNOT_EACCES; + } + + if (wire_ctx_available(ctx) < size) { + return KNOT_ESPACE; + } + + return KNOT_EOK; +} + + +static inline void wire_ctx_read(wire_ctx_t *ctx, void *data, size_t size) +{ + assert(ctx); + assert(data); + + if (ctx->error != KNOT_EOK) { + /* Avoid leaving data uninitialized. */ + memzero(data, size); + return; + } + + int ret = wire_ctx_can_read(ctx, size); + if (ret != KNOT_EOK) { + ctx->error = ret; + memzero(data, size); + return; + } + + memcpy(data, ctx->position, size); + ctx->position += size; +} + +static inline uint8_t wire_ctx_read_u8(wire_ctx_t *ctx) +{ + uint8_t result; + wire_ctx_read(ctx, &result, sizeof(result)); + + return result; +} + +static inline uint16_t wire_ctx_read_u16(wire_ctx_t *ctx) +{ + uint16_t result; + wire_ctx_read(ctx, &result, sizeof(result)); + + return be16toh(result); +} + +static inline uint32_t wire_ctx_read_u32(wire_ctx_t *ctx) +{ + uint32_t result; + wire_ctx_read(ctx, &result, sizeof(result)); + + return be32toh(result); +} + +static inline uint64_t wire_ctx_read_u48(wire_ctx_t *ctx) +{ + /* This case is slightly tricky. */ + uint64_t result = 0; + wire_ctx_read(ctx, (uint8_t *)&result + 1, 6); + + return be64toh(result) >> 8; +} + +static inline uint64_t wire_ctx_read_u64(wire_ctx_t *ctx) +{ + uint64_t result; + wire_ctx_read(ctx, &result, sizeof(result)); + + return be64toh(result); +} + + +static inline void wire_ctx_write(wire_ctx_t *ctx, const void *data, size_t size) +{ + assert(ctx); + + if (ctx->error != KNOT_EOK) { + return; + } + + if (size == 0) { + return; + } + + assert(data); + + int ret = wire_ctx_can_write(ctx, size); + if (ret != KNOT_EOK) { + ctx->error = ret; + return; + } + + memcpy(ctx->position, data, size); + ctx->position += size; +} + +static inline void wire_ctx_write_u8(wire_ctx_t *ctx, uint8_t value) +{ + wire_ctx_write(ctx, &value, sizeof(value)); +} + +static inline void wire_ctx_write_u16(wire_ctx_t *ctx, uint16_t value) +{ + uint16_t beval = htobe16(value); + wire_ctx_write(ctx, &beval, sizeof(beval)); +} + +static inline void wire_ctx_write_u32(wire_ctx_t *ctx, uint32_t value) +{ + uint32_t beval = htobe32(value); + wire_ctx_write(ctx, &beval, sizeof(beval)); +} + +static inline void wire_ctx_write_u48(wire_ctx_t *ctx, uint64_t value) +{ + /* This case is slightly tricky. */ + uint64_t swapped = htobe64(value << 8); + wire_ctx_write(ctx, (uint8_t *)&swapped + 1, 6); +} + +static inline void wire_ctx_write_u64(wire_ctx_t *ctx, uint64_t value) +{ + uint64_t beval = htobe64(value); + wire_ctx_write(ctx, &beval, sizeof(beval)); +} + +static inline void wire_ctx_clear(wire_ctx_t *ctx, size_t size) +{ + assert(ctx); + + if (ctx->error != KNOT_EOK) { + return; + } + + if (size == 0) { + return; + } + + int ret = wire_ctx_can_write(ctx, size); + if (ret != KNOT_EOK) { + ctx->error = ret; + return; + } + + memzero(ctx->position, size); + ctx->position += size; +} + +static inline void wire_ctx_copy(wire_ctx_t *dst, wire_ctx_t *src, size_t size) +{ + assert(dst); + assert(src); + + if (size == 0 || dst->error != KNOT_EOK) { + return; + } + + if (wire_ctx_can_read(src, size) != KNOT_EOK) { + dst->error = KNOT_EFEWDATA; + return; + } + + int ret = wire_ctx_can_write(dst, size); + if (ret != KNOT_EOK) { + dst->error = ret; + return; + } + + memcpy(dst->position, src->position, size); + dst->position += size; + src->position += size; +} |