{"id":2215691,"url":"http://patchwork.ozlabs.org/api/patches/2215691/?format=json","web_url":"http://patchwork.ozlabs.org/project/linux-cifs-client/patch/91c6fd0a44eb15a98f945171ead8062badb89a60.1774410440.git.lucien.xin@gmail.com/","project":{"id":12,"url":"http://patchwork.ozlabs.org/api/projects/12/?format=json","name":"Linux CIFS Client","link_name":"linux-cifs-client","list_id":"linux-cifs.vger.kernel.org","list_email":"linux-cifs@vger.kernel.org","web_url":"","scm_url":"","webscm_url":"","list_archive_url":"","list_archive_url_format":"","commit_url_format":""},"msgid":"<91c6fd0a44eb15a98f945171ead8062badb89a60.1774410440.git.lucien.xin@gmail.com>","list_archive_url":null,"date":"2026-03-25T03:47:11","name":"[net-next,v11,06/15] quic: add stream management","commit_ref":null,"pull_url":null,"state":"new","archived":false,"hash":"62377f3fff0520c41b2fd02b29e82d543b0df383","submitter":{"id":61073,"url":"http://patchwork.ozlabs.org/api/people/61073/?format=json","name":"Xin Long","email":"lucien.xin@gmail.com"},"delegate":null,"mbox":"http://patchwork.ozlabs.org/project/linux-cifs-client/patch/91c6fd0a44eb15a98f945171ead8062badb89a60.1774410440.git.lucien.xin@gmail.com/mbox/","series":[{"id":497380,"url":"http://patchwork.ozlabs.org/api/series/497380/?format=json","web_url":"http://patchwork.ozlabs.org/project/linux-cifs-client/list/?series=497380","date":"2026-03-25T03:47:06","name":"net: introduce QUIC infrastructure and core subcomponents","version":11,"mbox":"http://patchwork.ozlabs.org/series/497380/mbox/"}],"comments":"http://patchwork.ozlabs.org/api/patches/2215691/comments/","check":"pending","checks":"http://patchwork.ozlabs.org/api/patches/2215691/checks/","tags":{},"related":[],"headers":{"Return-Path":"\n <linux-cifs+bounces-10508-incoming=patchwork.ozlabs.org@vger.kernel.org>","X-Original-To":["incoming@patchwork.ozlabs.org","linux-cifs@vger.kernel.org"],"Delivered-To":"patchwork-incoming@legolas.ozlabs.org","Authentication-Results":["legolas.ozlabs.org;\n\tdkim=pass (2048-bit key;\n unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256\n header.s=20251104 header.b=ilMwRZO3;\n\tdkim-atps=neutral","legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=vger.kernel.org\n (client-ip=172.105.105.114; helo=tor.lore.kernel.org;\n envelope-from=linux-cifs+bounces-10508-incoming=patchwork.ozlabs.org@vger.kernel.org;\n receiver=patchwork.ozlabs.org)","smtp.subspace.kernel.org;\n\tdkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com\n header.b=\"ilMwRZO3\"","smtp.subspace.kernel.org;\n arc=none smtp.client-ip=209.85.160.181","smtp.subspace.kernel.org;\n dmarc=pass (p=none dis=none) header.from=gmail.com","smtp.subspace.kernel.org;\n spf=pass smtp.mailfrom=gmail.com"],"Received":["from tor.lore.kernel.org (tor.lore.kernel.org [172.105.105.114])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange x25519)\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4fgXzf2KtMz1y1G\n\tfor <incoming@patchwork.ozlabs.org>; Wed, 25 Mar 2026 14:52:30 +1100 (AEDT)","from smtp.subspace.kernel.org (conduit.subspace.kernel.org\n [100.90.174.1])\n\tby tor.lore.kernel.org (Postfix) with ESMTP id 3908E30BC20C\n\tfor <incoming@patchwork.ozlabs.org>; Wed, 25 Mar 2026 03:49:45 +0000 (UTC)","from localhost.localdomain (localhost.localdomain [127.0.0.1])\n\tby smtp.subspace.kernel.org (Postfix) with ESMTP id 46F1B286D70;\n\tWed, 25 Mar 2026 03:49:41 +0000 (UTC)","from mail-qt1-f181.google.com (mail-qt1-f181.google.com\n [209.85.160.181])\n\t(using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits))\n\t(No client certificate requested)\n\tby smtp.subspace.kernel.org (Postfix) with ESMTPS id 2871B2E7185\n\tfor <linux-cifs@vger.kernel.org>; Wed, 25 Mar 2026 03:49:38 +0000 (UTC)","by mail-qt1-f181.google.com with SMTP id\n d75a77b69052e-506bcb23a78so46741891cf.3\n        for <linux-cifs@vger.kernel.org>;\n Tue, 24 Mar 2026 20:49:38 -0700 (PDT)","from wsfd-netdev58.anl.eng.rdu2.dc.redhat.com ([66.187.232.140])\n        by smtp.gmail.com with ESMTPSA id\n d75a77b69052e-50b36cb2e29sm150093001cf.1.2026.03.24.20.49.35\n        (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n        Tue, 24 Mar 2026 20:49:37 -0700 (PDT)"],"ARC-Seal":"i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116;\n\tt=1774410581; cv=none;\n b=WQT3SeA/ad90NjgGs3fX9PMZTvnv84KenklkwQH6t27GdOjs/aWtUIMfUJh/Ib7H1iAm6fG4KWjXxU88EmUbXjiU79vOFTN8Kzt0CLfm4TyQl2zcEkzBjEvcEQ994FwjrWYCw+e4fVFY14dIKnMd/ahMQH6JHi9cdP0JL6OpFnI=","ARC-Message-Signature":"i=1; a=rsa-sha256; d=subspace.kernel.org;\n\ts=arc-20240116; t=1774410581; c=relaxed/simple;\n\tbh=/hCh8Zx3/BY8jF+1KwPgEwsvzaT5gDv5mdxJ/lR9gsI=;\n\th=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References:\n\t MIME-Version:Content-Type;\n b=LKXs39vdS36rF4PsbDa583E2eq3QU1YHqS8YPT3NFH8LYjpQo/BgwjQXPbcgsQvJ1xjoLHAavM1xqQi12iKxpK8kwEqtn+SJ5V+yfwgTr2T/UDWRTYZvlaYO6jqDpDtLptAGRtTWlG8u+1112JUtqonGbbu0g2FADWBMeK6fLno=","ARC-Authentication-Results":"i=1; smtp.subspace.kernel.org;\n dmarc=pass (p=none dis=none) header.from=gmail.com;\n spf=pass smtp.mailfrom=gmail.com;\n dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com\n header.b=ilMwRZO3; arc=none smtp.client-ip=209.85.160.181","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n        d=gmail.com; s=20251104; t=1774410578; x=1775015378;\n darn=vger.kernel.org;\n        h=content-transfer-encoding:mime-version:references:in-reply-to\n         :message-id:date:subject:cc:to:from:from:to:cc:subject:date\n         :message-id:reply-to;\n        bh=8FJEIVV5+/NFJkyakwv2PtRYAzHhtS8a7PnuVSByDsY=;\n        b=ilMwRZO3hFoLimIxliqZcRIPGOuqczWrxERFVbjOJi5CbimP1cn6v6atJ45rXCmDJO\n         VXxqfYzSk4TKCwxzwTxQWgkQkwc//j04Z5MzvElOZpgmDqob5VrCxhK/WNbFC1n/rCrf\n         xROZg7EXcSG2Rhq10nGljKtrS40N62HlIemLHDKraHMGErPCMFqItccbnPSVOxmyQTq8\n         YtTkJ9Zi69jkVqsdk0iAZOyFtS965d+C9BIdwJ6VMT+JCqH0oS3jNXhoN32irXyt1fa0\n         ttaGyejE8b4osmfZH3Ep1J2DowdiW2mN2QY9te0ruc/G3I0FuC+5U9iIpU74YmOf2Cmb\n         ia6w==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n        d=1e100.net; s=20251104; t=1774410578; x=1775015378;\n        h=content-transfer-encoding:mime-version:references:in-reply-to\n         :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from\n         :to:cc:subject:date:message-id:reply-to;\n        bh=8FJEIVV5+/NFJkyakwv2PtRYAzHhtS8a7PnuVSByDsY=;\n        b=Mj3htpZ+KY0uwuRHmyqUX/lGNzH9OGoCn4Z8F2/yPqK72XfdD+KNBJjiAGSoaopoQP\n         NPXIADNNSe0SdTFLAfteJJYLkpNoqPGmfqgDvVxIYX3DLQwRySdt8LzMGO+Dv5adrTEY\n         3IR0uF4BAcJseEe/NKQ9fnY+5zaFMk2Ks2REIxjOMhjpGalBVsW4aIEKLarGckKpfCgM\n         51TbE/2QwgT5zYsG5wph5C0z0Uih6VX8cZws9wFI06BHGzIQYIddw7zuUhdt06kATEy8\n         pHZHNKehJTQoNbaR2PC+W8eWCXa/AyjAYVk2BGA5p3m2pt2YWQiMGdb/ZW43fDDrCB+V\n         yBLA==","X-Forwarded-Encrypted":"i=1;\n AJvYcCVPaa+8gVNm6SYrWeJy51/O/1LM74+0Kcuj79JwrGbkOx4J9WjBCLnDkex/sgn3ELHQC1yZ7mF8mTbG@vger.kernel.org","X-Gm-Message-State":"AOJu0YyQsOG/yBgx6VCB25NL8MRja7VCEjwJzthvRIrXlV70xShWK0ay\n\tdtp9yLIXxp0B9Ke/c0YOVv7IS86xQYFfIjL30ljMrgNG5bY8u6ikavme","X-Gm-Gg":"ATEYQzyy2WPQJyG7BxIiOvoK+PJfOT3D8NvAgTkGjh1DYSx7R0NNR5KOqgKVaq//Wl8\n\t+JsnY0bjxHjrm2NIraET2cN5KzGyX7UK9vXKnRb5SOBlAJIGA8mbk3ufVa4OIrTuKIg+kSZuqK/\n\t8lnIRlcFgKEVCyWLhMALNYyo8PdHsp+7O3YFQsqrmCfUMf6z751LZk9gNHNK3TdMx9LtcvN8VpF\n\toxAK73v6BglMG54q+IQw8HYgVap9bX5wIjAcPOpvlXReWk9RkVAMnaM+vdFWp20qDeKRxDp/AdM\n\tX/60MBBOmljk2co6NYgpoVNOjVF3YBzrcHvRHYG6iK41yGAij1di3voyGYIV4SJq1UGcCI3TZ1s\n\t+SjVH4ZAQ9RW0aGfM9LhE/cJ9HAZQ+6uSsf9VpBlOvwsMDuwlrJvxKstS06+phvqwSaSWP4FvEA\n\t6Vscy+ePfkh8womgYdnz0tbKIA5088Az2/GyRCQQgP+wuwyFhsyP7+Bi/RrpkRg/F2egvLjzkfl\n\t8IDIcncvIqZwwRKLUywiZ7axS/Le7oDsbC5OqIKvZ1F59Ixp+5teTSrQ+9+hdjJHQ==","X-Received":"by 2002:a05:622a:1393:b0:50b:29f0:299c with SMTP id\n d75a77b69052e-50b80e8c042mr31675151cf.60.1774410577844;\n        Tue, 24 Mar 2026 20:49:37 -0700 (PDT)","From":"Xin Long <lucien.xin@gmail.com>","To":"network dev <netdev@vger.kernel.org>,\n\tquic@lists.linux.dev","Cc":"davem@davemloft.net,\n\tkuba@kernel.org,\n\tEric Dumazet <edumazet@google.com>,\n\tPaolo Abeni <pabeni@redhat.com>,\n\tSimon Horman <horms@kernel.org>,\n\tStefan Metzmacher <metze@samba.org>,\n\tMoritz Buhl <mbuhl@openbsd.org>,\n\tTyler Fanelli <tfanelli@redhat.com>,\n\tPengtao He <hepengtao@xiaomi.com>,\n\tThomas Dreibholz <dreibh@simula.no>,\n\tlinux-cifs@vger.kernel.org,\n\tSteve French <smfrench@gmail.com>,\n\tNamjae Jeon <linkinjeon@kernel.org>,\n\tPaulo Alcantara <pc@manguebit.com>,\n\tTom Talpey <tom@talpey.com>,\n\tkernel-tls-handshake@lists.linux.dev,\n\tChuck Lever <chuck.lever@oracle.com>,\n\tJeff Layton <jlayton@kernel.org>,\n\tSteve Dickson <steved@redhat.com>,\n\tHannes Reinecke <hare@suse.de>,\n\tAlexander Aring <aahringo@redhat.com>,\n\tDavid Howells <dhowells@redhat.com>,\n\tMatthieu Baerts <matttbe@kernel.org>,\n\tJohn Ericson <mail@johnericson.me>,\n\tCong Wang <xiyou.wangcong@gmail.com>,\n\t\"D . Wythe\" <alibuda@linux.alibaba.com>,\n\tJason Baron <jbaron@akamai.com>,\n\tilliliti <illiliti@protonmail.com>,\n\tSabrina Dubroca <sd@queasysnail.net>,\n\tMarcelo Ricardo Leitner <marcelo.leitner@gmail.com>,\n\tDaniel Stenberg <daniel@haxx.se>,\n\tAndy Gospodarek <andrew.gospodarek@broadcom.com>,\n\t\"Marc E . Fiuczynski\" <marc@fiuczynski.com>","Subject":"[PATCH net-next v11 06/15] quic: add stream management","Date":"Tue, 24 Mar 2026 23:47:11 -0400","Message-ID":"\n <91c6fd0a44eb15a98f945171ead8062badb89a60.1774410440.git.lucien.xin@gmail.com>","X-Mailer":"git-send-email 2.47.1","In-Reply-To":"<cover.1774410440.git.lucien.xin@gmail.com>","References":"<cover.1774410440.git.lucien.xin@gmail.com>","Precedence":"bulk","X-Mailing-List":"linux-cifs@vger.kernel.org","List-Id":"<linux-cifs.vger.kernel.org>","List-Subscribe":"<mailto:linux-cifs+subscribe@vger.kernel.org>","List-Unsubscribe":"<mailto:linux-cifs+unsubscribe@vger.kernel.org>","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit"},"content":"This patch introduces 'struct quic_stream_table' for managing QUIC streams,\neach represented by 'struct quic_stream'.\n\nIt implements mechanisms for acquiring and releasing streams on both the\nsend and receive paths, ensuring efficient lifecycle management during\ntransmission and reception.\n\n- quic_stream_get(): Acquire a send-side stream by ID and flags during\n  TX path, or a receive-side stream by ID during RX path.\n\n- quic_stream_put(): Release a send-side stream when sending is done,\n  or a receive-side stream when receiving is done.\n\nIt includes logic to detect when stream ID limits are reached and when\ncontrol frames should be sent to update or request limits from the peer.\n\n- quic_stream_id_exceeds(): Check a stream ID would exceed local (recv)\n  or peer (send) limits.\n\n- quic_stream_max_streams_update(): Determines whether a\n  MAX_STREAMS_UNI/BIDI frame should be sent to the peer.\n\nNote stream hash table is per socket, the operations on it are always\nprotected by the sock lock.\n\nSigned-off-by: Xin Long <lucien.xin@gmail.com>\nAcked-by: Paolo Abeni <pabeni@redhat.com>\n---\nv3:\n  - Merge send/recv stream helpers into unified functions to reduce code:\n    * quic_stream_id_send/recv() → quic_stream_id_valid()\n    * quic_stream_id_send/recv_closed() → quic_stream_id_closed()\n    * quic_stream_id_send/recv_exceeds() → quic_stream_id_exceeds()\n    (pointed out by Paolo).\n  - Clarify in changelog that stream hash table is always protected by sock\n    lock (suggested by Paolo).\n  - quic_stream_init/free(): adjust for new hashtable type; call\n    quic_stream_delete() in quic_stream_free() to avoid open-coded logic.\n  - Receiving streams: delete stream only when fully read or reset, instead\n    of when no data was received. Prevents freeing a stream while a FIN\n    with no data is still queued.\nv4:\n  - Replace struct quic_shash_table with struct hlist_head for the\n    stream hashtable. Since they are protected by the socket lock,\n    no per-chain lock is needed.\n  - Initialize stream to NULL in stream creation functions to avoid\n    warnings from Smatch (reported by Simon).\n  - Allocate send streams with GFP_KERNEL_ACCOUNT and receive streams\n    with GFP_ATOMIC | __GFP_ACCOUNT for memory accounting (suggested\n    by Paolo).\nv5:\n  - Introduce struct quic_stream_limits to merge quic_stream_send_create()\n    and quic_stream_recv_create(), and to simplify quic_stream_get_param()\n    (suggested by Paolo).\n  - Annotate the sock-lock requirement for quic_stream_send/recv_get()\n    and quic_stream_send/recv_put() (notied by Paolo).\n  - Add quic_stream_bidi_put() to deduplicate the common logic between\n    quic_stream_send_put() and quic_stream_recv_put().\n  - Remove the unnecessary check when incrementing\n    streams->send.next_bidi/uni_stream_id in quic_stream_create().\n  - Remove the unused 'is_serv' parameter from quic_stream_get_param().\nv7:\n  - Free the allocated streams on error path in quic_stream_create() (noted\n    by Paolo).\n  - Merge quic_stream_send_get/put() and quic_stream_recv_get/put() helpers\n    to quic_stream_get/put() (suggested by Paolo).\n  - Add more comments in quic_stream_id_exceeds() and quic_stream_create().\nv8:\n  - Replace bitfields with plain u8 in struct quic_stream_limits and struct\n    quic_stream (suggested by Paolo).\nv9:\n  - Fix grammar in the comment for quic_stream::send.window.\nv10:\n  - Move quic_stream_init() to after sock_prot_inuse_add() ensure counters\n    are incremented before any early return paths in quic_init_sock(),\n    preventing underflow in quic_destroy_sock() (noted by AI review).\n  - Initialize the output parameters '*max_uni' and '*max_bidi' to 0 at the\n    start of quic_stream_max_streams_update()\n  - Use 'stream->recv.state > QUIC_STREAM_RECV_STATE_RECVD' instead of '!='\n    for clearer intent.\n  - Simplify some state checks in quic_stream_put() by using range\n    comparisons (> or <) instead of multiple != conditions.\n  - streams_uni/bidi are u16 type, and their overflow is already prevented\n    by QUIC_MAX_STREAMS indirectly. Update comment in quic_stream_create().\n  - Replace open-coded kzalloc(sizeof(*stream)) with kzalloc_obj(*stream)\n    in quic_stream_create().\nv11:\n  - Set maximum line length to 80 characters.\n  - Change is_serv parameter type to bool in quic_stream_id_local().\n---\n net/quic/Makefile |   2 +-\n net/quic/socket.c |   5 +\n net/quic/socket.h |   8 +\n net/quic/stream.c | 444 ++++++++++++++++++++++++++++++++++++++++++++++\n net/quic/stream.h | 133 ++++++++++++++\n 5 files changed, 591 insertions(+), 1 deletion(-)\n create mode 100644 net/quic/stream.c\n create mode 100644 net/quic/stream.h","diff":"diff --git a/net/quic/Makefile b/net/quic/Makefile\nindex 13bf4a4e5442..094e9da5d739 100644\n--- a/net/quic/Makefile\n+++ b/net/quic/Makefile\n@@ -5,4 +5,4 @@\n \n obj-$(CONFIG_IP_QUIC) += quic.o\n \n-quic-y := common.o family.o protocol.o socket.o\n+quic-y := common.o family.o protocol.o socket.o stream.o\ndiff --git a/net/quic/socket.c b/net/quic/socket.c\nindex 8dc2cb7628db..0006668551f4 100644\n--- a/net/quic/socket.c\n+++ b/net/quic/socket.c\n@@ -45,11 +45,16 @@ static int quic_init_sock(struct sock *sk)\n \tsk_sockets_allocated_inc(sk);\n \tsock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1);\n \n+\tif (quic_stream_init(quic_streams(sk)))\n+\t\treturn -ENOMEM;\n+\n \treturn 0;\n }\n \n static void quic_destroy_sock(struct sock *sk)\n {\n+\tquic_stream_free(quic_streams(sk));\n+\n \tquic_data_free(quic_ticket(sk));\n \tquic_data_free(quic_token(sk));\n \tquic_data_free(quic_alpn(sk));\ndiff --git a/net/quic/socket.h b/net/quic/socket.h\nindex 61df0c5867be..e76737b9b74b 100644\n--- a/net/quic/socket.h\n+++ b/net/quic/socket.h\n@@ -13,6 +13,7 @@\n \n #include \"common.h\"\n #include \"family.h\"\n+#include \"stream.h\"\n \n #include \"protocol.h\"\n \n@@ -33,6 +34,8 @@ struct quic_sock {\n \tstruct quic_data\t\tticket;\n \tstruct quic_data\t\ttoken;\n \tstruct quic_data\t\talpn;\n+\n+\tstruct quic_stream_table\tstreams;\n };\n \n struct quic6_sock {\n@@ -65,6 +68,11 @@ static inline struct quic_data *quic_alpn(const struct sock *sk)\n \treturn &quic_sk(sk)->alpn;\n }\n \n+static inline struct quic_stream_table *quic_streams(const struct sock *sk)\n+{\n+\treturn &quic_sk(sk)->streams;\n+}\n+\n static inline bool quic_is_serv(const struct sock *sk)\n {\n \treturn !!sk->sk_max_ack_backlog;\ndiff --git a/net/quic/stream.c b/net/quic/stream.c\nnew file mode 100644\nindex 000000000000..4d980f9b03ce\n--- /dev/null\n+++ b/net/quic/stream.c\n@@ -0,0 +1,444 @@\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+/* QUIC kernel implementation\n+ * (C) Copyright Red Hat Corp. 2023\n+ *\n+ * This file is part of the QUIC kernel implementation\n+ *\n+ * Initialization/cleanup for QUIC protocol support.\n+ *\n+ * Written or modified by:\n+ *    Xin Long <lucien.xin@gmail.com>\n+ */\n+\n+#include <linux/quic.h>\n+\n+#include \"common.h\"\n+#include \"stream.h\"\n+\n+/* Check if a stream ID is valid for sending or receiving. */\n+static bool quic_stream_id_valid(s64 stream_id, bool is_serv, bool send)\n+{\n+\tu8 type = (stream_id & QUIC_STREAM_TYPE_MASK);\n+\n+\tif (send) {\n+\t\tif (is_serv)\n+\t\t\treturn type != QUIC_STREAM_TYPE_CLIENT_UNI;\n+\t\treturn type != QUIC_STREAM_TYPE_SERVER_UNI;\n+\t}\n+\tif (is_serv)\n+\t\treturn type != QUIC_STREAM_TYPE_SERVER_UNI;\n+\treturn type != QUIC_STREAM_TYPE_CLIENT_UNI;\n+}\n+\n+/* Check if a stream ID was initiated locally. */\n+static bool quic_stream_id_local(s64 stream_id, bool is_serv)\n+{\n+\treturn is_serv ^ !(stream_id & QUIC_STREAM_TYPE_SERVER_MASK);\n+}\n+\n+/* Check if a stream ID represents a unidirectional stream. */\n+static bool quic_stream_id_uni(s64 stream_id)\n+{\n+\treturn stream_id & QUIC_STREAM_TYPE_UNI_MASK;\n+}\n+\n+#define QUIC_STREAM_HT_SIZE\t64\n+\n+static struct hlist_head *quic_stream_head(struct quic_stream_table *streams,\n+\t\t\t\t\t   s64 stream_id)\n+{\n+\treturn &streams->head[stream_id & (QUIC_STREAM_HT_SIZE - 1)];\n+}\n+\n+struct quic_stream *quic_stream_find(struct quic_stream_table *streams,\n+\t\t\t\t     s64 stream_id)\n+{\n+\tstruct hlist_head *head = quic_stream_head(streams, stream_id);\n+\tstruct quic_stream *stream;\n+\n+\thlist_for_each_entry(stream, head, node) {\n+\t\tif (stream->id == stream_id)\n+\t\t\tbreak;\n+\t}\n+\treturn stream;\n+}\n+\n+static void quic_stream_add(struct quic_stream_table *streams,\n+\t\t\t    struct quic_stream *stream)\n+{\n+\tstruct hlist_head *head;\n+\n+\thead = quic_stream_head(streams, stream->id);\n+\thlist_add_head(&stream->node, head);\n+}\n+\n+static void quic_stream_delete(struct quic_stream *stream)\n+{\n+\thlist_del_init(&stream->node);\n+\tkfree(stream);\n+}\n+\n+/* Create and register new streams for sending or receiving. */\n+static struct quic_stream *quic_stream_create(struct quic_stream_table *streams,\n+\t\t\t\t\t      s64 max_stream_id, bool send,\n+\t\t\t\t\t      bool is_serv)\n+{\n+\tstruct quic_stream_limits *limits = &streams->send;\n+\tstruct quic_stream *pos, *stream = NULL;\n+\tgfp_t gfp = GFP_KERNEL_ACCOUNT;\n+\tstruct hlist_node *tmp;\n+\tHLIST_HEAD(head);\n+\ts64 stream_id;\n+\tu32 count = 0;\n+\n+\tif (!send) {\n+\t\tlimits = &streams->recv;\n+\t\tgfp = GFP_ATOMIC | __GFP_ACCOUNT;\n+\t}\n+\tstream_id = limits->next_bidi_stream_id;\n+\tif (quic_stream_id_uni(max_stream_id))\n+\t\tstream_id = limits->next_uni_stream_id;\n+\n+\t/* rfc9000#section-2.1: A stream ID that is used out of order results in\n+\t * all streams of that type with lower-numbered stream IDs also being\n+\t * opened.\n+\t */\n+\twhile (stream_id <= max_stream_id) {\n+\t\tstream = kzalloc_obj(*stream, gfp);\n+\t\tif (!stream)\n+\t\t\tgoto free;\n+\n+\t\tstream->id = stream_id;\n+\t\tif (quic_stream_id_uni(stream_id)) {\n+\t\t\tif (send) {\n+\t\t\t\tstream->send.max_bytes =\n+\t\t\t\t\tlimits->max_stream_data_uni;\n+\t\t\t} else {\n+\t\t\t\tstream->recv.max_bytes =\n+\t\t\t\t\tlimits->max_stream_data_uni;\n+\t\t\t\tstream->recv.window = stream->recv.max_bytes;\n+\t\t\t}\n+\t\t\thlist_add_head(&stream->node, &head);\n+\t\t\tstream_id += QUIC_STREAM_ID_STEP;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (quic_stream_id_local(stream_id, is_serv)) {\n+\t\t\tstream->send.max_bytes =\n+\t\t\t\tstreams->send.max_stream_data_bidi_remote;\n+\t\t\tstream->recv.max_bytes =\n+\t\t\t\tstreams->recv.max_stream_data_bidi_local;\n+\t\t} else {\n+\t\t\tstream->send.max_bytes =\n+\t\t\t\tstreams->send.max_stream_data_bidi_local;\n+\t\t\tstream->recv.max_bytes =\n+\t\t\t\tstreams->recv.max_stream_data_bidi_remote;\n+\t\t}\n+\t\tstream->recv.window = stream->recv.max_bytes;\n+\t\thlist_add_head(&stream->node, &head);\n+\t\tstream_id += QUIC_STREAM_ID_STEP;\n+\t}\n+\n+\thlist_for_each_entry_safe(pos, tmp, &head, node) {\n+\t\thlist_del_init(&pos->node);\n+\t\tquic_stream_add(streams, pos);\n+\t\tcount++;\n+\t}\n+\n+\t/* Streams must be opened sequentially. Update the next stream ID so the\n+\t * correct starting point is known if an out-of-order open is requested.\n+\t * Note overflow of next_uni/bidi_stream_id is impossible with s64.\n+\t */\n+\tif (quic_stream_id_uni(stream_id)) {\n+\t\tlimits->next_uni_stream_id = stream_id;\n+\t\tlimits->streams_uni += count;\n+\t\treturn stream;\n+\t}\n+\n+\tlimits->next_bidi_stream_id = stream_id;\n+\tlimits->streams_bidi += count;\n+\treturn stream;\n+\n+free:\n+\thlist_for_each_entry_safe(pos, tmp, &head, node) {\n+\t\thlist_del_init(&pos->node);\n+\t\tkfree(pos);\n+\t}\n+\treturn NULL;\n+}\n+\n+/* Check if a send or receive stream ID is already closed. */\n+static bool quic_stream_id_closed(struct quic_stream_table *streams,\n+\t\t\t\t  s64 stream_id, bool send)\n+{\n+\tstruct quic_stream_limits *limits = send ? &streams->send :\n+\t\t\t\t\t\t   &streams->recv;\n+\n+\tif (quic_stream_id_uni(stream_id))\n+\t\treturn stream_id < limits->next_uni_stream_id;\n+\treturn stream_id < limits->next_bidi_stream_id;\n+}\n+\n+/* Check if a stream ID would exceed local (recv) or peer (send) limits. */\n+bool quic_stream_id_exceeds(struct quic_stream_table *streams, s64 stream_id,\n+\t\t\t    bool send)\n+{\n+\tu64 nstreams;\n+\n+\tif (!send) {\n+\t\t/* recv.max_uni_stream_id is updated in\n+\t\t * quic_stream_max_streams_update() already based on\n+\t\t * next_uni/bidi_stream_id, max_streams_uni/bidi, and\n+\t\t * streams_uni/bidi, so only recv.max_uni_stream_id needs to be\n+\t\t * checked.\n+\t\t */\n+\t\tif (quic_stream_id_uni(stream_id))\n+\t\t\treturn stream_id > streams->recv.max_uni_stream_id;\n+\n+\t\treturn stream_id > streams->recv.max_bidi_stream_id;\n+\t}\n+\n+\tif (quic_stream_id_uni(stream_id)) {\n+\t\tif (stream_id > streams->send.max_uni_stream_id)\n+\t\t\treturn true;\n+\t\tstream_id -= streams->send.next_uni_stream_id;\n+\t\tnstreams = quic_stream_id_to_streams(stream_id);\n+\n+\t\treturn nstreams + streams->send.streams_uni >\n+\t\t       streams->send.max_streams_uni;\n+\t}\n+\n+\tif (stream_id > streams->send.max_bidi_stream_id)\n+\t\treturn true;\n+\tstream_id -= streams->send.next_bidi_stream_id;\n+\tnstreams = quic_stream_id_to_streams(stream_id);\n+\n+\treturn nstreams + streams->send.streams_bidi >\n+\t       streams->send.max_streams_bidi;\n+}\n+\n+/* Get or create a send or recv stream by ID. Requires sock lock held. */\n+struct quic_stream *quic_stream_get(struct quic_stream_table *streams,\n+\t\t\t\t    s64 stream_id, u32 flags, bool is_serv,\n+\t\t\t\t    bool send)\n+{\n+\tstruct quic_stream *stream;\n+\n+\tif (!quic_stream_id_valid(stream_id, is_serv, send))\n+\t\treturn ERR_PTR(-EINVAL);\n+\n+\tstream = quic_stream_find(streams, stream_id);\n+\tif (stream) {\n+\t\tif (send && (flags & MSG_QUIC_STREAM_NEW) &&\n+\t\t    stream->send.state != QUIC_STREAM_SEND_STATE_READY)\n+\t\t\treturn ERR_PTR(-EINVAL);\n+\t\treturn stream;\n+\t}\n+\n+\tif (!send && quic_stream_id_local(stream_id, is_serv)) {\n+\t\tif (quic_stream_id_closed(streams, stream_id, !send))\n+\t\t\treturn ERR_PTR(-ENOSTR);\n+\t\treturn ERR_PTR(-EINVAL);\n+\t}\n+\n+\tif (quic_stream_id_closed(streams, stream_id, send))\n+\t\treturn ERR_PTR(-ENOSTR);\n+\n+\tif (send && !(flags & MSG_QUIC_STREAM_NEW))\n+\t\treturn ERR_PTR(-EINVAL);\n+\n+\tif (quic_stream_id_exceeds(streams, stream_id, send))\n+\t\treturn ERR_PTR(-EAGAIN);\n+\n+\tstream = quic_stream_create(streams, stream_id, send, is_serv);\n+\tif (!stream)\n+\t\treturn ERR_PTR(-ENOSTR);\n+\n+\tif (send || quic_stream_id_valid(stream_id, is_serv, !send))\n+\t\tstreams->send.active_stream_id = stream_id;\n+\n+\treturn stream;\n+}\n+\n+/* Release or clean up a send or recv stream. This function updates stream\n+ * counters and state when a send stream has either successfully sent all data\n+ * or has been reset, or when a recv stream has either consumed all data or has\n+ * been reset. Requires sock lock held.\n+ */\n+void quic_stream_put(struct quic_stream_table *streams,\n+\t\t     struct quic_stream *stream, bool is_serv, bool send)\n+{\n+\tif (quic_stream_id_uni(stream->id)) {\n+\t\tif (send) {\n+\t\t\t/* For uni streams, decrement uni count and delete\n+\t\t\t * immediately.\n+\t\t\t */\n+\t\t\tstreams->send.streams_uni--;\n+\t\t\tquic_stream_delete(stream);\n+\t\t\treturn;\n+\t\t}\n+\t\t/* For uni streams, decrement uni count and mark done. */\n+\t\tif (!stream->recv.done) {\n+\t\t\tstream->recv.done = 1;\n+\t\t\tstreams->recv.streams_uni--;\n+\t\t\tstreams->recv.uni_pending = 1;\n+\t\t}\n+\t\t/* Delete stream if fully read or reset. */\n+\t\tif (stream->recv.state > QUIC_STREAM_RECV_STATE_RECVD)\n+\t\t\tquic_stream_delete(stream);\n+\t\treturn;\n+\t}\n+\n+\tif (send) {\n+\t\t/* For bidi streams, only proceed if receive side is in a final\n+\t\t * state.\n+\t\t */\n+\t\tif (stream->recv.state < QUIC_STREAM_RECV_STATE_RECVD)\n+\t\t\treturn;\n+\t} else {\n+\t\t/* For bidi streams, only proceed if send side is in a final\n+\t\t * state.\n+\t\t */\n+\t\tif (stream->send.state != QUIC_STREAM_SEND_STATE_RECVD &&\n+\t\t    stream->send.state != QUIC_STREAM_SEND_STATE_RESET_RECVD)\n+\t\t\treturn;\n+\t}\n+\n+\tif (quic_stream_id_local(stream->id, is_serv)) {\n+\t\t/* Local-initiated stream: mark send done and decrement\n+\t\t * send.bidi count.\n+\t\t */\n+\t\tif (!stream->send.done) {\n+\t\t\tstream->send.done = 1;\n+\t\t\tstreams->send.streams_bidi--;\n+\t\t}\n+\t} else {\n+\t\t/* Remote-initiated stream: mark recv done and decrement recv\n+\t\t * bidi count.\n+\t\t */\n+\t\tif (!stream->recv.done) {\n+\t\t\tstream->recv.done = 1;\n+\t\t\tstreams->recv.streams_bidi--;\n+\t\t\tstreams->recv.bidi_pending = 1;\n+\t\t}\n+\t}\n+\n+\t/* Delete stream if fully read or reset. */\n+\tif (stream->recv.state > QUIC_STREAM_RECV_STATE_RECVD)\n+\t\tquic_stream_delete(stream);\n+}\n+\n+/* Updates the maximum allowed incoming stream IDs if any streams were recently\n+ * closed.  Recalculates the max_uni and max_bidi stream ID limits based on the\n+ * number of open streams and whether any were marked for deletion.\n+ *\n+ * Returns true if either max_uni or max_bidi was updated, indicating that a\n+ * MAX_STREAMS_UNI or MAX_STREAMS_BIDI frame should be sent to the peer.\n+ */\n+bool quic_stream_max_streams_update(struct quic_stream_table *streams,\n+\t\t\t\t    s64 *max_uni, s64 *max_bidi)\n+{\n+\ts64 max, rem;\n+\n+\t*max_uni = 0;\n+\t*max_bidi = 0;\n+\tif (streams->recv.uni_pending) {\n+\t\trem = streams->recv.max_streams_uni - streams->recv.streams_uni;\n+\t\tmax = streams->recv.next_uni_stream_id - QUIC_STREAM_ID_STEP +\n+\t\t      (rem << QUIC_STREAM_TYPE_BITS);\n+\n+\t\tstreams->recv.max_uni_stream_id = max;\n+\t\t*max_uni = quic_stream_id_to_streams(max);\n+\t\tstreams->recv.uni_pending = 0;\n+\t}\n+\tif (streams->recv.bidi_pending) {\n+\t\trem = streams->recv.max_streams_bidi -\n+\t\t      streams->recv.streams_bidi;\n+\t\tmax = streams->recv.next_bidi_stream_id - QUIC_STREAM_ID_STEP +\n+\t\t\t(rem << QUIC_STREAM_TYPE_BITS);\n+\n+\t\tstreams->recv.max_bidi_stream_id = max;\n+\t\t*max_bidi = quic_stream_id_to_streams(max);\n+\t\tstreams->recv.bidi_pending = 0;\n+\t}\n+\n+\treturn *max_uni || *max_bidi;\n+}\n+\n+int quic_stream_init(struct quic_stream_table *streams)\n+{\n+\tstruct hlist_head *head;\n+\tint i;\n+\n+\thead = kmalloc_array(QUIC_STREAM_HT_SIZE, sizeof(*head), GFP_KERNEL);\n+\tif (!head)\n+\t\treturn -ENOMEM;\n+\tfor (i = 0; i < QUIC_STREAM_HT_SIZE; i++)\n+\t\tINIT_HLIST_HEAD(&head[i]);\n+\tstreams->head = head;\n+\treturn 0;\n+}\n+\n+void quic_stream_free(struct quic_stream_table *streams)\n+{\n+\tstruct quic_stream *stream;\n+\tstruct hlist_head *head;\n+\tstruct hlist_node *tmp;\n+\tint i;\n+\n+\tif (!streams->head)\n+\t\treturn;\n+\n+\tfor (i = 0; i < QUIC_STREAM_HT_SIZE; i++) {\n+\t\thead = &streams->head[i];\n+\t\thlist_for_each_entry_safe(stream, tmp, head, node)\n+\t\t\tquic_stream_delete(stream);\n+\t}\n+\tkfree(streams->head);\n+}\n+\n+/* Populate transport parameters from stream hash table. */\n+void quic_stream_get_param(struct quic_stream_table *streams,\n+\t\t\t   struct quic_transport_param *p)\n+{\n+\tstruct quic_stream_limits *limits = p->remote ? &streams->send :\n+\t\t\t\t\t\t\t&streams->recv;\n+\n+\tp->max_stream_data_bidi_remote = limits->max_stream_data_bidi_remote;\n+\tp->max_stream_data_bidi_local = limits->max_stream_data_bidi_local;\n+\tp->max_stream_data_uni = limits->max_stream_data_uni;\n+\tp->max_streams_bidi = limits->max_streams_bidi;\n+\tp->max_streams_uni = limits->max_streams_uni;\n+}\n+\n+/* Configure stream hashtable from transport parameters. */\n+void quic_stream_set_param(struct quic_stream_table *streams,\n+\t\t\t   struct quic_transport_param *p, bool is_serv)\n+{\n+\tstruct quic_stream_limits *limits = p->remote ? &streams->send :\n+\t\t\t\t\t\t\t&streams->recv;\n+\tu8 bidi_type, uni_type;\n+\n+\tlimits->max_stream_data_bidi_local = p->max_stream_data_bidi_local;\n+\tlimits->max_stream_data_bidi_remote = p->max_stream_data_bidi_remote;\n+\tlimits->max_stream_data_uni = p->max_stream_data_uni;\n+\tlimits->max_streams_bidi = p->max_streams_bidi;\n+\tlimits->max_streams_uni = p->max_streams_uni;\n+\tlimits->active_stream_id = -1;\n+\n+\tif (p->remote ^ is_serv) {\n+\t\tbidi_type = QUIC_STREAM_TYPE_CLIENT_BIDI;\n+\t\tuni_type = QUIC_STREAM_TYPE_CLIENT_UNI;\n+\t} else {\n+\t\tbidi_type = QUIC_STREAM_TYPE_SERVER_BIDI;\n+\t\tuni_type = QUIC_STREAM_TYPE_SERVER_UNI;\n+\t}\n+\n+\tlimits->max_bidi_stream_id =\n+\t\tquic_stream_streams_to_id(p->max_streams_bidi, bidi_type);\n+\tlimits->next_bidi_stream_id = bidi_type;\n+\n+\tlimits->max_uni_stream_id =\n+\t\tquic_stream_streams_to_id(p->max_streams_uni, uni_type);\n+\tlimits->next_uni_stream_id = uni_type;\n+}\ndiff --git a/net/quic/stream.h b/net/quic/stream.h\nnew file mode 100644\nindex 000000000000..435ae1246e05\n--- /dev/null\n+++ b/net/quic/stream.h\n@@ -0,0 +1,133 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/* QUIC kernel implementation\n+ * (C) Copyright Red Hat Corp. 2023\n+ *\n+ * This file is part of the QUIC kernel implementation\n+ *\n+ * Written or modified by:\n+ *    Xin Long <lucien.xin@gmail.com>\n+ */\n+\n+#define QUIC_DEF_STREAMS\t100\n+#define QUIC_MAX_STREAMS\t4096ULL\n+\n+/*\n+ * rfc9000#section-2.1:\n+ *\n+ *   The least significant bit (0x01) of the stream ID identifies the initiator\n+ *   of the stream.  Client-initiated streams have even-numbered stream IDs\n+ *   (with the bit set to 0), and server-initiated streams have odd-numbered\n+ *   stream IDs (with the bit set to 1).\n+ *\n+ *   The second least significant bit (0x02) of the stream ID distinguishes\n+ *   between bidirectional streams (with the bit set to 0) and unidirectional\n+ *   streams (with the bit set to 1).\n+ */\n+#define QUIC_STREAM_TYPE_BITS\t2\n+#define QUIC_STREAM_ID_STEP\tBIT(QUIC_STREAM_TYPE_BITS)\n+\n+#define QUIC_STREAM_TYPE_CLIENT_BIDI\t0x00\n+#define QUIC_STREAM_TYPE_SERVER_BIDI\t0x01\n+#define QUIC_STREAM_TYPE_CLIENT_UNI\t0x02\n+#define QUIC_STREAM_TYPE_SERVER_UNI\t0x03\n+\n+struct quic_stream {\n+\tstruct hlist_node node;\n+\ts64 id; /* Stream ID as defined in RFC 9000 Section 2.1 */\n+\tstruct {\n+\t\t/* Sending-side stream level flow control */\n+\t\tu64 last_max_bytes; /* Max send offset advertised by peer */\n+\t\tu64 max_bytes;      /* Max offset allowed to send */\n+\t\tu64 bytes;          /* Bytes already sent to peer */\n+\n+\t\tu32 errcode; /* App error code for RESET_STREAM */\n+\t\tu32 frags;   /* STREAM frames sent but not yet acked */\n+\t\tu8 state;    /* Send stream state, per rfc9000#section-3.1 */\n+\n+\t\tu8 data_blocked; /* True if flow control blocks sending */\n+\t\tu8 done;         /* True if FIN has been sent */\n+\t} send;\n+\tstruct {\n+\t\t/* Receiving-side stream level flow control */\n+\t\tu64 max_bytes; /* Max offset peer can send */\n+\t\tu64 window;    /* Remaining receive window */\n+\t\tu64 bytes;     /* Bytes consumed by app */\n+\n+\t\tu64 highest; /* Highest received offset */\n+\t\tu64 offset;  /* Data buffered or consumed */\n+\t\tu64 finalsz; /* Final stream size if FIN received */\n+\n+\t\tu32 frags; /* STREAM frames pending reassembly */\n+\t\tu8 state;  /* Receive stream state, per rfc9000#section-3.2 */\n+\n+\t\tu8 stop_sent; /* True if STOP_SENDING has been sent */\n+\t\tu8 done;      /* True if all data has been received or read */\n+\t} recv;\n+};\n+\n+struct quic_stream_limits {\n+\t/* Stream limit parameters defined in rfc9000#section-18.2:\n+\t *\n+\t * - initial_max_stream_data_bidi_remote\n+\t * - initial_max_stream_data_bidi_local\n+\t * - initial_max_stream_data_uni\n+\t * - initial_max_streams_bidi\n+\t * - initial_max_streams_uni\n+\t */\n+\tu64 max_stream_data_bidi_remote;\n+\tu64 max_stream_data_bidi_local;\n+\tu64 max_stream_data_uni;\n+\tu64 max_streams_bidi;\n+\tu64 max_streams_uni;\n+\n+\ts64 next_bidi_stream_id; /* Next bidi stream ID to open or accept */\n+\ts64 next_uni_stream_id;  /* Next uni stream ID to open or accept */\n+\ts64 max_bidi_stream_id;  /* Highest allowed bidi stream ID */\n+\ts64 max_uni_stream_id;   /* Highest allowed uni stream ID */\n+\ts64 active_stream_id;    /* Most recently opened stream ID */\n+\n+\tu8 bidi_blocked;  /* STREAMS_BLOCKED_BIDI sent, awaiting ACK */\n+\tu8 uni_blocked;   /* STREAMS_BLOCKED_UNI sent, awaiting ACK */\n+\tu8 bidi_pending;  /* MAX_STREAMS_BIDI needs to be sent */\n+\tu8 uni_pending;   /* MAX_STREAMS_UNI needs to be sent */\n+\n+\tu16 streams_bidi; /* Number of open bidi streams */\n+\tu16 streams_uni;  /* Number of open uni streams */\n+};\n+\n+struct quic_stream_table {\n+\tstruct hlist_head *head; /* Hash table storing all active streams */\n+\n+\tstruct quic_stream_limits send; /* Limits advertised by peer */\n+\tstruct quic_stream_limits recv; /* Limits we advertise to peer */\n+};\n+\n+static inline u64 quic_stream_id_to_streams(s64 stream_id)\n+{\n+\treturn (u64)(stream_id >> QUIC_STREAM_TYPE_BITS) + 1;\n+}\n+\n+static inline s64 quic_stream_streams_to_id(u64 streams, u8 type)\n+{\n+\treturn (s64)((streams - 1) << QUIC_STREAM_TYPE_BITS) | type;\n+}\n+\n+struct quic_stream *quic_stream_get(struct quic_stream_table *streams,\n+\t\t\t\t    s64 stream_id, u32 flags, bool is_serv,\n+\t\t\t\t    bool send);\n+void quic_stream_put(struct quic_stream_table *streams,\n+\t\t     struct quic_stream *stream, bool is_serv, bool send);\n+\n+bool quic_stream_max_streams_update(struct quic_stream_table *streams,\n+\t\t\t\t    s64 *max_uni, s64 *max_bidi);\n+bool quic_stream_id_exceeds(struct quic_stream_table *streams,\n+\t\t\t    s64 stream_id, bool send);\n+struct quic_stream *quic_stream_find(struct quic_stream_table *streams,\n+\t\t\t\t     s64 stream_id);\n+\n+void quic_stream_get_param(struct quic_stream_table *streams,\n+\t\t\t   struct quic_transport_param *p);\n+void quic_stream_set_param(struct quic_stream_table *streams,\n+\t\t\t   struct quic_transport_param *p, bool is_serv);\n+void quic_stream_free(struct quic_stream_table *streams);\n+int quic_stream_init(struct quic_stream_table *streams);\n","prefixes":["net-next","v11","06/15"]}