summaryrefslogtreecommitdiffstats
path: root/lib/ngtcp2_conn.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ngtcp2_conn.c')
-rw-r--r--lib/ngtcp2_conn.c13676
1 files changed, 13676 insertions, 0 deletions
diff --git a/lib/ngtcp2_conn.c b/lib/ngtcp2_conn.c
new file mode 100644
index 0000000..1bbda3f
--- /dev/null
+++ b/lib/ngtcp2_conn.c
@@ -0,0 +1,13676 @@
+/*
+ * 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"
+
+/* 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
+
+/*
+ * 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; }
+
+/*
+ * conn_is_handshake_completed returns nonzero if QUIC handshake has
+ * completed.
+ */
+static int conn_is_handshake_completed(ngtcp2_conn *conn) {
+ return (conn->flags & NGTCP2_CONN_FLAG_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_crypto_level crypto_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, crypto_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;
+
+ if (!conn->callbacks.path_validation) {
+ return 0;
+ }
+
+ if (pv->flags & NGTCP2_PV_FLAG_PREFERRED_ADDR) {
+ flags |= NGTCP2_PATH_VALIDATION_FLAG_PREFERRED_ADDR;
+ }
+
+ rv = conn->callbacks.path_validation(conn, flags, &pv->dcid.ps.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_address_present);
+
+ rv = conn->callbacks.select_preferred_addr(
+ conn, dest, &conn->remote.transport_params->preferred_address,
+ 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, (int)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 ngtcp2_vec *token) {
+ int rv;
+
+ if (!conn->callbacks.recv_new_token) {
+ return 0;
+ }
+
+ rv = conn->callbacks.recv_new_token(conn, token, 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_handshake_completed(conn)) {
+ flags |= NGTCP2_DATAGRAM_FLAG_EARLY;
+ }
+
+ 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_crypto_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_crypto_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 crypto_offset_less(const ngtcp2_ksl_key *lhs,
+ const ngtcp2_ksl_key *rhs) {
+ return *(int64_t *)lhs < *(int64_t *)rhs;
+}
+
+static int pktns_init(ngtcp2_pktns *pktns, ngtcp2_pktns_id pktns_id,
+ ngtcp2_rst *rst, ngtcp2_cc *cc, 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 = -1;
+ 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,
+ NULL, mem);
+
+ ngtcp2_ksl_init(&pktns->crypto.tx.frq, crypto_offset_less, sizeof(uint64_t),
+ mem);
+
+ ngtcp2_rtb_init(&pktns->rtb, pktns_id, &pktns->crypto.strm, rst, cc, 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, 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, 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) {
+ ngtcp2_frame_chain *frc;
+ ngtcp2_ksl_it it;
+
+ 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);
+
+ for (it = ngtcp2_ksl_begin(&pktns->crypto.tx.frq); !ngtcp2_ksl_it_end(&it);
+ ngtcp2_ksl_it_next(&it)) {
+ frc = ngtcp2_ksl_it_get(&it);
+ ngtcp2_frame_chain_objalloc_del(frc, pktns->rtb.frc_objalloc, mem);
+ }
+
+ ngtcp2_ksl_free(&pktns->crypto.tx.frq);
+ 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 void cc_del(ngtcp2_cc *cc, ngtcp2_cc_algo cc_algo,
+ const ngtcp2_mem *mem) {
+ switch (cc_algo) {
+ case NGTCP2_CC_ALGO_RENO:
+ ngtcp2_cc_reno_cc_free(cc, mem);
+ break;
+ case NGTCP2_CC_ALGO_CUBIC:
+ ngtcp2_cc_cubic_cc_free(cc, mem);
+ break;
+ case NGTCP2_CC_ALGO_BBR:
+ ngtcp2_cc_bbr_cc_free(cc, mem);
+ break;
+ case NGTCP2_CC_ALGO_BBR2:
+ ngtcp2_cc_bbr2_cc_free(cc, mem);
+ break;
+ default:
+ break;
+ }
+}
+
+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_rate = 0.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);
+}
+
+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_other_versions is the default other_versions field
+ sent by server. */
+static uint8_t server_default_other_versions[] = {0, 0, 0, 1};
+
+/*
+ * other_versions_new allocates new buffer, and writes |versions| of
+ * length |versionslen| in network byte order, suitable for sending in
+ * other_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 other_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;
+
+ /* grease_quic_bit is always enabled. */
+ p->grease_quic_bit = 1;
+
+ if (conn->server) {
+ p->version_info.chosen_version = chosen_version;
+ } else {
+ p->version_info.chosen_version = conn->client_chosen_version;
+ }
+ p->version_info.other_versions = conn->vneg.other_versions;
+ p->version_info.other_versionslen = conn->vneg.other_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;
+ (void)callbacks_version;
+ (void)settings_version;
+ (void)transport_params_version;
+
+ 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(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 || 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;
+ }
+
+ 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->token.len) {
+ buf = ngtcp2_mem_malloc(mem, settings->token.len);
+ if (buf == NULL) {
+ rv = NGTCP2_ERR_NOMEM;
+ goto fail_token;
+ }
+ memcpy(buf, settings->token.base, settings->token.len);
+ (*pconn)->local.settings.token.base = buf;
+ } else {
+ (*pconn)->local.settings.token.base = NULL;
+ (*pconn)->local.settings.token.len = 0;
+ }
+
+ 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:
+ rv = ngtcp2_cc_reno_cc_init(&(*pconn)->cc, &(*pconn)->log, mem);
+ if (rv != 0) {
+ goto fail_cc_init;
+ }
+ break;
+ case NGTCP2_CC_ALGO_CUBIC:
+ rv = ngtcp2_cc_cubic_cc_init(&(*pconn)->cc, &(*pconn)->log, mem);
+ if (rv != 0) {
+ goto fail_cc_init;
+ }
+ break;
+ case NGTCP2_CC_ALGO_BBR:
+ rv = ngtcp2_cc_bbr_cc_init(&(*pconn)->cc, &(*pconn)->log, &(*pconn)->cstat,
+ &(*pconn)->rst, settings->initial_ts,
+ callbacks->rand, &settings->rand_ctx, mem);
+ if (rv != 0) {
+ goto fail_cc_init;
+ }
+ break;
+ case NGTCP2_CC_ALGO_BBR2:
+ rv = ngtcp2_cc_bbr2_cc_init(&(*pconn)->cc, &(*pconn)->log, &(*pconn)->cstat,
+ &(*pconn)->rst, settings->initial_ts,
+ callbacks->rand, &settings->rand_ctx, mem);
+ if (rv != 0) {
+ goto fail_cc_init;
+ }
+ break;
+ default:
+ ngtcp2_unreachable();
+ }
+
+ rv = pktns_new(&(*pconn)->in_pktns, NGTCP2_PKTNS_ID_INITIAL, &(*pconn)->rst,
+ &(*pconn)->cc, &(*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, &(*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, &(*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;
+ }
+
+ if (settings->other_versionslen) {
+ if (!server && !ngtcp2_is_reserved_version(client_chosen_version)) {
+ for (i = 0; i < settings->other_versionslen; ++i) {
+ if (settings->other_versions[i] == client_chosen_version) {
+ break;
+ }
+ }
+
+ assert(i < settings->other_versionslen);
+ }
+
+ for (i = 0; i < settings->other_versionslen; ++i) {
+ assert(ngtcp2_is_reserved_version(settings->other_versions[i]) ||
+ ngtcp2_is_supported_version(settings->other_versions[i]));
+ }
+
+ rv = other_versions_new(&buf, settings->other_versions,
+ settings->other_versionslen, mem);
+ if (rv != 0) {
+ goto fail_other_versions;
+ }
+
+ (*pconn)->vneg.other_versions = buf;
+ (*pconn)->vneg.other_versionslen =
+ sizeof(uint32_t) * settings->other_versionslen;
+ } else if (server) {
+ if (settings->preferred_versionslen) {
+ rv = other_versions_new(&buf, settings->preferred_versions,
+ settings->preferred_versionslen, mem);
+ if (rv != 0) {
+ goto fail_other_versions;
+ }
+
+ (*pconn)->vneg.other_versions = buf;
+ (*pconn)->vneg.other_versionslen =
+ sizeof(uint32_t) * settings->preferred_versionslen;
+ } else {
+ (*pconn)->vneg.other_versions = server_default_other_versions;
+ (*pconn)->vneg.other_versionslen = sizeof(server_default_other_versions);
+ }
+ } else if (!server && !ngtcp2_is_reserved_version(client_chosen_version)) {
+ rv = other_versions_new(&buf, &client_chosen_version, 1, mem);
+ if (rv != 0) {
+ goto fail_other_versions;
+ }
+
+ (*pconn)->vneg.other_versions = buf;
+ (*pconn)->vneg.other_versionslen = sizeof(uint32_t);
+ }
+
+ (*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)->server = server;
+ (*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)->early.discard_started_ts = UINT64_MAX;
+
+ conn_reset_ecn_validation_state(*pconn);
+
+ ngtcp2_qlog_start(&(*pconn)->qlog, server ? &settings->qlog.odcid : dcid,
+ server);
+
+ return 0;
+
+fail_other_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:
+ cc_del(&(*pconn)->cc, settings->cc_algo, mem);
+fail_cc_init:
+ ngtcp2_mem_free(mem, (*pconn)->local.settings.token.base);
+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.token.len) {
+ /* 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.other_versions != server_default_other_versions) {
+ ngtcp2_mem_free(conn->mem, conn->vneg.other_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, conn->local.settings.token.base);
+
+ 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);
+
+ cc_del(&conn->cc, conn->cc_algo, 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, 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);
+}
+
+/*
+ * 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.
+ * If |nodelay| is nonzero, delayed ACK timer is ignored.
+ *
+ * The memory for ACK frame is dynamically allocated by this function.
+ * A caller is responsible to free it.
+ *
+ * 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.
+ */
+static int 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 {
+ 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_RCV,
+ "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;
+}
+
+static void conn_cryptofrq_clear(ngtcp2_conn *conn, ngtcp2_pktns *pktns) {
+ ngtcp2_frame_chain *frc;
+ ngtcp2_ksl_it it;
+
+ for (it = ngtcp2_ksl_begin(&pktns->crypto.tx.frq); !ngtcp2_ksl_it_end(&it);
+ ngtcp2_ksl_it_next(&it)) {
+ frc = ngtcp2_ksl_it_get(&it);
+ ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem);
+ }
+ ngtcp2_ksl_clear(&pktns->crypto.tx.frq);
+}
+
+/*
+ * conn_cryptofrq_unacked_offset returns the CRYPTO frame offset by
+ * taking into account acknowledged offset. If there is no data to
+ * send, this function returns (uint64_t)-1.
+ */
+static uint64_t conn_cryptofrq_unacked_offset(ngtcp2_conn *conn,
+ ngtcp2_pktns *pktns) {
+ ngtcp2_frame_chain *frc;
+ ngtcp2_crypto *fr;
+ ngtcp2_range gap;
+ ngtcp2_rtb *rtb = &pktns->rtb;
+ ngtcp2_ksl_it it;
+ uint64_t datalen;
+
+ (void)conn;
+
+ for (it = ngtcp2_ksl_begin(&pktns->crypto.tx.frq); !ngtcp2_ksl_it_end(&it);
+ ngtcp2_ksl_it_next(&it)) {
+ frc = ngtcp2_ksl_it_get(&it);
+ fr = &frc->fr.crypto;
+
+ gap = ngtcp2_strm_get_unacked_range_after(rtb->crypto, 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;
+ }
+ }
+
+ return (uint64_t)-1;
+}
+
+static int conn_cryptofrq_unacked_pop(ngtcp2_conn *conn, ngtcp2_pktns *pktns,
+ ngtcp2_frame_chain **pfrc) {
+ ngtcp2_frame_chain *frc, *nfrc;
+ ngtcp2_crypto *fr, *nfr;
+ uint64_t offset, end_offset;
+ size_t idx, end_idx;
+ uint64_t base_offset, end_base_offset;
+ ngtcp2_range gap;
+ ngtcp2_rtb *rtb = &pktns->rtb;
+ ngtcp2_vec *v;
+ int rv;
+ ngtcp2_ksl_it it;
+
+ *pfrc = NULL;
+
+ for (it = ngtcp2_ksl_begin(&pktns->crypto.tx.frq); !ngtcp2_ksl_it_end(&it);) {
+ frc = ngtcp2_ksl_it_get(&it);
+ fr = &frc->fr.crypto;
+
+ ngtcp2_ksl_remove_hint(&pktns->crypto.tx.frq, &it, &it, &fr->offset);
+
+ idx = 0;
+ offset = fr->offset;
+ base_offset = 0;
+
+ gap = ngtcp2_strm_get_unacked_range_after(rtb->crypto, 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) {
+ ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->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_crypto_datacnt_objalloc_new(
+ &nfrc, fr->datacnt - end_idx, &conn->frc_objalloc, conn->mem);
+ if (rv != 0) {
+ ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem);
+ return rv;
+ }
+
+ nfr = &nfrc->fr.crypto;
+ nfr->type = NGTCP2_FRAME_CRYPTO;
+ memcpy(nfr->data, fr->data + end_idx,
+ sizeof(nfr->data[0]) * (fr->datacnt - end_idx));
+
+ assert(nfr->data[0].len > end_base_offset);
+
+ 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(&pktns->crypto.tx.frq, NULL, &nfr->offset, nfrc);
+ if (rv != 0) {
+ assert(ngtcp2_err_is_fatal(rv));
+ ngtcp2_frame_chain_objalloc_del(nfrc, &conn->frc_objalloc, conn->mem);
+ ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->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->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;
+}
+static int conn_cryptofrq_pop(ngtcp2_conn *conn, ngtcp2_frame_chain **pfrc,
+ ngtcp2_pktns *pktns, size_t left) {
+ ngtcp2_crypto *fr, *nfr;
+ ngtcp2_frame_chain *frc, *nfrc;
+ int rv;
+ size_t nmerged;
+ uint64_t datalen;
+ ngtcp2_vec a[NGTCP2_MAX_CRYPTO_DATACNT];
+ ngtcp2_vec b[NGTCP2_MAX_CRYPTO_DATACNT];
+ size_t acnt, bcnt;
+ ngtcp2_ksl_it it;
+
+ rv = conn_cryptofrq_unacked_pop(conn, pktns, &frc);
+ if (rv != 0) {
+ return rv;
+ }
+ if (frc == NULL) {
+ *pfrc = NULL;
+ return 0;
+ }
+
+ fr = &frc->fr.crypto;
+ datalen = ngtcp2_vec_len(fr->data, fr->datacnt);
+
+ 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_CRYPTO_DATACNT);
+
+ assert(acnt > 0);
+ assert(bcnt > 0);
+
+ rv = ngtcp2_frame_chain_crypto_datacnt_objalloc_new(
+ &nfrc, bcnt, &conn->frc_objalloc, conn->mem);
+ if (rv != 0) {
+ assert(ngtcp2_err_is_fatal(rv));
+ ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem);
+ return rv;
+ }
+
+ nfr = &nfrc->fr.crypto;
+ nfr->type = NGTCP2_FRAME_CRYPTO;
+ nfr->offset = fr->offset + left;
+ nfr->datacnt = bcnt;
+ ngtcp2_vec_copy(nfr->data, b, bcnt);
+
+ rv = ngtcp2_ksl_insert(&pktns->crypto.tx.frq, NULL, &nfr->offset, nfrc);
+ if (rv != 0) {
+ assert(ngtcp2_err_is_fatal(rv));
+ ngtcp2_frame_chain_objalloc_del(nfrc, &conn->frc_objalloc, conn->mem);
+ ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem);
+ return rv;
+ }
+
+ rv = ngtcp2_frame_chain_crypto_datacnt_objalloc_new(
+ &nfrc, acnt, &conn->frc_objalloc, conn->mem);
+ if (rv != 0) {
+ assert(ngtcp2_err_is_fatal(rv));
+ ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem);
+ return rv;
+ }
+
+ nfr = &nfrc->fr.crypto;
+ *nfr = *fr;
+ nfr->datacnt = acnt;
+ ngtcp2_vec_copy(nfr->data, a, acnt);
+
+ ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->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(&pktns->crypto.tx.frq);) {
+ it = ngtcp2_ksl_begin(&pktns->crypto.tx.frq);
+ nfrc = ngtcp2_ksl_it_get(&it);
+ nfr = &nfrc->fr.crypto;
+
+ if (nfr->offset != fr->offset + datalen) {
+ assert(fr->offset + datalen < nfr->offset);
+ break;
+ }
+
+ rv = conn_cryptofrq_unacked_pop(conn, pktns, &nfrc);
+ if (rv != 0) {
+ assert(ngtcp2_err_is_fatal(rv));
+ ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem);
+ return rv;
+ }
+ if (nfrc == NULL) {
+ break;
+ }
+
+ nfr = &nfrc->fr.crypto;
+
+ nmerged = ngtcp2_vec_merge(a, &acnt, nfr->data, &nfr->datacnt, left,
+ NGTCP2_MAX_CRYPTO_DATACNT);
+ if (nmerged == 0) {
+ rv = ngtcp2_ksl_insert(&pktns->crypto.tx.frq, NULL, &nfr->offset, nfrc);
+ if (rv != 0) {
+ assert(ngtcp2_err_is_fatal(rv));
+ ngtcp2_frame_chain_objalloc_del(nfrc, &conn->frc_objalloc, conn->mem);
+ ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem);
+ return rv;
+ }
+ break;
+ }
+
+ datalen += nmerged;
+ left -= nmerged;
+
+ if (nfr->datacnt == 0) {
+ ngtcp2_frame_chain_objalloc_del(nfrc, &conn->frc_objalloc, conn->mem);
+ continue;
+ }
+
+ nfr->offset += nmerged;
+
+ rv = ngtcp2_ksl_insert(&pktns->crypto.tx.frq, NULL, &nfr->offset, nfrc);
+ if (rv != 0) {
+ ngtcp2_frame_chain_objalloc_del(nfrc, &conn->frc_objalloc, conn->mem);
+ ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem);
+ return rv;
+ }
+
+ break;
+ }
+
+ if (acnt == fr->datacnt) {
+ assert(acnt > 0);
+ fr->data[acnt - 1] = a[acnt - 1];
+
+ *pfrc = frc;
+ return 0;
+ }
+
+ assert(acnt > fr->datacnt);
+
+ rv = ngtcp2_frame_chain_crypto_datacnt_objalloc_new(
+ &nfrc, acnt, &conn->frc_objalloc, conn->mem);
+ if (rv != 0) {
+ ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem);
+ return rv;
+ }
+
+ nfr = &nfrc->fr.crypto;
+ *nfr = *fr;
+ nfr->datacnt = acnt;
+ ngtcp2_vec_copy(nfr->data, a, acnt);
+
+ ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem);
+
+ *pfrc = nfrc;
+
+ 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 or 1RTT 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_ksl_len(&conn->hs_pktns->crypto.tx.frq) ||
+ !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_ksl_len(&conn->hs_pktns->crypto.tx.frq) ||
+ !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 && !conn->pktns.crypto.tx.ckm) ||
+ write_datalen == 0) {
+ return 1;
+ } else {
+ /* If we have something to send in 0RTT or 1RTT 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 {
+ assert(type == NGTCP2_PKT_HANDSHAKE);
+
+ if (!require_padding) {
+ return 0;
+ }
+
+ if (!conn->pktns.crypto.tx.ckm || write_datalen == 0) {
+ return 1;
+ }
+
+ min_payloadlen = ngtcp2_max(write_datalen, 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;
+}
+
+/*
+ * 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 conn_keep_alive_enabled(conn) &&
+ 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_keep_alive_enabled(conn)) {
+ 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) {
+ 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 or 1RTT 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.token.len) {
+ hd.token = conn->local.settings.token;
+ }
+
+ 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 = 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_ksl_len(&pktns->crypto.tx.frq);) {
+ left = ngtcp2_ppe_left(&ppe);
+
+ crypto_offset = conn_cryptofrq_unacked_offset(conn, pktns);
+ if (crypto_offset == (size_t)-1) {
+ conn_cryptofrq_clear(conn, pktns);
+ break;
+ }
+
+ left = ngtcp2_pkt_crypto_max_datalen(crypto_offset, left, left);
+ if (left == (size_t)-1) {
+ break;
+ }
+
+ rv = conn_cryptofrq_pop(conn, &nfrc, pktns, 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)) {
+ /* The intention of smaller limit is get more chance to measure
+ RTT samples in early phase. */
+ if (pktns->tx.num_non_ack_pkt >= 1) {
+ 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.num_non_ack_pkt = 0;
+ }
+ } else {
+ ++pktns->tx.num_non_ack_pkt;
+ }
+ } else {
+ pktns->tx.num_non_ack_pkt = 0;
+ }
+ }
+ }
+
+ if (pkt_empty) {
+ 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 {
+ 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 = 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_ksl_len(&conn->in_pktns->crypto.tx.frq))) {
+ 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_RCV,
+ "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);
+
+ if (len >= NGTCP2_MAX_SCID_POOL_SIZE) {
+ 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;
+
+ return (size_t)ngtcp2_min(NGTCP2_MAX_SCID_POOL_SIZE, n) - len;
+}
+
+/*
+ * 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;
+ }
+
+ 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 (scid->retired_ts == UINT64_MAX || 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");
+}
+
+/*
+ * 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. */
+ if (conn->oscid.datalen && conn_is_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;
+ }
+
+ 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 =
+ !conn->server || destlen >= NGTCP2_MAX_UDP_PAYLOAD_SIZE;
+ /* We don't retransmit PATH_RESPONSE. */
+ }
+ }
+ }
+
+ rv = 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)) {
+ frc = *pfrc;
+ *pfrc = (*pfrc)->next;
+ ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem);
+ continue;
+ }
+
+ if (!(strm->flags & NGTCP2_STRM_FLAG_STREAM_STOP_SENDING_CALLED)) {
+ strm->flags |= NGTCP2_STRM_FLAG_STREAM_STOP_SENDING_CALLED;
+
+ rv = conn_call_stream_stop_sending(
+ conn, (*pfrc)->fr.stop_sending.stream_id,
+ (*pfrc)->fr.stop_sending.app_error_code, strm->stream_user_data);
+ if (rv != 0) {
+ assert(ngtcp2_err_is_fatal(rv));
+ return rv;
+ }
+ }
+
+ 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) ||
+ (*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_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 (rv != NGTCP2_ERR_NOBUF) {
+ for (; ngtcp2_ksl_len(&pktns->crypto.tx.frq);) {
+ left = ngtcp2_ppe_left(ppe);
+
+ crypto_offset = conn_cryptofrq_unacked_offset(conn, pktns);
+ if (crypto_offset == (size_t)-1) {
+ conn_cryptofrq_clear(conn, pktns);
+ break;
+ }
+
+ left = ngtcp2_pkt_crypto_max_datalen(crypto_offset, left, left);
+
+ if (left == (size_t)-1) {
+ break;
+ }
+
+ rv = conn_cryptofrq_pop(conn, &nfrc, pktns, 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;
+ }
+ }
+
+ /* Write MAX_STREAM_ID after RESET_STREAM so that we can extend stream
+ ID space in one packet. */
+ if (rv != NGTCP2_ERR_NOBUF && *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 (rv != NGTCP2_ERR_NOBUF && *pfrc == NULL) {
+ if (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 (rv != NGTCP2_ERR_NOBUF) {
+ for (; !ngtcp2_pq_empty(&conn->tx.strmq);) {
+ strm = ngtcp2_conn_tx_strmq_top(conn);
+
+ if (!(strm->flags & NGTCP2_STRM_FLAG_SHUT_RD) &&
+ 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;
+ ngtcp2_list_insert(nfrc, pfrc);
+
+ 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;
+ strm->rx.max_offset = strm->rx.unsent_max_offset =
+ nfrc->fr.max_stream_data.max_stream_data;
+ }
+
+ 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);
+ assert(conn->tx.strmq_nretrans);
+ --conn->tx.strmq_nretrans;
+ 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);
+ assert(conn->tx.strmq_nretrans);
+ --conn->tx.strmq_nretrans;
+ 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;
+ }
+ }
+ }
+
+ if (rv != NGTCP2_ERR_NOBUF && !send_stream && !send_datagram &&
+ !(rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) &&
+ pktns->rtb.num_retransmittable && pktns->tx.frq == NULL &&
+ 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 (rv != NGTCP2_ERR_NOBUF && send_stream && *pfrc == NULL &&
+ (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 (rv != NGTCP2_ERR_NOBUF && 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_DATAGRAM;
+
+ if (vmsg->datagram.paccepted) {
+ *vmsg->datagram.paccepted = 1;
+ }
+ } else {
+ send_datagram = 0;
+ }
+
+ if (pkt_empty) {
+ assert(rv == 0 || NGTCP2_ERR_NOBUF == rv);
+ if (rv == 0 && stream_blocked && ngtcp2_conn_get_max_data_left(conn)) {
+ return NGTCP2_ERR_STREAM_DATA_BLOCKED;
+ }
+
+ keep_alive_expired = conn_keep_alive_expired(conn, ts);
+
+ if (conn->pktns.rtb.probe_pkt_left == 0 && !keep_alive_expired &&
+ !require_padding) {
+ 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 (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 (pktns->tx.num_non_ack_pkt >= NGTCP2_MAX_NON_ACK_TX_PKT ||
+ 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.num_non_ack_pkt = 0;
+ }
+ } else {
+ ++pktns->tx.num_non_ack_pkt;
+ }
+ } else {
+ pktns->tx.num_non_ack_pkt = 0;
+ }
+
+ /* 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 {
+ lfr.padding.len = ngtcp2_ppe_padding_size(ppe, min_pktlen);
+ }
+
+ 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->flags &= (uint32_t)~NGTCP2_CONN_FLAG_PPE_PENDING;
+
+ 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 && !(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 && !(rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE) &&
+ 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_handshake_completed(conn) ||
+ (in_pktns && (in_pktns->rtb.num_pto_eliciting ||
+ ngtcp2_ksl_len(&in_pktns->crypto.tx.frq))) ||
+ (hs_pktns && (hs_pktns->rtb.num_pto_eliciting ||
+ ngtcp2_ksl_len(&hs_pktns->crypto.tx.frq)));
+}
+
+/*
+ * 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_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_MTU_PROBE) {
+ return NGTCP2_ERR_NO_VIABLE_PATH;
+ }
+
+ 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;
+ 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;
+
+ timeout = conn_compute_pto(conn, &conn->pktns);
+ timeout = ngtcp2_max(timeout, 3 * conn->cstat.initial_rtt);
+ 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;
+}
+
+int ngtcp2_conn_resched_frames(ngtcp2_conn *conn, ngtcp2_pktns *pktns,
+ ngtcp2_frame_chain **pfrc) {
+ ngtcp2_frame_chain **first = pfrc;
+ ngtcp2_frame_chain *frc;
+ ngtcp2_stream *sfr;
+ ngtcp2_strm *strm;
+ int rv;
+ int streamfrq_empty;
+
+ if (*pfrc == NULL) {
+ return 0;
+ }
+
+ 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, &conn->frc_objalloc, conn->mem);
+ break;
+ }
+ streamfrq_empty = ngtcp2_strm_streamfrq_empty(strm);
+ rv = ngtcp2_strm_streamfrq_push(strm, frc);
+ if (rv != 0) {
+ ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem);
+ return rv;
+ }
+ 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;
+ }
+ }
+ if (streamfrq_empty) {
+ ++conn->tx.strmq_nretrans;
+ }
+ break;
+ case NGTCP2_FRAME_CRYPTO:
+ frc = *pfrc;
+
+ *pfrc = frc->next;
+ frc->next = NULL;
+
+ rv = ngtcp2_ksl_insert(&pktns->crypto.tx.frq, NULL,
+ &frc->fr.crypto.offset, frc);
+ if (rv != 0) {
+ assert(ngtcp2_err_is_fatal(rv));
+ ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem);
+ return rv;
+ }
+ break;
+ default:
+ pfrc = &(*pfrc)->next;
+ }
+ }
+
+ *pfrc = pktns->tx.frq;
+ pktns->tx.frq = *first;
+
+ return 0;
+}
+
+/*
+ * 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];
+ ngtcp2_vec *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.token.len == 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;
+ }
+
+ token = &conn->local.settings.token;
+
+ ngtcp2_mem_free(conn->mem, token->base);
+ token->base = NULL;
+ token->len = 0;
+
+ token->base = ngtcp2_mem_malloc(conn->mem, retry.token.len);
+ if (token->base == NULL) {
+ return NGTCP2_ERR_NOMEM;
+ }
+ token->len = retry.token.len;
+
+ ngtcp2_cpymem(token->base, retry.token.base, retry.token.len);
+
+ 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_frame_chain *frc = NULL;
+ 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);
+ 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) {
+ /* TODO assert this */
+ assert(ngtcp2_err_is_fatal((int)num_acked));
+ ngtcp2_frame_chain_list_objalloc_del(frc, &conn->frc_objalloc, conn->mem);
+ 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_crypto_level crypto_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, crypto_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_connection_close_error *ccerr = &conn->rx.ccerr;
+
+ conn->state = NGTCP2_CS_DRAINING;
+ if (fr->type == NGTCP2_FRAME_CONNECTION_CLOSE) {
+ ccerr->type = NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT;
+ } else {
+ ccerr->type = NGTCP2_CONNECTION_CLOSE_ERROR_CODE_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_CONNECTION_CLOSE_ERROR_MAX_REASONLEN);
+ if (ccerr->reason == NULL) {
+ return NGTCP2_ERR_NOMEM;
+ }
+ }
+
+ ccerr->reasonlen =
+ ngtcp2_min(fr->reasonlen, NGTCP2_CONNECTION_CLOSE_ERROR_MAX_REASONLEN);
+ ngtcp2_cpymem(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);
+
+ 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_duration pto, timeout;
+ 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->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE)) {
+ if (pv->dcid.seq != conn->dcid.current.seq) {
+ 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);
+ }
+
+ if (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 (!(pv->flags & NGTCP2_PV_ENTRY_FLAG_UNDERSIZED)) {
+ rv = conn_start_pmtud(conn);
+ if (rv != 0) {
+ return rv;
+ }
+ }
+ }
+ }
+
+ 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) {
+ pto = conn_compute_pto(conn, &conn->pktns);
+ timeout = 3 * ngtcp2_max(pto, pv->fallback_pto);
+
+ if (ent_flags & NGTCP2_PV_ENTRY_FLAG_UNDERSIZED) {
+ assert(conn->server);
+
+ /* Validate path again */
+ rv = ngtcp2_pv_new(&npv, &pv->dcid, timeout,
+ NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE |
+ NGTCP2_PV_FLAG_MTU_PROBE,
+ &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, timeout,
+ 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);
+}
+
+/*
+ * pkt_num_bits returns the number of bits available when packet
+ * number is encoded in |pkt_numlen| bytes.
+ */
+static size_t pkt_num_bits(size_t pkt_numlen) {
+ switch (pkt_numlen) {
+ case 1:
+ return 8;
+ case 2:
+ return 16;
+ case 3:
+ return 24;
+ case 4:
+ return 32;
+ default:
+ ngtcp2_unreachable();
+ }
+}
+
+/*
+ * 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;
+
+ if (ack_eliciting && pktns->rx.max_ack_eliciting_pkt_num + 1 != pkt_num) {
+ ngtcp2_acktr_immediate_ack(&pktns->acktr);
+ }
+ if (pktns->rx.max_pkt_num < pkt_num) {
+ pktns->rx.max_pkt_num = pkt_num;
+ pktns->rx.max_pkt_ts = ts;
+ }
+ if (ack_eliciting && pktns->rx.max_ack_eliciting_pkt_num < pkt_num) {
+ pktns->rx.max_ack_eliciting_pkt_num = pkt_num;
+ }
+
+ 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);
+ }
+
+ 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 ngtcp2_vec *token, const ngtcp2_pkt_hd *hd) {
+ if (token->len == hd->token.len &&
+ ngtcp2_cmemeq(token->base, hd->token.base, token->len)) {
+ 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_other_versions_includes returns nonzero if |other_versions| of
+ * length |other_versionslen| includes |version|. |other_versions| is
+ * the wire image of other_versions field of version_information
+ * transport parameter, and each version is encoded in network byte
+ * order.
+ */
+static int vneg_other_versions_includes(const uint8_t *other_versions,
+ size_t other_versionslen,
+ uint32_t version) {
+ size_t i;
+ uint32_t v;
+
+ assert(!(other_versionslen & 0x3));
+
+ if (other_versionslen == 0) {
+ return 0;
+ }
+
+ for (i = 0; i < other_versionslen; i += sizeof(uint32_t)) {
+ other_versions = ngtcp2_get_uint32(&v, other_versions);
+
+ if (version == v) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static int conn_recv_crypto(ngtcp2_conn *conn, ngtcp2_crypto_level crypto_level,
+ ngtcp2_strm *strm, const ngtcp2_crypto *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_crypto_level crypto_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;
+ }
+
+ if (hd.type == 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_CONN_ID_NEGOTIATED) {
+ 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;
+ } else if (hd.type == NGTCP2_PKT_RETRY) {
+ hdpktlen = (size_t)nread;
+
+ ngtcp2_log_rx_pkt_hd(&conn->log, &hd);
+
+ if (conn->server) {
+ return NGTCP2_ERR_DISCARD_PKT;
+ }
+
+ /* Receiving Retry packet after getting Initial packet from server
+ is invalid. */
+ if (conn->flags & NGTCP2_CONN_FLAG_CONN_ID_NEGOTIATED) {
+ 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;
+ }
+
+ /* 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_CONN_ID_NEGOTIATED) &&
+ !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_CONN_ID_NEGOTIATED) {
+ 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.token.len) {
+ rv = verify_token(&conn->local.settings.token, &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_CONN_ID_NEGOTIATED) == 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.token.len != 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_other_versions_includes(conn->vneg.other_versions,
+ conn->vneg.other_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;
+ crypto_level = NGTCP2_CRYPTO_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;
+ crypto_level = NGTCP2_CRYPTO_LEVEL_HANDSHAKE;
+ ckm = pktns->crypto.rx.ckm;
+ hp_ctx = &pktns->crypto.rx.hp_ctx;
+
+ break;
+ default:
+ /* unknown packet type */
+ ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT,
+ "packet was ignored because of unknown packet type");
+ return (ngtcp2_ssize)pktlen;
+ }
+
+ 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,
+ pkt_num_bits(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_CONN_ID_NEGOTIATED) &&
+ !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_CONN_ID_NEGOTIATED)) {
+ conn->flags |= NGTCP2_CONN_FLAG_CONN_ID_NEGOTIATED;
+ 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->crypto.data, fr->crypto.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, crypto_level, crypto, &fr->crypto);
+ 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);
+
+ /* TODO Initial and Handshake are always acknowledged without
+ delay. */
+ 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;
+}
+
+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_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_EARLY;
+ }
+
+ 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. |crypto_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_crypto_level crypto_level,
+ ngtcp2_strm *crypto, const ngtcp2_crypto *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) &&
+ crypto_level == NGTCP2_CRYPTO_LEVEL_INITIAL) {
+ /* recovery draft: 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;
+ rv = ngtcp2_strm_update_rx_offset(crypto, rx_offset);
+ if (rv != 0) {
+ return rv;
+ }
+
+ rv = conn_call_recv_crypto_data(conn, crypto_level, offset, data, datalen);
+ if (rv != 0) {
+ return rv;
+ }
+
+ rv = conn_emit_pending_crypto_data(conn, crypto_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;
+ }
+ /* TODO Perhaps, call new_stream callback? */
+ 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 (!bidi) {
+ ngtcp2_strm_shutdown(strm, NGTCP2_STRM_FLAG_SHUT_WR);
+ }
+ }
+
+ 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_RECV_RST) {
+ 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_RECV_RST) {
+ 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;
+ rv = ngtcp2_strm_update_rx_offset(strm, rx_offset);
+ if (rv != 0) {
+ return rv;
+ }
+ } 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_handshake_completed(conn)) {
+ sdflags |= NGTCP2_STREAM_DATA_FLAG_EARLY;
+ }
+ 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) {
+ int rv;
+ ngtcp2_frame_chain *frc;
+ ngtcp2_pktns *pktns = &conn->pktns;
+
+ rv = ngtcp2_frame_chain_objalloc_new(&frc, &conn->frc_objalloc);
+ if (rv != 0) {
+ return rv;
+ }
+
+ frc->fr.type = NGTCP2_FRAME_RESET_STREAM;
+ frc->fr.reset_stream.stream_id = strm->stream_id;
+ frc->fr.reset_stream.app_error_code = app_error_code;
+ frc->fr.reset_stream.final_size = strm->tx.offset;
+
+ /* TODO This prepends RESET_STREAM to pktns->tx.frq. */
+ frc->next = pktns->tx.frq;
+ pktns->tx.frq = frc;
+
+ return 0;
+}
+
+/*
+ * 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) {
+ int rv;
+ ngtcp2_frame_chain *frc;
+ ngtcp2_pktns *pktns = &conn->pktns;
+
+ rv = ngtcp2_frame_chain_objalloc_new(&frc, &conn->frc_objalloc);
+ if (rv != 0) {
+ return rv;
+ }
+
+ frc->fr.type = NGTCP2_FRAME_STOP_SENDING;
+ frc->fr.stop_sending.stream_id = strm->stream_id;
+ frc->fr.stop_sending.app_error_code = app_error_code;
+
+ /* TODO This prepends STOP_SENDING to pktns->tx.frq. */
+ frc->next = pktns->tx.frq;
+ pktns->tx.frq = frc;
+
+ return 0;
+}
+
+/*
+ * 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_RECV_RST) {
+ 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_RECV_RST;
+
+ 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;
+ }
+
+ /* Frame is received reset 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;
+ }
+ }
+
+ 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_SENT_RST)) {
+ 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_SENT_RST;
+
+ if (ngtcp2_strm_is_tx_queued(strm) && !ngtcp2_strm_streamfrq_empty(strm)) {
+ assert(conn->tx.strmq_nretrans);
+ --conn->tx.strmq_nretrans;
+ }
+
+ 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->token.len == 0) {
+ return NGTCP2_ERR_FRAME_ENCODING;
+ }
+
+ return conn_call_recv_new_token(conn, &fr->token);
+}
+
+/*
+ * 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_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_duration pto, initial_pto, timeout;
+ 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);
+
+ pto = conn_compute_pto(conn, &conn->pktns);
+ initial_pto = conn_compute_initial_pto(conn, &conn->pktns);
+ timeout = 3 * ngtcp2_max(pto, initial_pto);
+
+ rv = ngtcp2_pv_new(&pv, dcid, timeout, 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_address_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);
+}
+
+/*
+ * 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 &&
+ ngtcp2_conn_initiate_key_update(conn, ts) != 0) {
+ return NGTCP2_ERR_AEAD_LIMIT_REACHED;
+ }
+
+ if ((conn->flags & NGTCP2_CONN_FLAG_KEY_UPDATE_NOT_CONFIRMED) ||
+ (confirmed_ts != UINT64_MAX && 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, initial_pto, timeout;
+ 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");
+
+ pto = conn_compute_pto(conn, &conn->pktns);
+ initial_pto = conn_compute_initial_pto(conn, &conn->pktns);
+ timeout = 3 * ngtcp2_max(pto, initial_pto);
+
+ 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;
+
+ rv = ngtcp2_pv_new(&pv, &dcid, timeout, 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);
+ } else {
+ /* For NAT rebinding, keep max_udp_payload_size since client most
+ likely does not send a padded PATH_CHALLENGE. */
+ dcid.max_udp_payload_size = ngtcp2_max(
+ dcid.max_udp_payload_size, conn->dcid.current.max_udp_payload_size);
+ }
+
+ 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);
+
+ 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 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_address_present) {
+ return 0;
+ }
+
+ paddr = &conn->local.transport_params.preferred_address;
+
+ 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;
+ }
+
+ 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;
+ }
+
+ 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,
+ pkt_num_bits(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_CRYPTO_LEVEL_APPLICATION,
+ &pktns->crypto.strm, &fr->crypto);
+ 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_DATA_BLOCKED:
+ /* TODO Not implemented yet */
+ 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_HANDLED;
+
+ 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_handshake_completed(conn) &&
+ !(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_COMPLETED_HANDLED)) {
+ 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 ServerHello 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.token.len) {
+ return nread;
+ }
+ return NGTCP2_ERR_RETRY;
+ }
+ if (conn->in_pktns->rx.buffed_pkts) {
+ /* 0RTT is buffered, force retry */
+ return NGTCP2_ERR_RETRY;
+ }
+ /* If neither CRYPTO frame nor 0RTT packet is 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_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->log.last_ts = ts;
+ conn->qlog.last_ts = 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:
+ case NGTCP2_CS_CLIENT_TLS_HANDSHAKE_FAILED:
+ 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:
+ case NGTCP2_CS_SERVER_TLS_HANDSHAKE_FAILED:
+ 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 or 1RTT 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_HANDLED)) {
+ 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_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_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_address_present) {
+ assert(!ngtcp2_ringbuf_full(&conn->dcid.unused.rb));
+
+ paddr = &conn->remote.transport_params->preferred_address;
+ 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 &&
+ /* 0 length STREAM frame is allowed */
+ (datalen == 0 ||
+ (datalen > 0 &&
+ (vmsg->stream.strm->tx.max_offset - vmsg->stream.strm->tx.offset) &&
+ (conn->tx.max_offset - conn->tx.offset)));
+ if (send_stream) {
+ write_datalen =
+ conn_enforce_flow_control(conn, vmsg->stream.strm, datalen);
+ write_datalen =
+ ngtcp2_min(write_datalen, NGTCP2_MIN_COALESCED_PAYLOADLEN);
+ write_datalen += NGTCP2_STREAM_OVERHEAD;
+
+ 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 {
+ conn->pkt.require_padding = 0;
+ }
+ } 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:
+ return spktlen;
+ case NGTCP2_ERR_WRITE_MORE:
+ conn->pkt.hs_spktlen = spktlen;
+ break;
+ }
+ return early_spktlen;
+ }
+
+ return spktlen + early_spktlen;
+}
+
+void ngtcp2_conn_handshake_completed(ngtcp2_conn *conn) {
+ conn->flags |= NGTCP2_CONN_FLAG_HANDSHAKE_COMPLETED;
+ if (conn->server) {
+ conn->flags |= NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED;
+ }
+}
+
+int ngtcp2_conn_get_handshake_completed(ngtcp2_conn *conn) {
+ return conn_is_handshake_completed(conn) &&
+ (conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_COMPLETED_HANDLED);
+}
+
+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. */
+ return NGTCP2_ERR_RETRY;
+ default:
+ return NGTCP2_ERR_INVALID_ARGUMENT;
+ }
+
+ if (pktlen < NGTCP2_MAX_UDP_PAYLOAD_SIZE ||
+ (p->token.len == 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_CRYPTO_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_CRYPTO_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_early_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_CRYPTO_LEVEL_EARLY);
+ } else {
+ rv = conn_call_recv_tx_key(conn, NGTCP2_CRYPTO_LEVEL_EARLY);
+ }
+ 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_CRYPTO_LEVEL_APPLICATION);
+ 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_CRYPTO_LEVEL_APPLICATION);
+ 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_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 ||
+ (confirmed_ts != UINT64_MAX && confirmed_ts + 3 * pto > ts)) {
+ return NGTCP2_ERR_INVALID_STATE;
+ }
+
+ conn_rotate_keys(conn, NGTCP2_MAX_PKT_NUM, /* initiator = */ 1);
+
+ return 0;
+}
+
+/*
+ * 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 (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_handshake_completed(conn) ||
+ conn->local.settings.handshake_timeout == UINT64_MAX) {
+ 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_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 &&
+ conn->early.discard_started_ts != UINT64_MAX) {
+ if (conn->early.discard_started_ts + 3 * pto <= ts) {
+ conn_discard_early_key(conn);
+ }
+ }
+
+ if (!conn_is_handshake_completed(conn) &&
+ conn->local.settings.handshake_timeout != UINT64_MAX &&
+ 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) &&
+ acktr->first_unacked_ts != UINT64_MAX &&
+ 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
+ * |other_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 *other_versions,
+ size_t other_versionslen,
+ uint32_t fallback_version) {
+ size_t i, j;
+ const uint8_t *p;
+ uint32_t v;
+
+ if (!preferred_versionslen ||
+ (!other_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 = other_versions; j < other_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 (!ngtcp2_cid_eq(&conn->rcid, &params->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, &params->retry_scid)) {
+ return NGTCP2_ERR_TRANSPORT_PARAM;
+ }
+ } else if (params->retry_scid_present) {
+ return NGTCP2_ERR_TRANSPORT_PARAM;
+ }
+
+ if (params->preferred_address_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_other_versions_includes(conn->vneg.other_versions,
+ conn->vneg.other_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 (and the supported draft versions) are treated
+ specially. If version_info is missing, no further validation
+ is necessary.
+ https://datatracker.ietf.org/doc/html/draft-ietf-quic-version-negotiation-10#section-8
+ */
+ if (conn->client_chosen_version == NGTCP2_PROTO_VER_V1 ||
+ (NGTCP2_PROTO_VER_DRAFT_MIN <= conn->client_chosen_version &&
+ conn->client_chosen_version <= NGTCP2_PROTO_VER_DRAFT_MAX)) {
+ return 0;
+ }
+
+ return NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE;
+ }
+
+ /* Server choose original version after Version Negotiation.
+ Draft 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.other_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.other_versions,
+ params->version_info.other_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->other_versions,
+ version_info->other_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;
+ }
+
+ /* Assume that ngtcp2_decode_transport_params 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, &params->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->version_info_present) {
+ 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, &params->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,
+ conn->server
+ ? NGTCP2_TRANSPORT_PARAMS_TYPE_CLIENT_HELLO
+ : NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS,
+ 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_remote_transport_params(ngtcp2_conn *conn,
+ const uint8_t *data,
+ size_t datalen) {
+ ngtcp2_transport_params params;
+ int rv;
+
+ rv = ngtcp2_decode_transport_params(
+ &params,
+ conn->server ? NGTCP2_TRANSPORT_PARAMS_TYPE_CLIENT_HELLO
+ : NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS,
+ data, datalen);
+ if (rv != 0) {
+ return rv;
+ }
+
+ return ngtcp2_conn_set_remote_transport_params(conn, &params);
+}
+
+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;
+}
+
+void ngtcp2_conn_set_early_remote_transport_params_versioned(
+ ngtcp2_conn *conn, int transport_params_version,
+ const ngtcp2_transport_params *params) {
+ ngtcp2_transport_params *p;
+ (void)transport_params_version;
+
+ 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));
+
+ conn->remote.transport_params = p;
+
+ 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;
+ p->active_connection_id_limit =
+ ngtcp2_max(NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT,
+ params->active_connection_id_limit);
+ p->max_idle_timeout = params->max_idle_timeout;
+ if (!params->max_udp_payload_size) {
+ p->max_udp_payload_size = NGTCP2_DEFAULT_MAX_RECV_UDP_PAYLOAD_SIZE;
+ } else {
+ p->max_udp_payload_size =
+ ngtcp2_max(NGTCP2_MAX_UDP_PAYLOAD_SIZE, params->max_udp_payload_size);
+ }
+ p->disable_active_migration = params->disable_active_migration;
+ p->max_datagram_frame_size = params->max_datagram_frame_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);
+}
+
+int ngtcp2_conn_set_local_transport_params_versioned(
+ ngtcp2_conn *conn, int transport_params_version,
+ const ngtcp2_transport_params *params) {
+ (void)transport_params_version;
+
+ assert(conn->server);
+ 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));
+
+ if (params->active_connection_id_limit == 0) {
+ params->active_connection_id_limit =
+ NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT;
+ }
+
+ params->initial_scid = conn->oscid;
+
+ if (conn->oscid.datalen == 0) {
+ params->preferred_address_present = 0;
+ }
+
+ if (conn->server && params->preferred_address_present) {
+ scident = ngtcp2_mem_malloc(mem, sizeof(*scident));
+ if (scident == NULL) {
+ return NGTCP2_ERR_NOMEM;
+ }
+
+ ngtcp2_scid_init(scident, 1, &params->preferred_address.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_encode_transport_params(
+ dest, destlen,
+ conn->server ? NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS
+ : NGTCP2_TRANSPORT_PARAMS_TYPE_CLIENT_HELLO,
+ &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;
+ int undersized;
+
+ 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 (vmsg == NULL && cstat->bytes_in_flight < cstat->cwnd &&
+ conn->tx.strmq_nretrans == 0) {
+ if (conn->local.settings.no_tx_udp_payload_size_shaping) {
+ undersized =
+ (size_t)nwrite < conn->local.settings.max_tx_udp_payload_size;
+ } else {
+ undersized = (size_t)nwrite < conn->dcid.current.max_udp_payload_size;
+ }
+
+ if (undersized) {
+ 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_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_STATE;
+ }
+
+ 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;
+ uint64_t datalen;
+ uint64_t write_datalen = 0;
+ int64_t prev_in_pkt_num = -1;
+ ngtcp2_ksl_it it;
+ ngtcp2_rtb_entry *rtbent;
+ (void)pkt_info_version;
+
+ conn->log.last_ts = ts;
+ conn->qlog.last_ts = 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:
+ case NGTCP2_CS_CLIENT_TLS_HANDSHAKE_FAILED:
+ 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:
+ case NGTCP2_CS_SERVER_TLS_HANDSHAKE_FAILED:
+ 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_RCV,
+ "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 (vmsg) {
+ switch (vmsg->type) {
+ case NGTCP2_VMSG_TYPE_STREAM:
+ datalen = ngtcp2_vec_len(vmsg->stream.data, vmsg->stream.datacnt);
+ if (datalen == 0 || (datalen > 0 &&
+ (vmsg->stream.strm->tx.max_offset -
+ vmsg->stream.strm->tx.offset) &&
+ (conn->tx.max_offset - conn->tx.offset))) {
+ write_datalen =
+ conn_enforce_flow_control(conn, vmsg->stream.strm, datalen);
+ write_datalen =
+ ngtcp2_min(write_datalen, NGTCP2_MIN_COALESCED_PAYLOADLEN);
+ write_datalen += NGTCP2_STREAM_OVERHEAD;
+ }
+ break;
+ case NGTCP2_VMSG_TYPE_DATAGRAM:
+ write_datalen =
+ ngtcp2_vec_len(vmsg->datagram.data, vmsg->datagram.datacnt) +
+ NGTCP2_DATAGRAM_OVERHEAD;
+ break;
+ default:
+ ngtcp2_unreachable();
+ }
+
+ if (conn->in_pktns && write_datalen > 0) {
+ 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, ts);
+ if (nwrite < 0) {
+ return nwrite;
+ }
+
+ res = nwrite;
+ dest += nwrite;
+ destlen -= (size_t)nwrite;
+
+ if (conn->in_pktns && write_datalen > 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->state != NGTCP2_CS_POST_HANDSHAKE &&
+ 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;
+ conn->pkt.hs_spktlen = 0;
+ 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_ksl_len(&conn->hs_pktns->crypto.tx.frq) == 0)) {
+ 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_RCV,
+ "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)) {
+ 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;
+ }
+ }
+ }
+
+ 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:
+ conn->pkt.hs_spktlen = 0;
+
+ if (nwrite >= 0) {
+ res += nwrite;
+ return res;
+ }
+ /* NGTCP2_CONN_FLAG_PPE_PENDING is set in conn_write_pkt above.
+ ppe_pending cannot be used here. */
+ if (conn->flags & NGTCP2_CONN_FLAG_PPE_PENDING) {
+ conn->pkt.hs_spktlen = res;
+ }
+
+ 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;
+
+ conn->log.last_ts = ts;
+ conn->qlog.last_ts = ts;
+
+ 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;
+
+ conn->log.last_ts = ts;
+ conn->qlog.last_ts = ts;
+
+ 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
+connection_close_error_init(ngtcp2_connection_close_error *ccerr,
+ ngtcp2_connection_close_error_code_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_connection_close_error_default(
+ ngtcp2_connection_close_error *ccerr) {
+ connection_close_error_init(ccerr,
+ NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT,
+ NGTCP2_NO_ERROR, NULL, 0);
+}
+
+void ngtcp2_connection_close_error_set_transport_error(
+ ngtcp2_connection_close_error *ccerr, uint64_t error_code,
+ const uint8_t *reason, size_t reasonlen) {
+ connection_close_error_init(ccerr,
+ NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT,
+ error_code, reason, reasonlen);
+}
+
+void ngtcp2_connection_close_error_set_transport_error_liberr(
+ ngtcp2_connection_close_error *ccerr, int liberr, const uint8_t *reason,
+ size_t reasonlen) {
+ switch (liberr) {
+ case NGTCP2_ERR_RECV_VERSION_NEGOTIATION:
+ connection_close_error_init(
+ ccerr,
+ NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT_VERSION_NEGOTIATION,
+ NGTCP2_NO_ERROR, reason, reasonlen);
+
+ return;
+ case NGTCP2_ERR_IDLE_CLOSE:
+ connection_close_error_init(
+ ccerr, NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT_IDLE_CLOSE,
+ NGTCP2_NO_ERROR, reason, reasonlen);
+
+ return;
+ };
+
+ ngtcp2_connection_close_error_set_transport_error(
+ ccerr, ngtcp2_err_infer_quic_transport_error_code(liberr), reason,
+ reasonlen);
+}
+
+void ngtcp2_connection_close_error_set_transport_error_tls_alert(
+ ngtcp2_connection_close_error *ccerr, uint8_t tls_alert,
+ const uint8_t *reason, size_t reasonlen) {
+ ngtcp2_connection_close_error_set_transport_error(
+ ccerr, NGTCP2_CRYPTO_ERROR | tls_alert, reason, reasonlen);
+}
+
+void ngtcp2_connection_close_error_set_application_error(
+ ngtcp2_connection_close_error *ccerr, uint64_t error_code,
+ const uint8_t *reason, size_t reasonlen) {
+ connection_close_error_init(
+ ccerr, NGTCP2_CONNECTION_CLOSE_ERROR_CODE_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_connection_close_error *ccerr, ngtcp2_tstamp ts) {
+ (void)pkt_info_version;
+
+ switch (ccerr->type) {
+ case NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT:
+ return ngtcp2_conn_write_connection_close_pkt(
+ conn, path, pi, dest, destlen, ccerr->error_code, ccerr->reason,
+ ccerr->reasonlen, ts);
+ case NGTCP2_CONNECTION_CLOSE_ERROR_CODE_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_is_in_closing_period(ngtcp2_conn *conn) {
+ return conn->state == NGTCP2_CS_CLOSING;
+}
+
+int ngtcp2_conn_is_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);
+ if (!ngtcp2_strm_streamfrq_empty(strm)) {
+ assert(conn->tx.strmq_nretrans);
+ --conn->tx.strmq_nretrans;
+ }
+ }
+
+ 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_RECV_RST) ||
+ ngtcp2_strm_rx_offset(strm) == strm->rx.last_offset) &&
+ (((strm->flags & NGTCP2_STRM_FLAG_SENT_RST) &&
+ (strm->flags & NGTCP2_STRM_FLAG_RST_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_SENT_RST) ||
+ 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_SENT_RST;
+
+ 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) {
+ 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_RECV_RST))) {
+ 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, int64_t stream_id,
+ uint64_t app_error_code) {
+ int rv;
+ ngtcp2_strm *strm;
+
+ 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, int64_t stream_id,
+ uint64_t app_error_code) {
+ 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_shutdown_stream_write(conn, strm, app_error_code);
+}
+
+int ngtcp2_conn_shutdown_stream_read(ngtcp2_conn *conn, int64_t stream_id,
+ uint64_t app_error_code) {
+ 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_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;
+
+ 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);
+ if (!ngtcp2_strm_streamfrq_empty(s)) {
+ assert(conn->tx.strmq_nretrans);
+ --conn->tx.strmq_nretrans;
+ }
+ }
+
+ 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->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_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.early_data_rejected) {
+ return conn->callbacks.early_data_rejected(conn, conn->user_data);
+ }
+
+ return 0;
+}
+
+int ngtcp2_conn_get_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_RCV,
+ "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_RCV,
+ "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_stat_versioned(ngtcp2_conn *conn,
+ int conn_stat_version,
+ ngtcp2_conn_stat *cstat) {
+ (void)conn_stat_version;
+
+ *cstat = conn->cstat;
+}
+
+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_RCV,
+ "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_RCV,
+ "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_RCV,
+ "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;
+
+ conn->log.last_ts = ts;
+ conn->qlog.last_ts = ts;
+
+ 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_RCV,
+ "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_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 = 1;
+ } else {
+ conn->pktns.rtb.probe_pkt_left = 2;
+ }
+ }
+
+ ++cstat->pto_count;
+
+ ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_RCV, "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_crypto_level crypto_level,
+ const uint8_t *data, const size_t datalen) {
+ ngtcp2_pktns *pktns;
+ ngtcp2_frame_chain *frc;
+ ngtcp2_crypto *fr;
+ int rv;
+
+ if (datalen == 0) {
+ return 0;
+ }
+
+ switch (crypto_level) {
+ case NGTCP2_CRYPTO_LEVEL_INITIAL:
+ assert(conn->in_pktns);
+ pktns = conn->in_pktns;
+ break;
+ case NGTCP2_CRYPTO_LEVEL_HANDSHAKE:
+ assert(conn->hs_pktns);
+ pktns = conn->hs_pktns;
+ break;
+ case NGTCP2_CRYPTO_LEVEL_APPLICATION:
+ 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.crypto;
+
+ fr->type = NGTCP2_FRAME_CRYPTO;
+ fr->offset = pktns->crypto.tx.offset;
+ fr->datacnt = 1;
+ fr->data[0].len = datalen;
+ fr->data[0].base = (uint8_t *)data;
+
+ rv = ngtcp2_ksl_insert(&pktns->crypto.tx.frq, NULL, &fr->offset, 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;
+ ngtcp2_vec tokenv = {(uint8_t *)token, tokenlen};
+
+ assert(conn->server);
+ assert(token);
+ assert(tokenlen);
+
+ rv = ngtcp2_frame_chain_new_token_objalloc_new(
+ &nfrc, &tokenv, &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_uncommited_preferred_address_cid(ngtcp2_conn *conn) {
+ return conn->server &&
+ !(conn->flags & NGTCP2_CONN_FLAG_LOCAL_TRANSPORT_PARAMS_COMMITTED) &&
+ conn->oscid.datalen &&
+ conn->local.transport_params.preferred_address_present;
+}
+
+size_t ngtcp2_conn_get_num_scid(ngtcp2_conn *conn) {
+ return ngtcp2_ksl_len(&conn->scid.set) +
+ (size_t)conn_has_uncommited_preferred_address_cid(conn);
+}
+
+size_t ngtcp2_conn_get_scid(ngtcp2_conn *conn, ngtcp2_cid *dest) {
+ ngtcp2_cid *origdest = dest;
+ ngtcp2_ksl_it it;
+ ngtcp2_scid *scid;
+
+ 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_uncommited_preferred_address_cid(conn)) {
+ *dest++ = conn->local.transport_params.preferred_address.cid;
+ }
+
+ return (size_t)(dest - origdest);
+}
+
+size_t ngtcp2_conn_get_num_active_dcid(ngtcp2_conn *conn) {
+ size_t n = 1; /* for conn->dcid.current */
+ ngtcp2_pv *pv = conn->pv;
+
+ if (!(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_COMPLETED_HANDLED)) {
+ return 0;
+ }
+
+ 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_HANDLED)) {
+ return 0;
+ }
+
+ 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_duration pto, initial_pto, timeout;
+ ngtcp2_pv *pv;
+
+ assert(!conn->server);
+
+ conn->log.last_ts = ts;
+ conn->qlog.last_ts = 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);
+
+ pto = conn_compute_pto(conn, &conn->pktns);
+ initial_pto = conn_compute_initial_pto(conn, &conn->pktns);
+ timeout = 3 * ngtcp2_max(pto, initial_pto);
+
+ /* 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, timeout, 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_duration pto, initial_pto, timeout;
+ ngtcp2_pv *pv;
+
+ assert(!conn->server);
+
+ conn->log.last_ts = ts;
+ conn->qlog.last_ts = 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);
+
+ pto = conn_compute_pto(conn, &conn->pktns);
+ initial_pto = conn_compute_initial_pto(conn, &conn->pktns);
+ timeout = 3 * ngtcp2_max(pto, initial_pto);
+
+ rv = ngtcp2_pv_new(&pv, dcid, timeout, 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_local_streams_uni(ngtcp2_conn *conn) {
+ return conn->local.uni.max_streams;
+}
+
+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_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_handshake_completed(conn)
+ ? &conn->pktns
+ : conn->hs_pktns);
+
+ return conn->idle_ts + ngtcp2_max(idle_timeout, trpto);
+}
+
+ngtcp2_duration ngtcp2_conn_get_pto(ngtcp2_conn *conn) {
+ return conn_compute_pto(
+ conn, conn_is_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_early_crypto_ctx(ngtcp2_conn *conn,
+ const ngtcp2_crypto_ctx *ctx) {
+ conn->early.ctx = *ctx;
+}
+
+const ngtcp2_crypto_ctx *ngtcp2_conn_get_early_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;
+}
+
+void ngtcp2_conn_get_connection_close_error(
+ ngtcp2_conn *conn, ngtcp2_connection_close_error *ccerr) {
+ *ccerr = 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) {
+ double pacing_rate;
+ ngtcp2_duration interval;
+
+ if (conn->tx.pacing.pktlen == 0) {
+ return;
+ }
+
+ if (conn->cstat.pacing_rate > 0) {
+ pacing_rate = conn->cstat.pacing_rate;
+ } else {
+ /* 1.25 is the under-utilization avoidance factor described in
+ https://datatracker.ietf.org/doc/html/rfc9002#section-7.7 */
+ pacing_rate =
+ (double)conn->cstat.cwnd / (double)conn->cstat.smoothed_rtt * 1.25;
+ }
+
+ interval = (ngtcp2_duration)((double)conn->tx.pacing.pktlen / pacing_rate);
+
+ conn->tx.pacing.next_ts = ts + interval;
+ 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 = NGTCP2_DEFAULT_HANDSHAKE_TIMEOUT;
+}
+
+void ngtcp2_transport_params_default_versioned(
+ int transport_params_version, ngtcp2_transport_params *params) {
+ (void)transport_params_version;
+
+ memset(params, 0, sizeof(*params));
+ params->max_udp_payload_size = NGTCP2_DEFAULT_MAX_RECV_UDP_PAYLOAD_SIZE;
+ params->ack_delay_exponent = NGTCP2_DEFAULT_ACK_DELAY_EXPONENT;
+ params->max_ack_delay = NGTCP2_DEFAULT_MAX_ACK_DELAY;
+ params->active_connection_id_limit =
+ NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT;
+}
+
+/* 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;
+}