commit c803eebd067b3e025cfa0da21712caf642ab73bc
parent dc426594a4224def23567add24ca9022204272ad
Author: Evgeny Grin (Karlson2k) <k2k@drgrin.dev>
Date: Sun, 2 Nov 2025 19:41:33 +0100
HTTP/2: basic implementation, Implemented URI normalizer and HTTP/2 URI parameters parsing
Diffstat:
82 files changed, 11201 insertions(+), 309 deletions(-)
diff --git a/src/examples2/demo.c b/src/examples2/demo.c
@@ -969,7 +969,7 @@ done_cb (struct MHD_Request *req,
}
/* create directories -- if they don't exist already */
#if ! defined(_WIN32) || defined(__CYGWIN__)
- (void) mkdir (lang->cstr,
+ (void) mkdir (lang.cstr,
S_IRWXU);
#else
(void) mkdir (lang.cstr);
diff --git a/src/examples2/minimal_example2.c b/src/examples2/minimal_example2.c
@@ -39,6 +39,12 @@ req_cb (void *cls,
(void) method;
(void) upload_size; /* Unused */
+ struct MHD_StringNullable val;
+ MHD_request_get_value (request,
+ MHD_VK_URI_QUERY_PARAM,
+ "abc",
+ &val);
+
return MHD_action_from_response (
request,
MHD_response_from_buffer_static (
diff --git a/src/mhd2/Makefile.am b/src/mhd2/Makefile.am
@@ -35,8 +35,8 @@ libmicrohttpd2_la_SOURCES = \
compat_calloc.h \
sys_w32_ver.h \
mhd_align.h mhd_bithelpers.h mhd_byteorder.h \
- mhd_constexpr.h mhd_assert.h mhd_unreachable.h \
- mhd_predict.h \
+ mhd_assert.h mhd_assume.h mhd_unreachable.h \
+ mhd_constexpr.h mhd_predict.h \
mhd_cntnr_ptr.h mhd_arr_num_elems.h \
mhd_tristate.h mhd_status_code_int.h \
mhd_socket_type.h mhd_sockets_macros.h \
@@ -47,6 +47,7 @@ libmicrohttpd2_la_SOURCES = \
mhd_str.c mhd_str.h \
mhd_str_macros.h mhd_str_types.h \
mhd_buffer.h \
+ mhd_comm_layer_state.h \
mhd_limits.h \
mhd_iovec.h \
mhd_dbg_print.h \
@@ -107,9 +108,34 @@ libmicrohttpd2_la_SOURCES += \
compat_calloc.c
endif
+#
httptwo_OPTSOURCES = \
+ mhd_http_layer_state.h \
+ h2/h2_bit_masks.h \
+ h2/h2_req_data.h \
+ h2/h2_stream_data.h \
+ h2/h2_resp_data.h \
+ h2/h2_conn_data.h h2/h2_err_codes.h h2/h2_frame_types.h \
+ h2/h2_frame_length.h h2/h2_frame_init.h \
+ h2/h2_frame_codec.c h2/h2_frame_codec.h \
+ h2/h2_proc_settings.c h2/h2_proc_settings.h h2/h2_settings.h \
+ h2/h2_req_item_kinds.h h2/h2_req_item_struct.h \
+ h2/h2_req_items_funcs.c h2/h2_req_items_funcs.h \
+ h2/h2_comm.c h2/h2_comm.h \
+ h2/h2_proc_conn.c h2/h2_proc_conn.h \
+ h2/h2_proc_in.c h2/h2_proc_in.h \
+ h2/h2_proc_out.c h2/h2_proc_out.h \
+ h2/h2_conn_streams.c h2/h2_conn_streams.h \
+ h2/h2_req_fields.c h2/h2_req_fields.h \
+ h2/h2_req_get_items.c h2/h2_req_get_items.h \
+ h2/h2_app_cb.c h2/h2_app_cb.h \
+ h2/h2_action.c h2/h2_action.h \
+ h2/h2_reply_funcs.c h2/h2_reply_funcs.h \
+ h2/hpack/h2_huffman_est.h \
h2/hpack/h2_huffman_codec.c \
h2/hpack/h2_huffman_codec.h \
+ h2/hpack/mhd_hpack_dec_types.h \
+ h2/hpack/mhd_hpack_enc_types.h \
h2/hpack/mhd_hpack_codec.c \
h2/hpack/mhd_hpack_codec.h
@@ -316,11 +342,12 @@ $(builddir)/../incl_priv/config/mhd_config.h.in: $(top_srcdir)/configure.ac
check-sources-missing:
@$(am__cd) "$(srcdir)" && \
- echo $(ECHO_N) "Checking for missing sources" ; \
- res="" ; \
- listed_srcs='$(DIST_SOURCES)' ; \
- for src in *.[ch] ; do \
- case " $$listed_srcs " in \
+ echo $(ECHO_N) "Checking for sources missing in Makefile" ; \
+ res="" && \
+ makefile_srcs='$(DIST_SOURCES)' && \
+ fs_srcs="*.[ch] h2/*.[ch] h2/hpack/*.[ch]" && \
+ for src in $$fs_srcs ; do \
+ case " $$makefile_srcs " in \
*" $$src "*) echo $(ECHO_N) "." ;; \
*) res="$$res $$src" ;; \
esac ; \
diff --git a/src/mhd2/action.c b/src/mhd2/action.c
@@ -59,6 +59,9 @@
#ifdef MHD_SUPPORT_UPGRADE
# include "upgrade_prep.h"
#endif
+#ifdef MHD_SUPPORT_HTTP2
+# include "h2/h2_action.h"
+#endif
#include "mhd_public_api.h"
@@ -67,7 +70,8 @@ MHD_EXTERN_ MHD_FN_PAR_NONNULL_ALL_
const struct MHD_Action *
MHD_action_suspend (struct MHD_Request *request)
{
- struct MHD_Action *const restrict head_act = &(request->app_act.head_act);
+ struct MHD_Action *const head_act = mhd_REQ_GET_ACT_HEAD (request);
+
if (mhd_ACTION_NO_ACTION != head_act->act)
return (const struct MHD_Action *) NULL;
@@ -81,13 +85,26 @@ MHD_FN_PAR_NONNULL_ (1) const struct MHD_Action *
MHD_action_from_response (struct MHD_Request *MHD_RESTRICT request,
struct MHD_Response *MHD_RESTRICT response)
{
- struct MHD_Action *const restrict head_act = &(request->app_act.head_act);
+ struct MHD_Action *const head_act = mhd_REQ_GET_ACT_HEAD (request);
if (NULL == response)
return (const struct MHD_Action *) NULL;
mhd_response_check_frozen_freeze (response);
mhd_response_inc_use_count (response);
+#ifdef MHD_SUPPORT_HTTP2
+ if (mhd_REQ_IS_HTTP2 (request))
+ {
+ if (! mhd_h2_act_is_resp_h2_compatible ((struct mhd_H2RequestData*) request,
+ response))
+ {
+ /* Clean-up unused response */
+ mhd_response_dec_use_count (response);
+ return (const struct MHD_Action *) NULL;
+ }
+ }
+#endif /* MHD_SUPPORT_HTTP2 */
+
if (mhd_ACTION_NO_ACTION != head_act->act)
{
/* Clean-up unused response */
@@ -95,12 +112,14 @@ MHD_action_from_response (struct MHD_Request *MHD_RESTRICT request,
return (const struct MHD_Action *) NULL;
}
#ifdef MHD_SUPPORT_AUTH_DIGEST
- if (mhd_RESP_HAD_AUTH_DIGEST (response) &&
+ if (mhd_RESP_HAS_AUTH_DIGEST (response) &&
! mhd_D_HAS_AUTH_DIGEST ( \
mhd_CNTNR_CPTR (request, struct MHD_Connection, rq)->daemon))
{
/* Clean-up unused response */
mhd_response_dec_use_count (response);
+ // TODO: get connection pointer for HTTP/2
+ mhd_assert (! mhd_REQ_IS_HTTP2 (request));
mhd_LOG_MSG (mhd_CNTNR_PTR (request, struct MHD_Connection, rq)->daemon, \
MHD_SC_AUTH_DIGEST_UNSUPPORTED, \
"Attempted to use a response with Digest Auth challenge on " \
@@ -125,9 +144,11 @@ MHD_action_process_upload (struct MHD_Request *request,
MHD_UploadCallback uc_inc,
void *uc_inc_cls)
{
- struct MHD_Action *const restrict head_act = &(request->app_act.head_act);
+ struct MHD_Action *const head_act = mhd_REQ_GET_ACT_HEAD (request);
+
if (mhd_ACTION_NO_ACTION != head_act->act)
return (const struct MHD_Action *) NULL;
+
if (0 == large_buffer_size)
{
if (NULL != uc_full)
@@ -164,10 +185,11 @@ MHD_action_parse_post (struct MHD_Request *request,
void *done_cb_cls)
{
#ifdef MHD_SUPPORT_POST_PARSER
- struct MHD_Action *const restrict head_act =
- &(request->app_act.head_act);
+ struct MHD_Action *const head_act = mhd_REQ_GET_ACT_HEAD (request);
+
if (mhd_ACTION_NO_ACTION != head_act->act)
return (const struct MHD_Action *) NULL;
+
if (NULL == done_cb)
return (const struct MHD_Action *) NULL;
@@ -205,6 +227,9 @@ MHD_action_upgrade (struct MHD_Request *MHD_RESTRICT request,
{
struct MHD_Action *const restrict head_act =
&(request->app_act.head_act);
+
+ if (mhd_REQ_IS_HTTP2 (request))
+ return (const struct MHD_Action *) NULL;
if (mhd_ACTION_NO_ACTION != head_act->act)
return (const struct MHD_Action *) NULL;
if (NULL == upgrade_handler)
@@ -241,6 +266,9 @@ MHD_upload_action_upgrade (
{
struct MHD_UploadAction *const restrict upl_act =
&(request->app_act.upl_act);
+
+ if (mhd_REQ_IS_HTTP2 (request))
+ return (const struct MHD_UploadAction *) NULL;
if (mhd_UPLOAD_ACTION_NO_ACTION != upl_act->act)
return (const struct MHD_UploadAction *) NULL;
if (NULL == upgrade_handler)
@@ -270,8 +298,7 @@ MHD_EXTERN_ MHD_FN_PAR_NONNULL_ALL_
const struct MHD_UploadAction *
MHD_upload_action_suspend (struct MHD_Request *request)
{
- struct MHD_UploadAction *const restrict upl_act =
- &(request->app_act.upl_act);
+ struct MHD_UploadAction *const upl_act = mhd_REQ_GET_ACT_UPLD (request);
if (mhd_UPLOAD_ACTION_NO_ACTION != upl_act->act)
return (const struct MHD_UploadAction *) NULL;
@@ -286,14 +313,26 @@ MHD_FN_PAR_NONNULL_ (1) const struct MHD_UploadAction *
MHD_upload_action_from_response (struct MHD_Request *MHD_RESTRICT request,
struct MHD_Response *MHD_RESTRICT response)
{
- struct MHD_UploadAction *const restrict upl_act =
- &(request->app_act.upl_act);
+ struct MHD_UploadAction *const upl_act = mhd_REQ_GET_ACT_UPLD (request);
if (NULL == response)
return (const struct MHD_UploadAction *) NULL;
mhd_response_check_frozen_freeze (response);
mhd_response_inc_use_count (response);
+#ifdef MHD_SUPPORT_HTTP2
+ if (mhd_REQ_IS_HTTP2 (request))
+ {
+ if (! mhd_h2_act_is_resp_h2_compatible ((struct mhd_H2RequestData*) request,
+ response))
+ {
+ /* Clean-up unused response */
+ mhd_response_dec_use_count (response);
+ return (const struct MHD_UploadAction *) NULL;
+ }
+ }
+#endif /* MHD_SUPPORT_HTTP2 */
+
if (mhd_UPLOAD_ACTION_NO_ACTION != upl_act->act)
{
/* Clean-up unused response */
@@ -307,6 +346,8 @@ MHD_upload_action_from_response (struct MHD_Request *MHD_RESTRICT request,
{
/* Clean-up unused response */
mhd_response_dec_use_count (response);
+ // TODO: get connection pointer for HTTP/2
+ mhd_assert (! mhd_REQ_IS_HTTP2 (request));
mhd_LOG_MSG (mhd_CNTNR_PTR (request, struct MHD_Connection, rq)->daemon, \
MHD_SC_AUTH_DIGEST_UNSUPPORTED, \
"Attempted to use a response with Digest Auth challenge on " \
@@ -326,8 +367,7 @@ MHD_EXTERN_
MHD_FN_PAR_NONNULL_ (1) const struct MHD_UploadAction *
MHD_upload_action_continue (struct MHD_Request *request)
{
- struct MHD_UploadAction *const restrict upl_act =
- &(request->app_act.upl_act);
+ struct MHD_UploadAction *const upl_act = mhd_REQ_GET_ACT_UPLD (request);
if (mhd_UPLOAD_ACTION_NO_ACTION != upl_act->act)
return (const struct MHD_UploadAction *) NULL;
diff --git a/src/mhd2/conn_data_process.c b/src/mhd2/conn_data_process.c
@@ -55,20 +55,38 @@
#include "mhd_assert.h"
#include "mhd_unreachable.h"
+#include "mhd_constexpr.h"
+
+#include <string.h>
#include "mhd_daemon.h"
#include "mhd_connection.h"
#include "daemon_logger.h"
+#include "mhd_comm_layer_state.h"
+#ifdef MHD_SUPPORT_HTTPS
+# include "conn_tls_check.h"
+#endif /* MHD_SUPPORT_HTTPS */
+
#include "conn_data_recv.h"
#include "conn_data_send.h"
#include "stream_process_states.h"
+#include "mhd_comm_layer_state.h"
+
+mhd_static_inline enum mhd_CommLayerState
+process_conn_layer (struct MHD_Connection *restrict c)
+{
#ifdef MHD_SUPPORT_HTTPS
-# include "conn_tls_check.h"
+ if (mhd_C_HAS_TLS (c))
+ return mhd_conn_tls_check (c);
#endif /* MHD_SUPPORT_HTTPS */
+ return mhd_COMM_LAYER_OK;
+}
+
+
MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool
mhd_conn_process_recv_send_data (struct MHD_Connection *restrict c)
{
@@ -88,24 +106,18 @@ mhd_conn_process_recv_send_data (struct MHD_Connection *restrict c)
data_processed = true;
}
-#ifdef MHD_SUPPORT_HTTPS
- if (mhd_C_HAS_TLS (c))
+ switch (process_conn_layer (c))
{
- switch (mhd_conn_tls_check (c))
- {
- case mhd_CONN_TLS_CHECK_OK:
- break; /* Process HTTP data */
- case mhd_CONN_TLS_CHECK_HANDSHAKING:
- return true; /* TLS is not yet ready */
- case mhd_CONN_TLS_CHECK_BROKEN:
- return false; /* Connection is broken */
- default:
- mhd_assert (0 && "Impossible value");
- mhd_UNREACHABLE ();
- break;
- }
+ case mhd_COMM_LAYER_OK:
+ break; /* Connected, the data */
+ case mhd_COMM_LAYER_PROCESSING:
+ return true; /* Not yet fully connected, too early for the data */
+ case mhd_COMM_LAYER_BROKEN:
+ return false; /* Connection is broken */
+ default:
+ mhd_UNREACHABLE ();
+ return false;
}
-#endif /* MHD_SUPPORT_HTTPS */
/* The "send-ready" state is known if system polling call is edge-triggered
(it always checks for both send- and recv-ready) or if connection needs
@@ -128,9 +140,12 @@ mhd_conn_process_recv_send_data (struct MHD_Connection *restrict c)
if (use_recv)
{
mhd_conn_data_recv (c, has_sock_err);
- if (! mhd_conn_process_data (c))
- return false;
- data_processed = true;
+ if (! mhd_C_IS_HTTP2 (c))
+ {
+ if (! mhd_conn_process_data (c))
+ return false;
+ data_processed = true;
+ }
}
}
@@ -146,21 +161,30 @@ mhd_conn_process_recv_send_data (struct MHD_Connection *restrict c)
buffers should have some space as sending was performed before receiving
or has not been performed yet. */
use_send = (0 != (mhd_SOCKET_NET_STATE_SEND_READY & c->sk.ready));
- use_send = use_send ||
- (data_processed && (! send_ready_state_known)
- && c->sk.props.is_nonblck);
- use_send = use_send ||
- (has_sock_err && c->sk.props.is_nonblck);
+ if (! mhd_C_IS_HTTP2 (c))
+ {
+ use_send = use_send ||
+ (data_processed && (! send_ready_state_known)
+ && c->sk.props.is_nonblck);
+ use_send = use_send ||
+ (has_sock_err && c->sk.props.is_nonblck);
+ }
if (use_send)
{
mhd_conn_data_send (c);
- if (! mhd_conn_process_data (c))
- return false;
- data_processed = true;
+ if (! mhd_C_IS_HTTP2 (c))
+ {
+ if (! mhd_conn_process_data (c))
+ return false;
+ data_processed = true;
+ }
}
}
- if (! data_processed)
+
+ if (! data_processed ||
+ mhd_C_IS_HTTP2 (c))
return mhd_conn_process_data (c);
+
return true;
}
diff --git a/src/mhd2/conn_data_send.c b/src/mhd2/conn_data_send.c
@@ -107,6 +107,21 @@ mhd_conn_data_send (struct MHD_Connection *restrict c)
res = mhd_SOCKET_ERR_INTERNAL; /* Mute compiler warning */
mhd_assert (mhd_SOCKET_ERR_INTERNAL == res); /* Mute analyser warning */
+#ifdef MHD_SUPPORT_HTTP2
+ if (mhd_C_IS_HTTP2 (c))
+ {
+ res = mhd_send_data (c,
+ c->write_buffer_append_offset
+ - c->write_buffer_send_offset,
+ c->write_buffer
+ + c->write_buffer_send_offset,
+ true,
+ &sent);
+ if (mhd_SOCKET_ERR_NO_ERROR == res)
+ c->write_buffer_send_offset += sent;
+ }
+ else
+#endif /* MHD_SUPPORT_HTTP2 */
switch (c->stage)
{
case mhd_HTTP_STAGE_CONTINUE_SENDING:
diff --git a/src/mhd2/conn_tls_check.c b/src/mhd2/conn_tls_check.c
@@ -62,7 +62,7 @@
#include "stream_process_states.h"
-MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ enum mhd_ConnTlsCheckResult
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ enum mhd_CommLayerState
mhd_conn_tls_check (struct MHD_Connection *restrict c)
{
mhd_assert (mhd_C_HAS_TLS (c));
@@ -72,7 +72,7 @@ mhd_conn_tls_check (struct MHD_Connection *restrict c)
(mhd_CONN_STATE_TLS_CONNECTED == c->conn_state));
if (mhd_CONN_STATE_TLS_CONNECTED == c->conn_state)
- return mhd_CONN_TLS_CHECK_OK; /* TLS is already connected */
+ return mhd_COMM_LAYER_OK; /* TLS is already connected */
if (0 != (mhd_SOCKET_NET_STATE_ERROR_READY & c->sk.ready))
{
@@ -80,13 +80,13 @@ mhd_conn_tls_check (struct MHD_Connection *restrict c)
if (mhd_SOCKET_ERR_NO_ERROR == c->sk.state.discnt_err)
c->sk.state.discnt_err = mhd_socket_error_get_from_socket (c->sk.fd);
mhd_conn_start_closing_skt_err (c);
- return mhd_CONN_TLS_CHECK_BROKEN;
+ return mhd_COMM_LAYER_BROKEN;
}
/* Check whether the socket is ready for the required send/recv operation */
if (0 == (((mhd_CONN_FLAG_RECV | mhd_CONN_FLAG_SEND)
& ((unsigned int) c->conn_state)
& ((unsigned int) c->sk.ready))))
- return mhd_CONN_TLS_CHECK_HANDSHAKING;
+ return mhd_COMM_LAYER_PROCESSING;
switch (mhd_tls_conn_handshake (c->tls))
{
@@ -107,7 +107,7 @@ mhd_conn_tls_check (struct MHD_Connection *restrict c)
Some early application-level data could be processing in this round. */
mhd_conn_event_loop_state_update (c);
- return mhd_CONN_TLS_CHECK_OK;
+ return mhd_COMM_LAYER_OK;
break;
case mhd_TLS_PROCED_RECV_MORE_NEEDED:
c->sk.ready = (enum mhd_SocketNetState) /* Clear 'recv-ready' */
@@ -138,14 +138,14 @@ mhd_conn_tls_check (struct MHD_Connection *restrict c)
"Failed to perform TLS handshake on the new connection");
c->sk.state.discnt_err = mhd_SOCKET_ERR_TLS;
mhd_conn_start_closing_skt_err (c);
- return mhd_CONN_TLS_CHECK_BROKEN;
+ return mhd_COMM_LAYER_BROKEN;
break;
default:
mhd_assert (0 && "Should be unreachable");
mhd_UNREACHABLE ();
- return mhd_CONN_TLS_CHECK_BROKEN;
+ return mhd_COMM_LAYER_BROKEN;
}
mhd_conn_mark_ready_update (c);
- return mhd_CONN_TLS_CHECK_HANDSHAKING;
+ return mhd_COMM_LAYER_PROCESSING;
}
diff --git a/src/mhd2/conn_tls_check.h b/src/mhd2/conn_tls_check.h
@@ -49,40 +49,19 @@
#include "sys_bool_type.h"
-struct MHD_Connection; /* forward declaration */
-
-/**
- * The results of connection TLS checking
- */
-enum mhd_ConnTlsCheckResult
-{
- /**
- * The TLS layer is connected, the communication over TLS can be performed
- */
- mhd_CONN_TLS_CHECK_OK = 0
- ,
- /**
- * The TLS layer connection is in progress.
- * Communication over TLS is not possible yet.
- */
- mhd_CONN_TLS_CHECK_HANDSHAKING
- ,
- /**
- * The connection is broken and must be closed
- */
- mhd_CONN_TLS_CHECK_BROKEN
-};
+#include "mhd_comm_layer_state.h"
+struct MHD_Connection; /* forward declaration */
/**
* Check connection TLS status, perform TLS (re-)handshake if necessary,
* update connection's recv()/send() event loop state and connection active
* state if network operation has been performed.
* @param c the connection to process
- * @return #mhd_CONN_TLS_CHECK_OK if the connection can be used,
- * other enum mhd_ConnTlsCheckResult values otherwise
+ * @return #mhd_COMM_LAYER_OK if the connection can be used,
+ * other enum mhd_CommLayerState values otherwise
*/
-MHD_INTERNAL enum mhd_ConnTlsCheckResult
+MHD_INTERNAL enum mhd_CommLayerState
mhd_conn_tls_check (struct MHD_Connection *restrict c)
MHD_FN_PAR_NONNULL_ALL_;
diff --git a/src/mhd2/daemon_add_conn.c b/src/mhd2/daemon_add_conn.c
@@ -89,6 +89,9 @@
#include "response_destroy.h"
#include "conn_mark_ready.h"
+#ifdef MHD_SUPPORT_HTTP2
+# include "h2/h2_comm.h"
+#endif /* MHD_SUPPORT_HTTP2 */
#ifdef MHD_SUPPORT_HTTPS
# include "mhd_tls_funcs.h"
#endif
@@ -97,6 +100,18 @@
#include "daemon_add_conn.h"
+#ifdef MHD_SUPPORT_HTTP2
+static void
+connection_set_http_layer_init_state (struct MHD_Connection *restrict c)
+{
+ c->h_layer.state = mhd_HTTP_LAYER_PREFACE;
+ c->h_layer.fam = mhd_HTTP_VER_FAM_NOT_SET;
+}
+
+
+#else /* ! MHD_SUPPORT_HTTP2 */
+# define connection_set_http_layer_init_state(c) ((void) 0)
+#endif /* ! MHD_SUPPORT_HTTP2 */
/**
* Set initial internal states for the connection to start reading and
@@ -251,6 +266,10 @@ new_connection_prepare_ (struct MHD_Daemon *restrict daemon,
# endif
#endif
+#ifdef MHD_SUPPORT_HTTP2
+ mhd_h2_blank_init (c);
+#endif /* MHD_SUPPORT_HTTP2 */
+
if (! external_add)
{
c->sk.state.corked = mhd_T_NO;
@@ -398,6 +417,7 @@ new_connection_process_inner (struct MHD_Daemon *restrict daemon,
mhd_DLINKEDL_INS_FIRST_D (&(daemon->conns.def_timeout), \
connection, by_timeout);
+ connection_set_http_layer_init_state (connection);
connection_set_initial_state (connection);
notify_app_conn (daemon, connection, false);
diff --git a/src/mhd2/daemon_start.c b/src/mhd2/daemon_start.c
@@ -153,6 +153,13 @@ daemon_set_basic_settings (struct MHD_Daemon *restrict d,
{
static const uint_fast64_t max_timeout_ms_value =
((uint_fast64_t) ~((uint_fast64_t) 0)) / 8;
+
+#ifdef MHD_SUPPORT_HTTP2
+ // TODO: make it configurable
+ d->http_cfg.http1x = true;
+ d->http_cfg.http2 = true;
+#endif /* MHD_SUPPORT_HTTP2 */
+
d->req_cfg.strictness = s->protocol_strict_level.v_sl;
#ifdef MHD_SUPPORT_COOKIES
diff --git a/src/mhd2/events_process.c b/src/mhd2/events_process.c
@@ -86,6 +86,10 @@
# include "upgrade_proc.h"
#endif /* MHD_SUPPORT_UPGRADE */
+#ifdef MHD_SUPPORT_HTTP2
+# include "h2/h2_comm.h"
+#endif
+
#include "mhd_public_api.h"
#ifdef mhd_DEBUG_POLLING_FDS
@@ -566,7 +570,13 @@ mhd_daemon_close_all_conns (struct MHD_Daemon *d)
else /* Combined with the next 'if' */
#endif
if (1)
+ {
+#ifdef MHD_SUPPORT_HTTP2
+ if (mhd_C_IS_HTTP2 (c))
+ mhd_h2_conn_deinit (c);
+#endif /* MHD_SUPPORT_HTTP2 */
mhd_conn_start_closing_d_shutdown (c);
+ }
mhd_conn_pre_clean (c);
mhd_conn_remove_from_daemon (c);
mhd_conn_close_final (c);
@@ -1055,7 +1065,8 @@ select_update_statuses_from_fdsets_and_resume_conn (struct MHD_Daemon *d,
}
#ifndef MHD_FAVOR_SMALL_CODE
- mhd_assert ((0 == num_events) || resuming_conn);
+ // TODO: recheck functionality with HTTP/2
+ // mhd_assert ((0 == num_events) || resuming_conn);
#endif /* MHD_FAVOR_SMALL_CODE */
return true;
}
diff --git a/src/mhd2/h2/h2_action.c b/src/mhd2/h2/h2_action.c
@@ -0,0 +1,93 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
+/*
+ This file is part of GNU libmicrohttpd.
+ Copyright (C) 2025 Evgeny Grin (Karlson2k)
+
+ GNU libmicrohttpd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ GNU libmicrohttpd is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ Alternatively, you can redistribute GNU libmicrohttpd and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of
+ the License, or (at your option) any later version, together
+ with the eCos exception, as follows:
+
+ As a special exception, if other files instantiate templates or
+ use macros or inline functions from this file, or you compile this
+ file and link it with other works to produce a work based on this
+ file, this file does not by itself cause the resulting work to be
+ covered by the GNU General Public License. However the source code
+ for this file must still be made available in accordance with
+ section (3) of the GNU General Public License v2.
+
+ This exception does not invalidate any other reasons why a work
+ based on this file might be covered by the GNU General Public
+ License.
+
+ You should have received copies of the GNU Lesser General Public
+ License and the GNU General Public License along with this library;
+ if not, see <https://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file src/mhd2/h2/h2_action.c
+ * @brief Implementation of HTTP/2 action creators
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+
+#include "mhd_sys_options.h"
+
+#include "mhd_assert.h"
+
+#include "mhd_action.h"
+#include "mhd_response.h"
+
+#include "h2_req_data.h"
+
+#include "h2_action.h"
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool
+mhd_h2_act_is_resp_h2_compatible (const struct mhd_H2RequestData *restrict req,
+ const struct MHD_Response *restrict response)
+{
+ mhd_assert (req->is_http2);
+
+ // TODO: move new two checks to the unified (HTTP/1.x and HTTP/2) code
+ if ((mhd_HTTP_METHOD_CONNECT == req->method) &&
+ (200 <= response->sc) && (299 >= response->sc))
+ return false;
+
+ if ((MHD_NO != response->cfg.head_only) &&
+ (200 <= response->sc) &&
+ (MHD_HTTP_STATUS_NOT_MODIFIED != response->sc) &&
+ (MHD_HTTP_STATUS_NO_CONTENT != response->sc) &&
+ (mhd_HTTP_METHOD_HEAD != req->method))
+ return false;
+
+ // TODO: support digest auth with HTTP/2
+ if (mhd_RESP_HAS_AUTH_DIGEST (response))
+ return false;
+
+ // TODO: implement callback for the next response
+ if (MHD_HTTP_STATUS_OK > response->sc)
+ return false;
+
+ // TODO: work with all types
+ if ((mhd_RESPONSE_CONTENT_DATA_BUFFER != response->cntn_dtype)
+ && (mhd_RESPONSE_CONTENT_DATA_FILE != response->cntn_dtype)
+ && (mhd_RESPONSE_CONTENT_DATA_IOVEC != response->cntn_dtype))
+ return false;
+ if ((mhd_RESPONSE_CONTENT_DATA_FILE == response->cntn_dtype) &&
+ response->cntn.file.is_pipe)
+ return false;
+
+ return true;
+}
diff --git a/src/mhd2/h2/h2_action.h b/src/mhd2/h2/h2_action.h
@@ -0,0 +1,60 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
+/*
+ This file is part of GNU libmicrohttpd.
+ Copyright (C) 2025 Evgeny Grin (Karlson2k)
+
+ GNU libmicrohttpd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ GNU libmicrohttpd is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ Alternatively, you can redistribute GNU libmicrohttpd and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of
+ the License, or (at your option) any later version, together
+ with the eCos exception, as follows:
+
+ As a special exception, if other files instantiate templates or
+ use macros or inline functions from this file, or you compile this
+ file and link it with other works to produce a work based on this
+ file, this file does not by itself cause the resulting work to be
+ covered by the GNU General Public License. However the source code
+ for this file must still be made available in accordance with
+ section (3) of the GNU General Public License v2.
+
+ This exception does not invalidate any other reasons why a work
+ based on this file might be covered by the GNU General Public
+ License.
+
+ You should have received copies of the GNU Lesser General Public
+ License and the GNU General Public License along with this library;
+ if not, see <https://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file src/mhd2/h2/h2_action.h
+ * @brief Declarations of HTTP/2 action creators
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+#ifndef MHD_H2_ACTION_H
+#define MHD_H2_ACTION_H 1
+
+#include "mhd_sys_options.h"
+
+#include "sys_bool_type.h"
+
+struct mhd_H2RequestData; /* Forward declaration */
+struct MHD_Response; /* Forward declaration */
+
+MHD_INTERNAL bool
+mhd_h2_act_is_resp_h2_compatible (const struct mhd_H2RequestData *restrict req,
+ const struct MHD_Response *restrict response)
+MHD_FN_PAR_NONNULL_ALL_;
+
+#endif /* ! MHD_H2_ACTION_H */
diff --git a/src/mhd2/h2/h2_app_cb.c b/src/mhd2/h2/h2_app_cb.c
@@ -0,0 +1,251 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
+/*
+ This file is part of GNU libmicrohttpd.
+ Copyright (C) 2025 Evgeny Grin (Karlson2k)
+
+ GNU libmicrohttpd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ GNU libmicrohttpd is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ Alternatively, you can redistribute GNU libmicrohttpd and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of
+ the License, or (at your option) any later version, together
+ with the eCos exception, as follows:
+
+ As a special exception, if other files instantiate templates or
+ use macros or inline functions from this file, or you compile this
+ file and link it with other works to produce a work based on this
+ file, this file does not by itself cause the resulting work to be
+ covered by the GNU General Public License. However the source code
+ for this file must still be made available in accordance with
+ section (3) of the GNU General Public License v2.
+
+ This exception does not invalidate any other reasons why a work
+ based on this file might be covered by the GNU General Public
+ License.
+
+ You should have received copies of the GNU Lesser General Public
+ License and the GNU General Public License along with this library;
+ if not, see <https://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file src/mhd2/h2/h2_app_cb.c
+ * @brief Implementation of HTTP/2 functions for calling application callbacks
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+
+#include "mhd_sys_options.h"
+
+#include "sys_bool_type.h"
+#include "sys_base_types.h"
+
+#include "mhd_assert.h"
+#include "mhd_unreachable.h"
+
+#include "mhd_str_types.h"
+
+#include "mhd_daemon.h"
+#include "mhd_connection.h"
+#include "h2_conn_data.h"
+#include "h2_stream_data.h"
+
+#include "mhd_panic.h"
+#include "daemon_logger.h"
+
+#include "response_destroy.h"
+
+#include "h2_req_items_funcs.h"
+#include "h2_conn_streams.h"
+
+#include "h2_app_cb.h"
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool
+mhd_h2_stream_cb_early_uri (struct mhd_H2Stream *restrict s)
+{
+ mhd_assert (mhd_H2_REQ_STAGE_HEADERS_DECODING == s->req.stage);
+ mhd_assert (mhd_HTTP_METHOD_NO_METHOD != s->req.method);
+ mhd_assert (mhd_h2_items_debug_get_streamid (s->c->h2.mem.req_ib)
+ == s->stream_id);
+ mhd_assert (mhd_H2_REQ_ITEM_POS_INVALID != s->req.pos_path);
+
+ if (NULL != s->c->daemon->req_cfg.uri_cb.cb)
+ {
+ struct MHD_EarlyUriCbData req_data;
+ bool res;
+
+ req_data.request = (struct MHD_Request *) (void*) &(s->req);
+ res = mhd_h2_items_get_item_value (s->c->h2.mem.req_ib,
+ s->req.pos_path,
+ &(req_data.full_uri));
+ mhd_assert (res);
+ (void) res;
+
+ if (s->c->h2.state.top_proc_stream_id < s->stream_id)
+ s->c->h2.state.top_proc_stream_id = s->stream_id;
+ s->req.app_seen = true;
+
+ s->c->daemon->req_cfg.uri_cb.cb (s->c->daemon->req_cfg.uri_cb.cls,
+ &req_data,
+ &(s->req.app_context));
+ }
+
+ return true;
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool
+mhd_h2_stream_cb_request (struct mhd_H2Stream *restrict s)
+{
+ struct MHD_Connection *restrict c = s->c;
+ struct MHD_Daemon *restrict d = c->daemon;
+ struct MHD_String path;
+ const struct MHD_Action *a;
+
+ mhd_assert (mhd_C_IS_HTTP2 (c));
+ mhd_assert (mhd_H2_REQ_STAGE_HEADERS_PROCESSING == s->req.stage);
+ mhd_assert (mhd_HTTP_METHOD_NO_METHOD != s->req.method);
+ mhd_assert (mhd_h2_items_debug_get_streamid (s->c->h2.mem.req_ib)
+ == s->stream_id);
+ mhd_assert (mhd_H2_REQ_ITEM_POS_INVALID != s->req.pos_path);
+
+ mhd_assert (NULL == s->rpl.response);
+
+ if (mhd_ACTION_NO_ACTION != s->req.app_act.head_act.act)
+ MHD_PANIC ("MHD_Action has been set already");
+
+ if (1)
+ {
+ bool res =
+ mhd_h2_items_get_item_value (s->c->h2.mem.req_ib,
+ s->req.pos_path,
+ &(path));
+ mhd_assert (res);
+ (void) res;
+ }
+
+ if (s->c->h2.state.top_proc_stream_id < s->stream_id)
+ s->c->h2.state.top_proc_stream_id = s->stream_id;
+ s->req.app_seen = true;
+
+ a = d->req_cfg.cb (d->req_cfg.cb_cls,
+ (struct MHD_Request *) (void*) &(s->req),
+ &path,
+ (enum MHD_HTTP_Method) s->req.method,
+ s->req.cntn_size);
+
+ if ((NULL != a)
+ && (((&(s->req.app_act.head_act) != a))
+ || ! mhd_ACTION_IS_VALID (s->req.app_act.head_act.act)))
+ {
+ mhd_LOG_MSG (d, MHD_SC_ACTION_INVALID, \
+ "Provided action is not a correct action generated " \
+ "for the current request.");
+ /* Perform cleanup of the created but now unused action */
+ switch (s->req.app_act.head_act.act)
+ {
+ case mhd_ACTION_RESPONSE:
+ mhd_assert (NULL != s->req.app_act.head_act.data.response);
+ mhd_response_dec_use_count (s->req.app_act.head_act.data.response);
+ break;
+ case mhd_ACTION_UPLOAD:
+ case mhd_ACTION_SUSPEND:
+ /* No cleanup needed */
+ break;
+#ifdef MHD_SUPPORT_POST_PARSER
+ case mhd_ACTION_POST_PARSE:
+ /* No cleanup needed */
+ break;
+#endif /* MHD_SUPPORT_POST_PARSER */
+#ifdef MHD_SUPPORT_UPGRADE
+ case mhd_ACTION_UPGRADE:
+ /* No cleanup needed */
+ break;
+#endif /* MHD_SUPPORT_UPGRADE */
+ case mhd_ACTION_ABORT:
+ mhd_UNREACHABLE ();
+ break;
+ case mhd_ACTION_NO_ACTION:
+ default:
+ break;
+ }
+ a = NULL;
+ }
+ if (NULL == a)
+ s->req.app_act.head_act.act = mhd_ACTION_ABORT;
+
+ switch (s->req.app_act.head_act.act)
+ {
+ case mhd_ACTION_RESPONSE:
+ s->rpl.response = s->req.app_act.head_act.data.response;
+ return true;
+#if 0
+ case mhd_ACTION_UPLOAD:
+ if (0 != s->req.cntn_size)
+ {
+ if (! check_and_alloc_buf_for_upload_processing (c))
+ return true;
+ c->stage = mhd_HTTP_STAGE_BODY_RECEIVING;
+ return (0 != c->read_buffer_offset);
+ }
+ c->stage = mhd_HTTP_STAGE_FULL_REQ_RECEIVED;
+ return true;
+#ifdef MHD_SUPPORT_POST_PARSER
+ case mhd_ACTION_POST_PARSE:
+ if (0 == s->req.cntn.cntn_size)
+ {
+ s->req.u_proc.post.parse_result = MHD_POST_PARSE_RES_REQUEST_EMPTY;
+ c->stage = mhd_HTTP_STAGE_FULL_REQ_RECEIVED;
+ return true;
+ }
+ if (! mhd_stream_prepare_for_post_parse (c))
+ {
+ mhd_assert (mhd_HTTP_STAGE_FOOTERS_RECEIVED < c->stage);
+ return true;
+ }
+ if (need_100_continue (c))
+ {
+ c->stage = mhd_HTTP_STAGE_CONTINUE_SENDING;
+ return true;
+ }
+ c->stage = mhd_HTTP_STAGE_BODY_RECEIVING;
+ return true;
+#endif /* MHD_SUPPORT_POST_PARSER */
+ case mhd_ACTION_SUSPEND:
+ c->suspended = true;
+#ifdef mhd_DEBUG_SUSPEND_RESUME
+ fprintf (stderr,
+ "%%%%%% Suspending connection, FD: %2llu\n",
+ (unsigned long long) c->sk.fd);
+#endif /* mhd_DEBUG_SUSPEND_RESUME */
+ s->req.app_act.head_act.act = mhd_ACTION_NO_ACTION;
+ return false;
+#ifdef MHD_SUPPORT_UPGRADE
+ case mhd_ACTION_UPGRADE:
+ mhd_assert (0 == s->req.cntn.cntn_size);
+ c->stage = mhd_HTTP_STAGE_UPGRADE_HEADERS_SENDING;
+ return false;
+#endif /* MHD_SUPPORT_UPGRADE */
+ case mhd_ACTION_ABORT:
+ mhd_conn_start_closing_app_abort (c);
+ return true;
+ case mhd_ACTION_NO_ACTION:
+ default:
+ mhd_assert (0 && "Impossible value");
+ mhd_UNREACHABLE ();
+ break;
+#endif
+ }
+
+ return mhd_h2_stream_abort (s,
+ mhd_H2_ERR_INTERNAL_ERROR);
+}
diff --git a/src/mhd2/h2/h2_app_cb.h b/src/mhd2/h2/h2_app_cb.h
@@ -0,0 +1,63 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
+/*
+ This file is part of GNU libmicrohttpd.
+ Copyright (C) 2025 Evgeny Grin (Karlson2k)
+
+ GNU libmicrohttpd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ GNU libmicrohttpd is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ Alternatively, you can redistribute GNU libmicrohttpd and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of
+ the License, or (at your option) any later version, together
+ with the eCos exception, as follows:
+
+ As a special exception, if other files instantiate templates or
+ use macros or inline functions from this file, or you compile this
+ file and link it with other works to produce a work based on this
+ file, this file does not by itself cause the resulting work to be
+ covered by the GNU General Public License. However the source code
+ for this file must still be made available in accordance with
+ section (3) of the GNU General Public License v2.
+
+ This exception does not invalidate any other reasons why a work
+ based on this file might be covered by the GNU General Public
+ License.
+
+ You should have received copies of the GNU Lesser General Public
+ License and the GNU General Public License along with this library;
+ if not, see <https://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file src/mhd2/h2/h2_app_cb.h
+ * @brief Declarations of HTTP/2 functions for calling application callbacks
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+#ifndef MHD_H2_APP_CB_H
+#define MHD_H2_APP_CB_H 1
+
+#include "mhd_sys_options.h"
+
+#include "sys_bool_type.h"
+
+struct MHD_Connection; /* forward declaration */
+struct mhd_H2Stream; /* forward declaration */
+
+MHD_INTERNAL bool
+mhd_h2_stream_cb_early_uri (struct mhd_H2Stream *restrict s)
+MHD_FN_PAR_NONNULL_ALL_;
+
+MHD_INTERNAL bool
+mhd_h2_stream_cb_request (struct mhd_H2Stream *restrict s)
+MHD_FN_PAR_NONNULL_ALL_;
+
+#endif /* ! MHD_H2_APP_CB_H */
diff --git a/src/mhd2/h2/h2_bit_masks.h b/src/mhd2/h2/h2_bit_masks.h
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
+/*
+ This file is part of GNU libmicrohttpd.
+ Copyright (C) 2025 Evgeny Grin (Karlson2k)
+
+ GNU libmicrohttpd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ GNU libmicrohttpd is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ Alternatively, you can redistribute GNU libmicrohttpd and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of
+ the License, or (at your option) any later version, together
+ with the eCos exception, as follows:
+
+ As a special exception, if other files instantiate templates or
+ use macros or inline functions from this file, or you compile this
+ file and link it with other works to produce a work based on this
+ file, this file does not by itself cause the resulting work to be
+ covered by the GNU General Public License. However the source code
+ for this file must still be made available in accordance with
+ section (3) of the GNU General Public License v2.
+
+ This exception does not invalidate any other reasons why a work
+ based on this file might be covered by the GNU General Public
+ License.
+
+ You should have received copies of the GNU Lesser General Public
+ License and the GNU General Public License along with this library;
+ if not, see <https://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file src/mhd2/h2/h2_bit_masks.h
+ * @brief Various bitmasks useful with HTTP/2
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+#ifndef MHD_H2_BIT_MASKS_H
+#define MHD_H2_BIT_MASKS_H 1
+
+#include "mhd_sys_options.h"
+
+
+/**
+ * Mask for 31-bit unsigned values
+ */
+#define mhd_MASK_31BITS (0x7FFFFFFFu)
+/**
+ * Mask for 24-bit unsigned value
+ */
+#define mhd_MASK_24BITS (0xFFFFFFu)
+
+/**
+ * Mask for HTTP/2 Stream Identifier field
+ */
+#define mhd_H2_STREAM_ID_MASK mhd_MASK_31BITS
+/**
+ * Mask for HTTP/2 frame length field
+ */
+#define mhd_H2_FR_LENGTH_MASK mhd_MASK_24BITS
+
+
+#endif /* ! MHD_H2_BIT_MASKS_H */
diff --git a/src/mhd2/h2/h2_comm.c b/src/mhd2/h2/h2_comm.c
@@ -0,0 +1,606 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
+/*
+ This file is part of GNU libmicrohttpd.
+ Copyright (C) 2025 Evgeny Grin (Karlson2k)
+
+ GNU libmicrohttpd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ GNU libmicrohttpd is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ Alternatively, you can redistribute GNU libmicrohttpd and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of
+ the License, or (at your option) any later version, together
+ with the eCos exception, as follows:
+
+ As a special exception, if other files instantiate templates or
+ use macros or inline functions from this file, or you compile this
+ file and link it with other works to produce a work based on this
+ file, this file does not by itself cause the resulting work to be
+ covered by the GNU General Public License. However the source code
+ for this file must still be made available in accordance with
+ section (3) of the GNU General Public License v2.
+
+ This exception does not invalidate any other reasons why a work
+ based on this file might be covered by the GNU General Public
+ License.
+
+ You should have received copies of the GNU Lesser General Public
+ License and the GNU General Public License along with this library;
+ if not, see <https://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file src/mhd2/h2/h2_comm.c
+ * @brief Implementation of HTTP/2 connection communication functions
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+#include "mhd_sys_options.h"
+
+#include "sys_bool_type.h"
+#include "sys_base_types.h"
+
+#include <string.h>
+
+#include "mhd_unreachable.h"
+#include "mhd_constexpr.h"
+#include "mhd_assert.h"
+
+#include "mhd_connection.h"
+#include "mhd_daemon.h"
+
+#include "mempool_funcs.h"
+
+#ifdef MHD_SUPPORT_HTTPS
+# include "mhd_tls_funcs.h"
+#endif
+
+#include "respond_with_error.h"
+#include "stream_funcs.h"
+#include "daemon_logger.h"
+
+#include "h2_req_items_funcs.h"
+#include "h2_frame_codec.h"
+#include "h2_proc_settings.h"
+#include "h2_proc_conn.h"
+#include "h2_proc_in.h"
+#include "h2_conn_streams.h"
+#include "hpack/mhd_hpack_codec.h"
+
+#include "h2_comm.h"
+
+#ifndef HAVE_NULL_PTR_ALL_ZEROS
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void
+mhd_h2_blank_init (struct MHD_Connection *restrict c)
+{
+ c->write_buffer = NULL;
+}
+
+
+#endif /* HAVE_NULL_PTR_ALL_ZEROS */
+
+/**
+ * HTTP/2 connection preface
+ *
+ * Extracted from RFC 9113, Section 3.4
+ */
+mhd_constexpr uint8_t mhd_h2_preface[mhd_H2_PREFACE_LEN] = {
+ 0x50u, 0x52u, 0x49u, 0x20u, 0x2Au, 0x20u, 0x48u, 0x54u, 0x54u, 0x50u,
+ 0x2Fu, 0x32u, 0x2Eu, 0x30u, 0x0Du, 0x0Au, 0x0Du, 0x0Au, 0x53u, 0x4Du,
+ 0x0Du, 0x0Au, 0x0Du, 0x0Au
+};
+
+
+/**
+ * Result of HTTP/2 preface check
+ */
+enum MHD_FIXED_ENUM_ mhd_H2PrefaceCheckResult
+{
+ /**
+ * Received data matches HTTP/2 preface
+ */
+ mhd_H2_PREFACE_CHECK_IS_HTTP2
+ ,
+ /**
+ * Received data does not match HTTP/2 preface
+ */
+ mhd_H2_PREFACE_CHECK_IS_NOT_HTTP2
+ ,
+ /**
+ * Not enough data has been received
+ */
+ mhd_H2_PREFACE_CHECK_NEED_MORE_DATA
+};
+
+/**
+ * Check HTTP/2 connection preface
+ * @param c the connection to process
+ * @return enum mhd_H2PrefaceCheckResult status code
+ */
+mhd_static_inline MHD_FN_PAR_NONNULL_ALL_ enum mhd_H2PrefaceCheckResult
+mhd_h2_check_preface (struct MHD_Connection *restrict c)
+{
+ bool have_enough_data;
+
+ mhd_assert (mhd_HTTP_LAYER_PREFACE == c->h_layer.state);
+ mhd_assert (NULL != c->read_buffer);
+ mhd_assert (mhd_H2_PREFACE_LEN <= c->read_buffer_size);
+
+ if (0u == c->read_buffer_offset)
+ return mhd_H2_PREFACE_CHECK_NEED_MORE_DATA;
+
+ have_enough_data = (mhd_H2_PREFACE_LEN <= c->read_buffer_offset);
+
+ if (0 !=
+ memcmp (mhd_h2_preface,
+ c->read_buffer,
+ have_enough_data ? mhd_H2_PREFACE_LEN : c->read_buffer_offset))
+ return mhd_H2_PREFACE_CHECK_IS_NOT_HTTP2;
+
+ return (have_enough_data ?
+ mhd_H2_PREFACE_CHECK_IS_HTTP2 : mhd_H2_PREFACE_CHECK_NEED_MORE_DATA);
+}
+
+
+#define ERR_RSP_H2_NOT_SUPPORTED \
+ "<html><head><title>HTTP/2 is not supported</title></head>" \
+ "<body>HTTP/2 protocol is not supported.</body></html>"
+
+#define ERR_RSP_H2_WITH_ALPN_HTTP1 \
+ "<html><head><title>HTTP/2 without matching ALPN</title></head>" \
+ "<body>ALPN selected HTTP/1.x protocol, HTTP/2 cannot be used " \
+ "over TLS if ALPN selected another application protocol.</body></html>"
+
+#define ERR_RSP_H2_WITHOUT_ALPN \
+ "<html><head><title>HTTP/2 without ALPN on HTTPS</title></head>" \
+ "<body>HTTP/2 cannot be used over TLS without ALPN.</body></html>"
+
+/**
+ * Perform switching connection to HTTP/2 mode
+ * @param c the connection to switch
+ * @return 'true' if switched successfully,
+ * 'false' if connection is broken and should be closed
+ */
+static MHD_FN_PAR_NONNULL_ALL_ bool
+h2_switch_to_h2 (struct MHD_Connection *restrict c)
+{
+ size_t buff_size;
+ mhd_assert (mhd_HTTP_LAYER_PREFACE == c->h_layer.state);
+ mhd_assert (mhd_H2_PREFACE_LEN <= c->read_buffer_offset);
+ mhd_assert (mhd_HTTP_VER_FAM_NOT_SET == c->h_layer.fam);
+
+ mhd_DLINKEDL_INIT_LIST_D (&(c->h2.streams.active));
+ mhd_DLINKEDL_INIT_LIST_D (&(c->h2.streams.send_q));
+ c->h2.streams.num_streams = 0u;
+
+ c->h2.state.init.got_setns = false;
+ c->h2.state.init.sent_setns = false;
+
+ c->h2.state.sent_setns_noakc = 0u;
+
+ c->h2.state.send_window = mhd_H2_STNG_DEF_INIT_WIN_SIZE;
+ c->h2.state.recv_window = mhd_H2_STNG_DEF_INIT_WIN_SIZE;
+
+ c->h2.state.top_seen_stream_id = 0u;
+
+ // TODO: make some parameters configurable
+ c->h2.rcv_cfg.stream_init_win_sz = 0x7FFFFFFFu;
+ c->h2.rcv_cfg.conn_full_win_sz = 0x7FFFFFFFu;
+ c->h2.rcv_cfg.max_frame_size = mhd_H2_STNG_DEF_MAX_FRAME_SIZE;
+ c->h2.rcv_cfg.max_header_list = 16u * 1024u;
+ c->h2.rcv_cfg.max_concur_streams = 100u;
+
+ c->h2.peer.stream_init_win_sz = mhd_H2_STNG_DEF_INIT_WIN_SIZE;
+ c->h2.peer.max_frame_size = mhd_H2_STNG_DEF_MAX_FRAME_SIZE;
+ c->h2.peer.max_header_list = (uint_least32_t) (~((uint_least32_t) 0u));
+ c->h2.peer.max_concur_streams = (uint_least32_t) (~((uint_least32_t) 0u));
+
+ buff_size = mhd_pool_get_size (c->pool);
+ c->read_buffer =
+ (char *)
+ mhd_pool_reset (c->pool,
+ c->read_buffer,
+ c->read_buffer_offset,
+ buff_size);
+ mhd_assert (NULL != c->read_buffer);
+ c->read_buffer_size = buff_size;
+ c->h2.buff.r_cur_frame = mhd_H2_PREFACE_LEN;
+
+ c->h2.mem.send_pool =
+ mhd_pool_create (c->daemon->conns.cfg.mem_pool_size,
+ c->daemon->conns.cfg.mem_pool_zeroing);
+ if (NULL != c->h2.mem.send_pool)
+ {
+ buff_size = mhd_pool_get_size (c->h2.mem.send_pool);
+ c->write_buffer =
+ (char *)
+ mhd_pool_allocate (c->h2.mem.send_pool,
+ buff_size,
+ false);
+ mhd_assert (NULL != c->write_buffer);
+ c->write_buffer_size = buff_size;
+ c->write_buffer_append_offset = 0u;
+ c->write_buffer_send_offset = 0u;
+
+ if (mhd_hpack_dec_init (&(c->h2.hk_dec)))
+ {
+ if (mhd_hpack_enc_init (&(c->h2.hk_enc)))
+ {
+ // TODO: make the size configurable
+ c->h2.mem.req_ib = mhd_h2_items_block_create (16u * 1024);
+
+ if (NULL != c->h2.mem.req_ib)
+ {
+ c->h_layer.fam = mhd_HTTP_VER_FAM_2;
+ c->h_layer.state = mhd_HTTP_LAYER_CONNECTED;
+
+ return true; /* Success exit point */
+ }
+ /* Clean-up */
+ mhd_hpack_enc_deinit (&(c->h2.hk_enc));
+ }
+ mhd_hpack_dec_deinit (&(c->h2.hk_dec));
+ }
+ c->write_buffer = NULL;
+ mhd_pool_destroy (c->h2.mem.send_pool);
+ c->h2.mem.send_pool = NULL;
+ mhd_LOG_MSG (c->daemon, MHD_SC_H2_CONN_MEM_ALLOC_FAILURE, \
+ "Failed to allocate memory for the HTTP/2 "
+ "connection resources.");
+ }
+ else
+ mhd_LOG_MSG (c->daemon, MHD_SC_POOL_MEM_ALLOC_FAILURE, \
+ "Failed to allocate memory for the HTTP/2 send buffer.");
+
+ mhd_conn_start_closing_no_sys_res (c);
+ c->h_layer.fam = mhd_HTTP_VER_FAM_NOT_SET;
+ c->h_layer.state = mhd_HTTP_LAYER_BROKEN;
+ return false;
+}
+
+
+/**
+ * Perform de-initialisation of HTTP/2-specific data
+ * @param c the connection to de-initialise
+ */
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void
+mhd_h2_conn_deinit (struct MHD_Connection *restrict c)
+{
+ mhd_assert (mhd_C_IS_HTTP2 (c));
+ mhd_assert ((mhd_HTTP_LAYER_CONNECTED == c->h_layer.state) ||
+ (mhd_HTTP_LAYER_CLOSING == c->h_layer.state) ||
+ (mhd_HTTP_LAYER_BROKEN == c->h_layer.state));
+ mhd_assert (! c->h2.dbg.h2_deinited);
+
+ mhd_h2_conn_close_streams_all (c);
+
+ mhd_h2_items_block_destroy (c->h2.mem.req_ib);
+ c->h2.mem.req_ib = NULL;
+ mhd_hpack_enc_deinit (&(c->h2.hk_enc));
+ mhd_hpack_dec_deinit (&(c->h2.hk_dec));
+
+ mhd_assert (NULL != c->write_buffer);
+ mhd_assert (NULL != c->h2.mem.send_pool);
+ c->write_buffer_send_offset = 0u;
+ c->write_buffer_append_offset = 0u;
+ c->write_buffer_size = 0u;
+ c->write_buffer = NULL;
+ mhd_pool_destroy (c->h2.mem.send_pool);
+ c->h2.mem.send_pool = NULL;
+
+#ifndef NDEBUG
+ c->h2.dbg.h2_deinited = true;
+#endif /* ! NDEBUG */
+
+ if ((mhd_HTTP_LAYER_BROKEN == c->h_layer.state) ||
+ (mhd_SOCKET_ERR_NO_ERROR != c->sk.state.discnt_err) ||
+ (0 != (c->sk.ready & mhd_SOCKET_NET_STATE_ERROR_READY)))
+ mhd_conn_start_closing_h2_hard (c);
+ else
+ mhd_conn_start_closing_h2_soft (c);
+
+ if (mhd_HTTP_LAYER_CLOSED > c->h_layer.state)
+ c->h_layer.state = mhd_HTTP_LAYER_CLOSED;
+}
+
+
+/**
+ * Handle detection of HTTP/2 preface
+ * @param c the connection to process
+ * @return 'true' if connection should be continued to be processed as
+ * HTTP connection (possibly to send already scheduled error
+ * response),
+ * 'false' if connection is broken and should be closed
+ */
+static MHD_FN_PAR_NONNULL_ALL_ bool
+h2_handle_preface_found (struct MHD_Connection *restrict c)
+{
+ const bool allow_on_alpn_mismatch =
+ (c->daemon->req_cfg.strictness <= MHD_PSL_EXTRA_PERMISSIVE);
+ const bool allow_without_alpn =
+ (c->daemon->req_cfg.strictness <= MHD_PSL_PERMISSIVE);
+
+ mhd_assert (mhd_HTTP_LAYER_PREFACE == c->h_layer.state);
+ mhd_assert (mhd_H2_PREFACE_LEN <= c->read_buffer_offset);
+
+ if (! mhd_D_IS_HTTP2_ENABLED (c->daemon))
+ {
+ c->h_layer.state = mhd_HTTP_LAYER_CONNECTED;
+ c->h_layer.fam = mhd_HTTP_VER_FAM_NOT_2;
+ mhd_RESPOND_WITH_ERROR_STATIC (c,
+ MHD_HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED,
+ ERR_RSP_H2_NOT_SUPPORTED);
+ return true; /* Send error response */
+ }
+
+#ifdef MHD_SUPPORT_HTTPS
+ if (mhd_C_HAS_TLS (c))
+ {
+ switch (mhd_tls_conn_get_alpn_prot (c->tls))
+ {
+ case mhd_TLS_ALPN_PROT_HTTP1_0:
+ case mhd_TLS_ALPN_PROT_HTTP1_1:
+ if (! allow_on_alpn_mismatch)
+ {
+ c->h_layer.state = mhd_HTTP_LAYER_CONNECTED;
+ c->h_layer.fam = mhd_HTTP_VER_FAM_NOT_2;
+ mhd_RESPOND_WITH_ERROR_STATIC (c,
+ MHD_HTTP_STATUS_BAD_REQUEST,
+ ERR_RSP_H2_WITH_ALPN_HTTP1);
+ return true; /* Send error response */
+ }
+ break;
+ case mhd_TLS_ALPN_PROT_HTTP2:
+ break;
+ case mhd_TLS_ALPN_PROT_NOT_SELECTED:
+ case mhd_TLS_ALPN_PROT_ERROR:
+ if (! allow_without_alpn)
+ {
+ c->h_layer.state = mhd_HTTP_LAYER_CONNECTED;
+ c->h_layer.fam = mhd_HTTP_VER_FAM_NOT_2;
+ mhd_RESPOND_WITH_ERROR_STATIC (c,
+ MHD_HTTP_STATUS_BAD_REQUEST,
+ ERR_RSP_H2_WITHOUT_ALPN);
+ return true; /* Send error response */
+ }
+ break;
+ default:
+ mhd_UNREACHABLE ();
+ return false;
+ }
+ }
+#endif /* MHD_SUPPORT_HTTPS */
+ return h2_switch_to_h2 (c);
+}
+
+
+#define ERR_RSP_H2_REQUIRED \
+ "<html><head><title>HTTP/2 is required</title></head>" \
+ "<body>HTTP/2 protocol is required</body></html>"
+
+#define ERR_RSP_NOT_H2_WITH_ALPN_H2 \
+ "<html><head><title>ALPN selected HTTP/2</title></head>" \
+ "<body>ALPN selected HTTP/2 protocol, " \
+ "only HTTP/2 communication could be used.</body></html>"
+
+/**
+ * Handle absence of HTTP/2 preface
+ * @param c the connection to process
+ * @return 'true' if connection should be continued to be processed as
+ * HTTP connection (possibly to send already scheduled error
+ * response),
+ * 'false' if connection is broken and should be closed
+ */
+static MHD_FN_PAR_NONNULL_ALL_ bool
+h2_handle_preface_not_found (struct MHD_Connection *restrict c)
+{
+ const bool allow_on_alpn_mismatch =
+ (c->daemon->req_cfg.strictness <= MHD_PSL_EXTRA_PERMISSIVE);
+
+ mhd_assert (mhd_HTTP_LAYER_PREFACE == c->h_layer.state);
+ mhd_assert (mhd_H2_PREFACE_LEN <= c->read_buffer_offset);
+
+#ifdef MHD_SUPPORT_HTTPS
+ if (mhd_C_HAS_TLS (c))
+ {
+ switch (mhd_tls_conn_get_alpn_prot (c->tls))
+ {
+ case mhd_TLS_ALPN_PROT_HTTP1_0:
+ case mhd_TLS_ALPN_PROT_HTTP1_1:
+ case mhd_TLS_ALPN_PROT_NOT_SELECTED:
+ case mhd_TLS_ALPN_PROT_ERROR:
+ break;
+ case mhd_TLS_ALPN_PROT_HTTP2:
+ if (! allow_on_alpn_mismatch)
+ {
+ c->h_layer.state = mhd_HTTP_LAYER_BROKEN;
+ c->h_layer.fam = mhd_HTTP_VER_FAM_INVALID;
+ mhd_conn_start_closing (c,
+ mhd_CONN_CLOSE_H2_PREFACE_MISSING,
+ mhd_MSG4LOG ("No valid HTTP/2 preface on " \
+ "TLS connection with 'h2' "
+ "selected by ALPN"));
+ return false;
+ }
+ break;
+ default:
+ mhd_UNREACHABLE ();
+ c->h_layer.state = mhd_HTTP_LAYER_BROKEN;
+ c->h_layer.fam = mhd_HTTP_VER_FAM_INVALID;
+ return false;
+ break;
+ }
+ }
+#endif /* MHD_SUPPORT_HTTPS */
+
+ if (! mhd_D_IS_HTTP1_ENABLED (c->daemon))
+ {
+ c->h_layer.state = mhd_HTTP_LAYER_CONNECTED;
+ c->h_layer.fam = mhd_HTTP_VER_FAM_NOT_2;
+ mhd_RESPOND_WITH_ERROR_STATIC (c,
+ MHD_HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED,
+ ERR_RSP_H2_REQUIRED);
+ return true; /* Send error response */
+ }
+
+ c->h_layer.state = mhd_HTTP_LAYER_CONNECTED;
+ c->h_layer.fam = mhd_HTTP_VER_FAM_NOT_2;
+ /* The data in the receive buffer will be re-interpreted as HTTP/1.x request */
+ return true;
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ MHD_INTERNAL enum mhd_CommLayerState
+mhd_h2_process_preface (struct MHD_Connection *restrict c)
+{
+ mhd_assert (mhd_HTTP_LAYER_PREFACE == c->h_layer.state);
+
+ switch (mhd_h2_check_preface (c))
+ {
+ case mhd_H2_PREFACE_CHECK_IS_HTTP2:
+ return
+ h2_handle_preface_found (c) ? mhd_COMM_LAYER_OK : mhd_COMM_LAYER_BROKEN;
+ case mhd_H2_PREFACE_CHECK_IS_NOT_HTTP2:
+ return
+ h2_handle_preface_not_found (c) ?
+ mhd_COMM_LAYER_OK : mhd_COMM_LAYER_BROKEN;
+ case mhd_H2_PREFACE_CHECK_NEED_MORE_DATA:
+ return mhd_COMM_LAYER_PROCESSING;
+ default:
+ break;
+ }
+ mhd_UNREACHABLE ();
+ return mhd_COMM_LAYER_BROKEN;
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void
+mhd_h2_conn_state_update (struct MHD_Connection *restrict c)
+{
+ unsigned int new_state;
+
+ mhd_assert (mhd_C_IS_HTTP2 (c));
+
+ mhd_assert (0u == (c->event_loop_info & MHD_EVENT_LOOP_INFO_CLEANUP));
+#ifdef MHD_SUPPORT_UPGRADE
+ mhd_assert (0u == (c->event_loop_info & MHD_EVENT_LOOP_INFO_UPGRADED));
+#endif /* MHD_SUPPORT_UPGRADE */
+
+ new_state = 0u;
+
+ if (c->read_buffer_offset < c->read_buffer_size)
+ new_state |= (unsigned int) MHD_EVENT_LOOP_INFO_RECV;
+
+ if (c->write_buffer_send_offset < c->write_buffer_append_offset)
+ new_state |= (unsigned int) MHD_EVENT_LOOP_INFO_SEND;
+
+ c->event_loop_info = (enum MHD_ConnectionEventLoopInfo) new_state;
+}
+
+
+static MHD_FN_PAR_NONNULL_ALL_ void
+h2_conn_manage_buff_out (struct MHD_Connection *restrict c)
+{
+ mhd_assert (c->write_buffer_send_offset <= c->write_buffer_append_offset);
+ if (c->write_buffer_send_offset == c->write_buffer_append_offset)
+ {
+ c->write_buffer_send_offset = 0u;
+ c->write_buffer_append_offset = 0u;
+ }
+ else if ((0u != c->write_buffer_append_offset) &&
+ (c->write_buffer_send_offset > c->write_buffer_size / 128))
+ {
+ const size_t left_unsent =
+ c->write_buffer_append_offset - c->write_buffer_send_offset;
+ memmove (c->write_buffer,
+ c->write_buffer + c->write_buffer_send_offset,
+ left_unsent);
+ c->write_buffer_send_offset = 0u;
+ c->write_buffer_append_offset = left_unsent;
+ }
+}
+
+
+static MHD_FN_PAR_NONNULL_ALL_ void
+h2_conn_process_data_inner (struct MHD_Connection *restrict c)
+{
+ /* Check whether first SETTINGS frame was send (queued).
+ It is a part of connection initialisation. */
+ if (! c->h2.state.init.sent_setns)
+ {
+ if (! mhd_h2_q_settings_first_fr (c))
+ return;
+ }
+ /* Check whether first peer SETTINGS frame was received.
+ It is a part of connection initialisation. */
+ if (! c->h2.state.init.got_setns)
+ {
+ if (! mhd_h2_conn_process_first_fr (c))
+ return; /* HTTP/2 cannot be processed */
+ }
+
+ h2_conn_manage_buff_out (c);
+
+ /* Process incoming data.
+ When incoming frames are processed, short control frames (such as
+ PING ACK, SETTINGS ACK, RST_STREAM) could be added to the sending buffer.
+ If connection is broken or output buffer has no space to add required
+ frame, this connection processing is stopped here until the output buffer
+ got more space. */
+ if (! mhd_h2_conn_process_in_data (c))
+ return;
+
+ /* Close broken streams, update receive windows.
+ Short control frames, like WINDOW_UPDATE, RST_STREAM could be added to the
+ sending buffer.
+ If connection is broken or output buffer has no space to add required
+ frame, this connection processing is stopped here until the output buffer
+ got more space. */
+ if (! mhd_h2_conn_process_changes (c))
+ return;
+
+ /* Finally send the replies (if output buffer still have any space). */
+ mhd_h2_conn_process_streams_sending_queue (c);
+
+ return;
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool
+mhd_h2_conn_process_data (struct MHD_Connection *restrict c)
+{
+ mhd_assert (mhd_C_IS_HTTP2 (c));
+
+ mhd_assert (mhd_HTTP_LAYER_CONNECTED <= c->h_layer.state);
+ mhd_assert (mhd_HTTP_LAYER_CLOSING >= c->h_layer.state);
+
+ if (mhd_HTTP_LAYER_CLOSING == c->h_layer.state)
+ {
+ if (c->write_buffer_append_offset == c->write_buffer_send_offset)
+ {
+ mhd_h2_conn_deinit (c);
+ return false;
+ }
+ return true;
+ }
+
+ h2_conn_process_data_inner (c);
+
+ mhd_assert (mhd_HTTP_LAYER_CLOSED != c->h_layer.state);
+ if ((mhd_SOCKET_ERR_NO_ERROR != c->sk.state.discnt_err) ||
+ (0 != (c->sk.ready & mhd_SOCKET_NET_STATE_ERROR_READY)) ||
+ (mhd_HTTP_LAYER_CLOSING == c->h_layer.state))
+ {
+ mhd_h2_conn_deinit (c);
+ return false;
+ }
+
+ return true;
+}
diff --git a/src/mhd2/h2/h2_comm.h b/src/mhd2/h2/h2_comm.h
@@ -0,0 +1,96 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
+/*
+ This file is part of GNU libmicrohttpd.
+ Copyright (C) 2025 Evgeny Grin (Karlson2k)
+
+ GNU libmicrohttpd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ GNU libmicrohttpd is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ Alternatively, you can redistribute GNU libmicrohttpd and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of
+ the License, or (at your option) any later version, together
+ with the eCos exception, as follows:
+
+ As a special exception, if other files instantiate templates or
+ use macros or inline functions from this file, or you compile this
+ file and link it with other works to produce a work based on this
+ file, this file does not by itself cause the resulting work to be
+ covered by the GNU General Public License. However the source code
+ for this file must still be made available in accordance with
+ section (3) of the GNU General Public License v2.
+
+ This exception does not invalidate any other reasons why a work
+ based on this file might be covered by the GNU General Public
+ License.
+
+ You should have received copies of the GNU Lesser General Public
+ License and the GNU General Public License along with this library;
+ if not, see <https://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file src/mhd2/h2/h2_comm.h
+ * @brief Declarations of HTTP/2 connection communication functions
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+#ifndef MHD_H2_COMM_H
+#define MHD_H2_COMM_H 1
+
+#include "mhd_sys_options.h"
+
+#include "sys_bool_type.h"
+
+#include "mhd_comm_layer_state.h"
+
+struct MHD_Connection; /* forward declaration */
+
+/**
+ * Perform initial (blank) initialisation of HTTP/2 connection data
+ * @param c the connection to initialise
+ */
+#ifndef HAVE_NULL_PTR_ALL_ZEROS
+MHD_INTERNAL void
+mhd_h2_blank_init (struct MHD_Connection *restrict c);
+
+MHD_FN_PAR_NONNULL_ALL_;
+#else /* ! HAVE_NULL_PTR_ALL_ZEROS */
+#define mhd_h2_blank_init(c) ((void) 0)
+#endif /* ! HAVE_NULL_PTR_ALL_ZEROS */
+
+/**
+ * Perform handshaking of HTTP communication layer.
+ *
+ * Update @a c->h_layer and switch to HTTP/2 mode as necessary.
+ * @param c the connection to use
+ * @return #mhd_COMM_LAYER_PROCESSING if preface processing is not completed,
+ * #mhd_COMM_LAYER_OK if HTTP communication can be performed now,
+ * #mhd_COMM_LAYER_BROKEN if connection is broken and should be closed.
+ */
+MHD_INTERNAL enum mhd_CommLayerState
+mhd_h2_process_preface (struct MHD_Connection *restrict c)
+MHD_FN_PAR_NONNULL_ALL_;
+
+
+MHD_INTERNAL void
+mhd_h2_conn_state_update (struct MHD_Connection *restrict c)
+MHD_FN_PAR_NONNULL_ALL_;
+
+
+MHD_INTERNAL bool
+mhd_h2_conn_process_data (struct MHD_Connection *restrict c)
+MHD_FN_PAR_NONNULL_ALL_;
+
+MHD_INTERNAL void
+mhd_h2_conn_deinit (struct MHD_Connection *restrict c)
+MHD_FN_PAR_NONNULL_ALL_;
+
+#endif /* ! MHD_H2_COMM_H */
diff --git a/src/mhd2/h2/h2_conn_data.h b/src/mhd2/h2/h2_conn_data.h
@@ -0,0 +1,245 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
+/*
+ This file is part of GNU libmicrohttpd.
+ Copyright (C) 2025 Evgeny Grin (Karlson2k)
+
+ GNU libmicrohttpd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ GNU libmicrohttpd is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ Alternatively, you can redistribute GNU libmicrohttpd and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of
+ the License, or (at your option) any later version, together
+ with the eCos exception, as follows:
+
+ As a special exception, if other files instantiate templates or
+ use macros or inline functions from this file, or you compile this
+ file and link it with other works to produce a work based on this
+ file, this file does not by itself cause the resulting work to be
+ covered by the GNU General Public License. However the source code
+ for this file must still be made available in accordance with
+ section (3) of the GNU General Public License v2.
+
+ This exception does not invalidate any other reasons why a work
+ based on this file might be covered by the GNU General Public
+ License.
+
+ You should have received copies of the GNU Lesser General Public
+ License and the GNU General Public License along with this library;
+ if not, see <https://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file src/mhd2/h2/h2_conn_data.h
+ * @brief Definition of HTTP/2 connection-specific data
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+#ifndef MHD_H2_CONN_DATA_H
+#define MHD_H2_CONN_DATA_H 1
+
+#include "mhd_sys_options.h"
+
+#include "sys_bool_type.h"
+#include "sys_base_types.h"
+
+#include "mhd_dlinked_list.h"
+
+#include "hpack/mhd_hpack_dec_types.h"
+#include "hpack/mhd_hpack_enc_types.h"
+
+/**
+ * The size in bytes of HTTP/2 preface
+ */
+#define mhd_H2_PREFACE_LEN (24u)
+
+struct mhd_MemoryPool; /* Forward declaration */
+struct mhd_H2ReqItemsBlock; /* Forward declaration */
+struct mhd_H2Stream; /* Forward declaration */
+
+mhd_DLINKEDL_LIST_DEF (mhd_H2Stream);
+
+struct mhd_H2ConnRecvCtrlData
+{
+ uint_least32_t stream_init_win_sz;
+
+ uint_least32_t conn_full_win_sz;
+
+ uint_least32_t max_frame_size; // TODO: implement update when ACKed by peer
+
+ uint_least32_t max_header_list;
+
+ uint_least32_t max_concur_streams;
+};
+
+struct mhd_H2ConnMemData
+{
+ struct mhd_MemoryPool *send_pool;
+
+ /**
+ * Request items block (headers, footers, URI arguments, cookies etc.)
+ */
+ struct mhd_H2ReqItemsBlock *req_ib;
+};
+
+struct mhd_H2GoawayData
+{
+ bool occurred;
+
+ uint_least32_t code;
+};
+
+struct mhd_H2ConnStateInitData
+{
+ /**
+ * 'true' if the first peer settings has been received and processed
+ */
+ bool got_setns;
+ /**
+ * 'true' if the first settings frame has been sent
+ */
+ bool sent_setns;
+};
+
+struct mhd_H2ConnStateData
+{
+ struct mhd_H2ConnStateInitData init;
+ /**
+ * Number of sent settings not ACKed yet by peer
+ */
+ uint_fast64_t sent_setns_noakc;
+
+ int_least32_t send_window;
+
+ int_least32_t recv_window;
+ /**
+ * Highest Stream ID of any received stream
+ */
+ uint_least32_t top_seen_stream_id;
+ /**
+ * Highest Stream ID of the stream for which application callback is called
+ */
+ uint_least32_t top_proc_stream_id;
+ /**
+ * Highest Stream ID of the stream for which RST_STREAM was sent
+ */
+ uint_least32_t top_rst_stream_id;
+ /**
+ * If set to non-zero then the next incoming frame must be a CONTINUATION
+ * frame with specified Stream ID.
+ */
+ uint_least32_t continuation_stream_id;
+
+ struct mhd_H2GoawayData sent_goaway;
+
+ struct mhd_H2GoawayData recvd_goaway;
+};
+
+struct mhd_H2ConnPeerData
+{
+ uint_least32_t stream_init_win_sz;
+
+ /**
+ * Maximum outgoing frame size to use.
+ * If peer set value smaller than allowed by the settings, then this value
+ * is in this field. Otherwise, this field indicates maximum size allowed
+ * by the settings.
+ */
+ uint_least32_t max_frame_size;
+
+ uint_least32_t max_header_list;
+
+ uint_least32_t max_concur_streams;
+
+ /**
+ * Stream ID specified as the last stream in peer GOAWAY message
+ */
+ uint_least32_t stream_id_limit; // TODO check value when getting new streams
+};
+
+struct mhd_H2ConnStreams
+{
+ /**
+ * The list of all not finished streams
+ */
+ mhd_DLNKDL_LIST (mhd_H2Stream, active);
+
+ /**
+ * The sending queue
+ */
+ mhd_DLNKDL_LIST (mhd_H2Stream, send_q);
+
+ /**
+ * The total number streams in the @a active list
+ */
+ size_t num_streams;
+};
+
+struct mhd_H2ConnBuffData
+{
+ /**
+ * The offset in the read buffer of the frame to be processes / being processed
+ */
+ size_t r_cur_frame;
+
+ /**
+ * Position of unprocessed part of HEADERS/CONTINUATION payload.
+ * Used only when the next frame is CONTINUATION.
+ */
+ size_t unproc_hdrs_pos;
+
+ /**
+ * Size of unprocessed part of HEADERS/CONTINUATION payload.
+ * Must be non-zero only when the next frame is CONTINUATION.
+ */
+ size_t unproc_hdrs_size;
+};
+
+#ifndef NDEBUG
+struct mhd_H2ConnDebug
+{
+ volatile bool w_buff_updating;
+ volatile bool h2_deinited;
+};
+#endif /* ! NDEBUG */
+
+struct mhd_H2ConnData
+{
+ struct mhd_H2ConnRecvCtrlData rcv_cfg;
+
+ struct mhd_H2ConnMemData mem;
+
+ struct mhd_H2ConnStateData state;
+
+ struct mhd_H2ConnPeerData peer;
+
+ struct mhd_HpackDecContext hk_dec;
+
+ struct mhd_HpackEncContext hk_enc;
+
+ struct mhd_H2ConnStreams streams;
+
+ struct mhd_H2ConnBuffData buff;
+#ifndef NDEBUG
+ struct mhd_H2ConnDebug dbg;
+#endif /* ! NDEBUG */
+};
+
+#ifndef NDEBUG
+# define mhd_H2_W_BUFF_UPDATING_SET(h2data) \
+ do {(h2data)->dbg.w_buff_updating = true;} while (0)
+# define mhd_H2_W_BUFF_UPDATING_CLEAR(h2data) \
+ do {(h2data)->dbg.w_buff_updating = false;} while (0)
+#else /* NDEBUG */
+# define mhd_H2_W_BUFF_UPDATING_SET(h2data) ((void) 0)
+# define mhd_H2_W_BUFF_UPDATING_CLEAR(h2data) ((void) 0)
+#endif /* NDEBUG */
+
+#endif /* ! MHD_H2_CONN_DATA_H */
diff --git a/src/mhd2/h2/h2_conn_streams.c b/src/mhd2/h2/h2_conn_streams.c
@@ -0,0 +1,710 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
+/*
+ This file is part of GNU libmicrohttpd.
+ Copyright (C) 2025 Evgeny Grin (Karlson2k)
+
+ GNU libmicrohttpd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ GNU libmicrohttpd is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ Alternatively, you can redistribute GNU libmicrohttpd and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of
+ the License, or (at your option) any later version, together
+ with the eCos exception, as follows:
+
+ As a special exception, if other files instantiate templates or
+ use macros or inline functions from this file, or you compile this
+ file and link it with other works to produce a work based on this
+ file, this file does not by itself cause the resulting work to be
+ covered by the GNU General Public License. However the source code
+ for this file must still be made available in accordance with
+ section (3) of the GNU General Public License v2.
+
+ This exception does not invalidate any other reasons why a work
+ based on this file might be covered by the GNU General Public
+ License.
+
+ You should have received copies of the GNU Lesser General Public
+ License and the GNU General Public License along with this library;
+ if not, see <https://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file src/mhd2/h2/h2_conn_streams.c
+ * @brief Implementation of HTTP/2 connection streams processing functions
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+#include "mhd_sys_options.h"
+
+#include "sys_bool_type.h"
+#include "sys_base_types.h"
+
+#include "mhd_assert.h"
+#include "mhd_unreachable.h"
+
+#include "mhd_constexpr.h"
+
+#include "compat_calloc.h"
+#include "sys_malloc.h"
+
+#include "mhd_dlinked_list.h"
+
+#include "mhd_response.h"
+#include "mhd_connection.h"
+
+#include "mempool_funcs.h"
+
+#include "response_destroy.h"
+
+#include "h2_bit_masks.h"
+#include "h2_err_codes.h"
+
+#include "h2_stream_data.h"
+#include "h2_proc_out.h"
+#include "h2_proc_conn.h"
+#include "h2_app_cb.h"
+
+#include "h2_req_items_funcs.h"
+#include "h2_req_fields.h"
+#include "h2_reply_funcs.h"
+
+#include "h2_conn_streams.h"
+
+#include <string.h>
+
+
+static MHD_FN_PAR_NONNULL_ALL_ struct mhd_H2Stream *
+conn_add_new_stream (struct MHD_Connection *restrict c,
+ uint_least32_t stream_id)
+{
+ struct mhd_H2Stream *s;
+
+ mhd_assert ((stream_id & mhd_H2_STREAM_ID_MASK) == stream_id);
+ mhd_assert (c->h2.rcv_cfg.max_concur_streams > c->h2.streams.num_streams);
+
+ s = (struct mhd_H2Stream *) mhd_calloc (1u,
+ sizeof(struct mhd_H2Stream));
+ if (NULL == s)
+ return NULL;
+
+ s->is_h2 = true;
+ s->stream_id = stream_id;
+
+#ifndef HAVE_NULL_PTR_ALL_ZEROS
+ mhd_DLINKEDL_INIT_LINKS (s, streams);
+ mhd_DLINKEDL_INIT_LINKS (s, send_q);
+ s->req.app_context = NULL;
+#endif /* ! HAVE_NULL_PTR_ALL_ZEROS */
+
+ s->c = c;
+
+ s->req.is_http2 = true;
+ s->req.stage = mhd_H2_REQ_STAGE_HEADERS_INCOMPLETE;
+ s->req.pos_method = mhd_H2_REQ_ITEM_POS_INVALID;
+ s->req.pos_path = mhd_H2_REQ_ITEM_POS_INVALID;
+ s->req.pos_authority = mhd_H2_REQ_ITEM_POS_INVALID;
+
+ s->state.recv_window = (int_least32_t) c->h2.rcv_cfg.stream_init_win_sz;
+ mhd_assert (0 < s->state.recv_window);
+ s->state.send_window = (int_least32_t) c->h2.peer.stream_init_win_sz;
+ mhd_assert (0 < s->state.send_window);
+
+ mhd_DLINKEDL_INS_LAST_D (&(c->h2.streams.active), s, streams);
+ mhd_assert (0u != ~(c->h2.streams.num_streams));
+ ++(c->h2.streams.num_streams);
+
+ return s;
+}
+
+
+static MHD_FN_PAR_NONNULL_ALL_ void
+conn_remove_stream (struct MHD_Connection *c,
+ struct mhd_H2Stream *restrict s)
+{
+ mhd_assert (s->c == c);
+
+ if (NULL != s->rpl.response)
+ mhd_response_dec_use_count (s->rpl.response);
+ mhd_DLINKEDL_DEL_D (&(c->h2.streams.active), s, streams);
+
+ free (s);
+ mhd_assert (0u != c->h2.streams.num_streams);
+ --(c->h2.streams.num_streams);
+}
+
+
+static MHD_FN_PAR_NONNULL_ALL_ struct mhd_H2Stream *
+conn_find_stream (struct MHD_Connection *restrict c,
+ uint_least32_t stream_id)
+{
+ struct mhd_H2Stream *s;
+
+ // TODO: improve search. Binary tree or linear array?
+ for (s = mhd_DLINKEDL_GET_FIRST_D (&(c->h2.streams.active));
+ NULL != s;
+ s = mhd_DLINKEDL_GET_NEXT (s, streams))
+ {
+ if (stream_id == s->stream_id)
+ return s;
+ }
+
+ return NULL;
+}
+
+
+static void
+stream_start_replying (struct mhd_H2Stream *restrict s)
+MHD_FN_PAR_NONNULL_ALL_;
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool
+mhd_h2_stream_req_problem (struct mhd_H2Stream *restrict s,
+ enum mhd_H2RequestProblemType problem_type)
+{
+ // TODO: send error reply
+ (void) problem_type;
+ return mhd_h2_stream_abort (s,
+ mhd_H2_ERR_PROTOCOL_ERROR); // TODO: use correct error code
+}
+
+
+static MHD_FN_PAR_NONNULL_ALL_ bool
+stream_send_rst_stream (struct mhd_H2Stream *restrict s,
+ enum mhd_H2ErrorCode err)
+{
+ mhd_assert ((! s->state.sent_rst_stream) &&
+ "RST_STREAM must not be sent more than once for active stream");
+
+ if (! mhd_h2_q_rst_stream (s->c,
+ s->stream_id,
+ err))
+ return false;
+
+ s->state.sent_rst_stream = true;
+ s->state.mhd_err = err;
+
+ return true;
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool
+mhd_h2_stream_abort (struct mhd_H2Stream *restrict s,
+ enum mhd_H2ErrorCode err)
+{
+ s->req.stage = mhd_H2_REQ_STAGE_BROKEN;
+ // TODO: Handle correctly by RST_STREAM
+ mhd_h2_conn_finish (s->c,
+ err,
+ true);
+ return false;
+}
+
+
+static MHD_FN_PAR_NONNULL_ALL_ bool
+stream_proc_complete_headers (struct mhd_H2Stream *restrict s)
+{
+ mhd_assert (mhd_H2_REQ_STAGE_HEADERS_INCOMPLETE == s->req.stage);
+ mhd_assert (0u == s->c->h2.state.continuation_stream_id);
+ mhd_assert (mhd_h2_items_debug_get_streamid (s->c->h2.mem.req_ib)
+ == s->stream_id);
+
+ s->req.stage = mhd_H2_REQ_STAGE_HEADERS_DECODING;
+ s->req.cntn_size = (s->req.got_end_stream ? 0u : MHD_SIZE_UNKNOWN);
+
+ if (! mhd_h2_req_headers_preprocess (s))
+ return false;
+
+ if (! mhd_h2_stream_cb_early_uri (s))
+ return false;
+
+ if (! mhd_h2_req_uri_parse (s))
+ return false;
+
+ if (! mhd_h2_req_cookie_parse (s))
+ return false;
+
+ s->req.stage = mhd_H2_REQ_STAGE_HEADERS_PROCESSING;
+
+ if (! mhd_h2_stream_cb_request (s))
+ return false;
+
+ mhd_assert (mhd_H2_REQ_STAGE_BROKEN != s->req.stage);
+
+ if (s->req.got_end_stream)
+ {
+ s->req.stage = mhd_H2_REQ_STAGE_END_STREAM;
+ mhd_assert (NULL != s->rpl.response);
+ }
+ else
+ s->req.stage = mhd_H2_REQ_STAGE_HEADERS_COMPLETE;
+
+ if (NULL != s->rpl.response)
+ stream_start_replying (s);
+
+ return true;
+}
+
+
+static MHD_FN_PAR_NONNULL_ALL_ bool
+stream_proc_in_headers (struct mhd_H2Stream *restrict s,
+ bool end_headers,
+ struct mhd_Buffer *restrict payload)
+{
+ struct MHD_Connection *const c = s->c;
+ size_t unprocessed;
+
+ switch (mhd_h2_req_fields_decode (&(c->h2.hk_dec),
+ payload,
+ false,
+ c->h2.mem.req_ib,
+ &unprocessed))
+ {
+ case mhd_H2_DEC_FIELDS_OK:
+ break;
+ case mhd_H2_DEC_FIELDS_NO_SPACE:
+ // TODO: Send error response before closing, use RST_STREAM
+ case mhd_H2_DEC_FIELDS_INT_ERR:
+ return mhd_h2_stream_req_problem (s,
+ mhd_H2_REQ_PRBLM_INT_ERROR);
+ case mhd_H2_DEC_FIELDS_BROKEN_DATA:
+ s->req.stage = mhd_H2_REQ_STAGE_BROKEN;
+ mhd_h2_conn_finish (c,
+ mhd_H2_ERR_COMPRESSION_ERROR,
+ true);
+ return false;
+ case mhd_H2_DEC_FIELDS_PROT_ABUSE:
+ s->req.stage = mhd_H2_REQ_STAGE_BROKEN;
+ mhd_h2_conn_finish (c,
+ mhd_H2_ERR_ENHANCE_YOUR_CALM,
+ true);
+ return false;
+ default:
+ mhd_UNREACHABLE ();
+ s->req.stage = mhd_H2_REQ_STAGE_BROKEN;
+ mhd_h2_conn_finish (c,
+ mhd_H2_ERR_INTERNAL_ERROR,
+ true);
+ return false;
+ }
+
+ if (end_headers && (0u != unprocessed))
+ return ! mhd_h2_conn_finish (c,
+ mhd_H2_ERR_COMPRESSION_ERROR,
+ true);
+
+ if (! end_headers)
+ {
+ if (0u != unprocessed)
+ {
+ const size_t payload_offset = (size_t) (payload->data - c->read_buffer);
+
+ /* Unprocessed part may contain only a single field line.
+ Stop stream if it is larger than 3/4 of the max headers size. */
+ if ((c->h2.rcv_cfg.max_header_list - c->h2.rcv_cfg.max_header_list / 4) <=
+ unprocessed)
+ return mhd_h2_stream_req_problem (s,
+ mhd_H2_REQ_PRBLM_HEADERS_TOO_LARGE);
+
+ mhd_assert (c->h2.rcv_cfg.max_frame_size < mhd_pool_get_size (c->pool));
+ if (((mhd_pool_get_size (c->pool) - c->h2.rcv_cfg.max_frame_size) / 2) <=
+ unprocessed)
+ return mhd_h2_stream_req_problem (s,
+ mhd_H2_REQ_PRBLM_HEADERS_TOO_LARGE);
+
+ c->h2.buff.unproc_hdrs_pos = payload_offset + payload->size - unprocessed;
+ c->h2.buff.unproc_hdrs_size = unprocessed;
+ }
+
+ c->h2.state.continuation_stream_id = s->stream_id;
+ }
+ else
+ {
+ stream_proc_complete_headers (s);
+ }
+
+ return true;
+}
+
+
+static MHD_FN_PAR_NONNULL_ALL_ bool
+conn_proc_new_in_stream (struct MHD_Connection *restrict c,
+ uint_least32_t stream_id,
+ bool end_stream,
+ bool end_headers,
+ struct mhd_Buffer *restrict payload)
+{
+ struct mhd_H2Stream *s;
+
+ mhd_assert ((stream_id & mhd_H2_STREAM_ID_MASK) == stream_id);
+ mhd_assert (c->h2.state.top_seen_stream_id >= c->h2.state.top_proc_stream_id);
+ mhd_assert (c->h2.state.top_seen_stream_id < stream_id);
+ mhd_assert (0u == c->h2.state.continuation_stream_id);
+ mhd_assert (NULL == conn_find_stream (c, stream_id));
+ mhd_assert (0u == c->h2.buff.unproc_hdrs_size);
+ mhd_assert (c->read_buffer <= payload->data);
+ mhd_assert ((c->read_buffer_size + c->read_buffer) >=
+ (payload->data + payload->size));
+
+ if (c->h2.streams.num_streams >= c->h2.rcv_cfg.max_concur_streams)
+ return mhd_h2_q_rst_stream (c,
+ stream_id,
+ mhd_H2_ERR_REFUSED_STREAM);
+
+ s = conn_add_new_stream (c,
+ stream_id);
+
+ if (NULL == s)
+ return mhd_h2_q_rst_stream (c, /* REFUSED_STREAM indicates that stream has not been processed at all */
+ stream_id,
+ mhd_H2_ERR_REFUSED_STREAM);
+ mhd_h2_items_block_reset (c->h2.mem.req_ib);
+
+
+ mhd_h2_items_debug_set_streamid (c->h2.mem.req_ib,
+ stream_id);
+
+ s->req.got_end_stream = end_stream;
+
+ /* The next call process frame data. Current function must not return
+ 'false' (unless the connection is broken) beyond this point as the
+ connection data (HPACK) has been modified . */
+ return stream_proc_in_headers (s,
+ end_headers,
+ payload);
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool
+mhd_h2_conn_streamid_in_headers (struct MHD_Connection *restrict c,
+ uint_least32_t stream_id,
+ bool end_stream,
+ bool end_headers,
+ struct mhd_Buffer *restrict payload)
+{
+ mhd_assert (0u != stream_id);
+ mhd_assert ((stream_id & mhd_H2_STREAM_ID_MASK) == stream_id);
+ mhd_assert (c->h2.state.top_seen_stream_id >= c->h2.state.top_proc_stream_id);
+
+ if (0u == (stream_id & 1u))
+ return mhd_h2_conn_finish (c,
+ mhd_H2_ERR_PROTOCOL_ERROR,
+ false);
+
+ if (c->h2.state.top_seen_stream_id < stream_id)
+ return conn_proc_new_in_stream (c,
+ stream_id,
+ end_stream,
+ end_headers,
+ payload);
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool
+mhd_h2_conn_streamid_in_continuation (struct MHD_Connection *restrict c,
+ uint_least32_t stream_id,
+ bool end_headers,
+ struct mhd_Buffer *payload)
+{
+ struct mhd_Buffer combined_payload;
+ struct mhd_H2Stream *s;
+
+ mhd_assert (0u != stream_id);
+ mhd_assert ((stream_id & mhd_H2_STREAM_ID_MASK) == stream_id);
+ mhd_assert (c->h2.state.top_seen_stream_id >= c->h2.state.top_proc_stream_id);
+
+ if (c->h2.state.continuation_stream_id != stream_id)
+ return mhd_h2_conn_finish (c,
+ mhd_H2_ERR_PROTOCOL_ERROR,
+ false);
+ s = conn_find_stream (c,
+ stream_id);
+ mhd_assert (NULL != s);
+
+ if (0u == c->h2.buff.unproc_hdrs_size)
+ combined_payload = *payload;
+ else
+ {
+ /* Concatenate previously unprocessed part and the new part.
+ This will break CONTINUATION frame header, but the frame header is not
+ needed as all data from the header has been decoded.
+ However, unless connection is broken, 'false' must not be
+ returned by this function beyond this point as the same frame
+ cannot be decoded again. */
+ memmove (payload->data - c->h2.buff.unproc_hdrs_size,
+ c->read_buffer + c->h2.buff.unproc_hdrs_pos,
+ c->h2.buff.unproc_hdrs_size);
+ combined_payload.data = payload->data - c->h2.buff.unproc_hdrs_size;
+ combined_payload.size = c->h2.buff.unproc_hdrs_size + payload->size;
+ }
+
+ return stream_proc_in_headers (s,
+ end_headers,
+ &combined_payload);
+}
+
+
+static MHD_FN_PAR_NONNULL_ALL_ void
+stream_set_reply_props (struct mhd_H2Stream *restrict s)
+{
+ mhd_assert (mhd_H2_RPL_STAGE_HEADERS_INCOMPLETE == s->rpl.stage);
+ mhd_assert ((mhd_H2_REQ_STAGE_END_STREAM == s->req.stage) ||
+ (mhd_H2_REQ_STAGE_HEADERS_COMPLETE == s->req.stage));
+ mhd_assert (NULL != s->rpl.response);
+#if 0 // TODO: implement chained replies
+ if (199 >= s->rpl.response->sc)
+ {
+ s->rpl.fields.auto_cntn_len = false;
+ s->rpl.send_content = false;
+ s->rpl.chained = true;
+ }
+ else
+#endif
+ if (MHD_HTTP_STATUS_NO_CONTENT == s->rpl.response->sc)
+ {
+ s->rpl.fields.auto_cntn_len = false;
+ s->rpl.send_content = false;
+ }
+ else if ((mhd_HTTP_METHOD_HEAD == s->req.method) ||
+ (MHD_HTTP_STATUS_NOT_MODIFIED == s->rpl.response->sc))
+ {
+ s->rpl.fields.auto_cntn_len =
+ (MHD_NO == s->rpl.response->cfg.head_only)
+ && (MHD_SIZE_UNKNOWN != s->rpl.response->cntn_size);
+ s->rpl.send_content = false;
+ }
+ else
+ {
+ s->rpl.fields.auto_cntn_len = (MHD_SIZE_UNKNOWN != s->rpl.response->
+ cntn_size);
+ s->rpl.send_content = (0u != s->rpl.response->cntn_size);
+ }
+ s->rpl.cntn_read_pos = 0u;
+}
+
+
+static MHD_FN_PAR_NONNULL_ALL_ void
+stream_start_replying (struct mhd_H2Stream *restrict s)
+{
+ struct MHD_Connection *c = s->c;
+
+ /* The stream must not be in the sending queue */
+ mhd_assert (NULL == mhd_DLINKEDL_GET_NEXT (s, send_q));
+ mhd_assert (s != mhd_DLINKEDL_GET_LAST_D (&(c->h2.streams.send_q)));
+ mhd_assert (mhd_H2_RPL_STAGE_HEADERS_INCOMPLETE == s->rpl.stage);
+ mhd_assert (NULL != s->rpl.response);
+
+ stream_set_reply_props (s);
+
+ mhd_DLINKEDL_INS_LAST (&(c->h2.streams), s, send_q);
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool
+mhd_h2_conn_streamid_in_rst_stream (struct MHD_Connection *restrict c,
+ uint_least32_t stream_id,
+ enum mhd_H2ErrorCode err)
+{
+
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool
+mhd_h2_conn_streamid_window_incr (struct MHD_Connection *restrict c,
+ uint_least32_t stream_id,
+ uint_least32_t incr)
+{
+ struct mhd_H2Stream *s = conn_find_stream (c,
+ stream_id);
+ if (NULL == s)
+ {
+ if ((0u == (stream_id & 1u)) ||
+ (c->h2.state.top_rst_stream_id < stream_id))
+ return mhd_h2_conn_finish (c,
+ mhd_H2_ERR_PROTOCOL_ERROR,
+ false);
+
+ return true; /* Just ignore the frame */
+ }
+ if ((0 < s->state.send_window)
+ && (0 > s->state.send_window + (int_least32_t) stream_id))
+ return mhd_h2_stream_req_problem (s,
+ mhd_H2_REQ_PRBLM_FLOW_CONTROL);
+ s->state.send_window += (int_least32_t) stream_id;
+ return true;
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool
+mhd_h2_conn_streamid_abort (struct MHD_Connection *restrict c,
+ uint_least32_t stream_id,
+ enum mhd_H2ErrorCode err)
+{
+
+}
+
+
+static MHD_FN_PAR_NONNULL_ALL_ bool
+stream_maintain_rcv_window (struct mhd_H2Stream *restrict s)
+{
+ struct MHD_Connection *restrict c = s->c;
+
+ mhd_assert (0 <= s->state.recv_window);
+ /* Dumb algorithm: if receive windows is less than three quarters of the full
+ * window size, then bump to the full size. */
+
+ if ((c->h2.rcv_cfg.stream_init_win_sz - c->h2.rcv_cfg.stream_init_win_sz / 4)
+ <= (uint_least32_t) s->state.recv_window)
+ {
+ uint_least32_t incr =
+ (uint_least32_t)
+ (c->h2.rcv_cfg.stream_init_win_sz
+ - (uint_least32_t) s->state.recv_window);
+ mhd_assert (0x7FFFFFFFu >= incr);
+ if (! mhd_h2_q_window_update (c,
+ s->stream_id,
+ incr))
+ return false;
+
+ s->state.recv_window = (int_least32_t) c->h2.rcv_cfg.stream_init_win_sz;
+ }
+
+ return true;
+}
+
+
+mhd_static_inline MHD_FN_PAR_NONNULL_ALL_ bool
+stream_is_closed (const struct mhd_H2Stream *restrict s)
+{
+ /* If END_STREAM flag has been both send and received then stream is closed */
+ if ((mhd_H2_REQ_STAGE_END_STREAM == s->req.stage)
+ && (mhd_H2_RPL_STAGE_END_STREAM == s->rpl.stage))
+ return true;
+
+ if ((s->state.rcvd_rst_stream) || (s->state.sent_rst_stream))
+ return true;
+
+ return false;
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool
+mhd_h2_conn_maintain_streams_all (struct MHD_Connection *c)
+{
+ struct mhd_H2Stream *next;
+ struct mhd_H2Stream *s;
+ mhd_assert ((! c->h2.state.sent_goaway.occurred) ||
+ (mhd_H2_ERR_NO_ERROR == c->h2.state.sent_goaway.code));
+
+ next = mhd_DLINKEDL_GET_FIRST_D (&(c->h2.streams.active));
+ while (NULL != (s = next))
+ {
+ next = mhd_DLINKEDL_GET_NEXT (s, streams);
+
+ /* Send RST_STREAM is needed */
+ if ((! s->state.rcvd_rst_stream)
+ && (! s->state.sent_rst_stream))
+ {
+ if ((mhd_H2_REQ_STAGE_BROKEN == s->req.stage) ||
+ (mhd_H2_RPL_STAGE_BROKEN == s->rpl.stage))
+ {
+ enum mhd_H2ErrorCode err;
+ err = s->state.mhd_err;
+ if (mhd_H2_ERR_NO_ERROR == err)
+ err = mhd_H2_ERR_INTERNAL_ERROR;
+ if (! stream_send_rst_stream (s,
+ err))
+ return false;
+ }
+ }
+
+ /* Close and remove stream if it is finished */
+ if (stream_is_closed (s))
+ {
+ conn_remove_stream (c,
+ s);
+ continue;
+ }
+
+ if (mhd_H2_REQ_STAGE_END_STREAM > s->req.stage)
+ {
+ if (! stream_maintain_rcv_window (s))
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+mhd_constexpr size_t min_usable_buff = 32u;
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool
+mhd_h2_conn_process_streams_sending_queue (struct MHD_Connection *c)
+{
+ struct mhd_H2Stream *already_processed;
+ mhd_assert ((! c->h2.state.sent_goaway.occurred) ||
+ (mhd_H2_ERR_NO_ERROR == c->h2.state.sent_goaway.code));
+
+ already_processed = NULL;
+ while (! 0)
+ {
+ struct mhd_H2Stream *const s =
+ mhd_DLINKEDL_GET_FIRST_D (&(c->h2.streams.send_q));
+
+ if (NULL == s)
+ break;
+
+ if (already_processed == s)
+ break;
+
+ mhd_assert (! stream_is_closed (s));
+
+ mhd_h2_stream_reply_send (s);
+ if (mhd_H2_ERR_NO_ERROR != c->h2.state.sent_goaway.code)
+ return false;
+
+ mhd_DLINKEDL_DEL_D (&(c->h2.streams.send_q), s, send_q);
+ if (stream_is_closed (s))
+ conn_remove_stream (c,
+ s);
+ else if (mhd_H2_RPL_STAGE_END_STREAM > s->rpl.stage)
+ {
+ /* Still sending, move the stream to the end of the queue */
+ mhd_DLINKEDL_INS_LAST_D (&(c->h2.streams.send_q), s, send_q);
+ if (NULL == already_processed)
+ already_processed = s;
+ }
+ if ((c->write_buffer_size - c->write_buffer_append_offset) <
+ min_usable_buff)
+ return false;
+ }
+ return true;
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void
+mhd_h2_conn_close_streams_all (struct MHD_Connection *restrict c)
+{
+ while (! 0)
+ {
+ struct mhd_H2Stream *const s =
+ mhd_DLINKEDL_GET_FIRST_D (&(c->h2.streams.send_q));
+
+ if (NULL == s)
+ break;
+
+ mhd_assert (! stream_is_closed (s));
+
+ mhd_DLINKEDL_DEL_D (&(c->h2.streams.send_q), s, send_q);
+ conn_remove_stream (c,
+ s);
+ }
+ mhd_assert (0u == c->h2.streams.num_streams);
+}
diff --git a/src/mhd2/h2/h2_conn_streams.h b/src/mhd2/h2/h2_conn_streams.h
@@ -0,0 +1,184 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
+/*
+ This file is part of GNU libmicrohttpd.
+ Copyright (C) 2025 Evgeny Grin (Karlson2k)
+
+ GNU libmicrohttpd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ GNU libmicrohttpd is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ Alternatively, you can redistribute GNU libmicrohttpd and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of
+ the License, or (at your option) any later version, together
+ with the eCos exception, as follows:
+
+ As a special exception, if other files instantiate templates or
+ use macros or inline functions from this file, or you compile this
+ file and link it with other works to produce a work based on this
+ file, this file does not by itself cause the resulting work to be
+ covered by the GNU General Public License. However the source code
+ for this file must still be made available in accordance with
+ section (3) of the GNU General Public License v2.
+
+ This exception does not invalidate any other reasons why a work
+ based on this file might be covered by the GNU General Public
+ License.
+
+ You should have received copies of the GNU Lesser General Public
+ License and the GNU General Public License along with this library;
+ if not, see <https://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file src/mhd2/h2/h2_conn_streams.h
+ * @brief Declarations of HTTP/2 connection streams processing functions
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+#ifndef MHD_H2_CONN_STREAMS_H
+#define MHD_H2_CONN_STREAMS_H 1
+
+#include "mhd_sys_options.h"
+
+#include "sys_bool_type.h"
+
+#include "h2_err_codes.h"
+
+struct MHD_Connection; /* forward declaration */
+struct mhd_Buffer; /* forward declaration */
+struct mhd_H2Stream; /* forward declaration */
+
+enum mhd_H2RequestProblemType
+{
+ mhd_H2_REQ_PRBLM_FIELD_CHARS
+ ,
+ mhd_H2_REQ_PRBLM_FORBIDDEN_HEADER
+ ,
+ mhd_H2_REQ_PRBLM_CNTNT_LEN_WRONG
+ ,
+ mhd_H2_REQ_PRBLM_HOST_HDR_WRONG_EXTRA
+ ,
+ mhd_H2_REQ_PRBLM_HEADERS_NO_ENDSTREAM
+ ,
+ mhd_H2_REQ_PRBLM_PSEUDOHDR_IN_TRAILER
+ ,
+ mhd_H2_REQ_PRBLM_UNKNOWN_PSEUDOHDR
+ ,
+ mhd_H2_REQ_PRBLM_PSEUDOHDR_AFTER_HDR
+ ,
+ mhd_H2_REQ_PRBLM_PSEUDOHDR_MISSING
+ ,
+ mhd_H2_REQ_PRBLM_PSEUDOHDR_DUP
+ ,
+ mhd_H2_REQ_PRBLM_PSEUDOHDR_EXTRA
+ ,
+ mhd_H2_REQ_PRBLM_FLOW_CONTROL
+ ,
+
+ mhd_H2_REQ_PRBLM_HEADERS_TOO_LARGE
+ ,
+ mhd_H2_REQ_PRBLM_INT_ERROR
+};
+
+/**
+ * Handle malformed request.
+ * Send error reply, if possible; abort the request.
+ * @param s the stream to handle
+ * @param problem_type the type of the problem
+ * @return always 'false'
+ */
+MHD_INTERNAL bool
+mhd_h2_stream_req_problem (struct mhd_H2Stream *restrict s,
+ enum mhd_H2RequestProblemType problem_type)
+MHD_FN_PAR_NONNULL_ALL_;
+
+
+/**
+ * Perform early abort of the stream, typically due to some error in processing.
+ * @param s the stream to abort
+ * @param err the error code to use
+ * @return always 'false'
+ */
+MHD_INTERNAL bool
+mhd_h2_stream_abort (struct mhd_H2Stream *restrict s,
+ enum mhd_H2ErrorCode err)
+MHD_FN_PAR_NONNULL_ALL_;
+
+MHD_INTERNAL bool
+mhd_h2_conn_streamid_in_headers (struct MHD_Connection *restrict c,
+ uint_least32_t stream_id,
+ bool end_stream,
+ bool end_headers,
+ struct mhd_Buffer *restrict payload)
+MHD_FN_PAR_NONNULL_ALL_;
+
+MHD_INTERNAL bool
+mhd_h2_conn_streamid_in_continuation (struct MHD_Connection *restrict c,
+ uint_least32_t stream_id,
+ bool end_headers,
+ struct mhd_Buffer *payload)
+MHD_FN_PAR_NONNULL_ALL_;
+
+MHD_INTERNAL bool
+mhd_h2_conn_streamid_in_data (struct MHD_Connection *restrict c,
+ uint_least32_t stream_id,
+ bool end_stream,
+ struct mhd_Buffer *restrict payload)
+MHD_FN_PAR_NONNULL_ALL_;
+
+MHD_INTERNAL bool
+mhd_h2_conn_streamid_in_rst_stream (struct MHD_Connection *restrict c,
+ uint_least32_t stream_id,
+ enum mhd_H2ErrorCode err)
+MHD_FN_PAR_NONNULL_ALL_;
+
+MHD_INTERNAL bool
+mhd_h2_conn_streamid_window_incr (struct MHD_Connection *restrict c,
+ uint_least32_t stream_id,
+ uint_least32_t incr)
+MHD_FN_PAR_NONNULL_ALL_;
+
+MHD_INTERNAL bool
+mhd_h2_conn_streamid_abort (struct MHD_Connection *restrict c,
+ uint_least32_t stream_id,
+ enum mhd_H2ErrorCode err)
+MHD_FN_PAR_NONNULL_ALL_;
+
+/**
+ * Process maintenance of all streams in the connection
+ * @param c the connection to use
+ * @return 'true' if all connections have been processed,
+ * 'false' is output buffer has no space for required frame or
+ * if connection is broken
+ */
+MHD_INTERNAL bool
+mhd_h2_conn_maintain_streams_all (struct MHD_Connection *restrict c)
+MHD_FN_PAR_NONNULL_ALL_;
+
+/**
+ * Process sending queue, send replies.
+ * @param c the connection to use
+ * @return 'true' if all connections have been processed,
+ * 'false' is output buffer has no space for required frame or
+ * if connection is broken
+ */
+MHD_INTERNAL bool
+mhd_h2_conn_process_streams_sending_queue (struct MHD_Connection *restrict c)
+MHD_FN_PAR_NONNULL_ALL_;
+
+/**
+ * Force close of all connection's streams.
+ * @param c the connection to use
+ */
+MHD_INTERNAL void
+mhd_h2_conn_close_streams_all (struct MHD_Connection *restrict c)
+MHD_FN_PAR_NONNULL_ALL_;
+
+#endif /* ! MHD_H2_CONN_STREAMS_H */
diff --git a/src/mhd2/h2/h2_err_codes.h b/src/mhd2/h2/h2_err_codes.h
@@ -0,0 +1,99 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
+/*
+ This file is part of GNU libmicrohttpd.
+ Copyright (C) 2025 Evgeny Grin (Karlson2k)
+
+ GNU libmicrohttpd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ GNU libmicrohttpd is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ Alternatively, you can redistribute GNU libmicrohttpd and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of
+ the License, or (at your option) any later version, together
+ with the eCos exception, as follows:
+
+ As a special exception, if other files instantiate templates or
+ use macros or inline functions from this file, or you compile this
+ file and link it with other works to produce a work based on this
+ file, this file does not by itself cause the resulting work to be
+ covered by the GNU General Public License. However the source code
+ for this file must still be made available in accordance with
+ section (3) of the GNU General Public License v2.
+
+ This exception does not invalidate any other reasons why a work
+ based on this file might be covered by the GNU General Public
+ License.
+
+ You should have received copies of the GNU Lesser General Public
+ License and the GNU General Public License along with this library;
+ if not, see <https://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file src/mhd2/h2/h2_err_codes.h
+ * @brief Definition of HTTP/2 error codes
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+#ifndef MHD_H2_ERR_CODES_H
+#define MHD_H2_ERR_CODES_H 1
+
+#include "mhd_sys_options.h"
+
+#ifdef mhd_USE_ENUM_BASE_T
+# include "sys_base_types.h"
+#endif
+
+/**
+ * HTTP/2 error codes
+ *
+ * Extracted from RFC 9113, Section 7
+ */
+enum mhd_H2ErrorCode
+mhd_ENUM_BASE_T (uint_least32_t)
+{
+ mhd_H2_ERR_NO_ERROR = 0x00u
+ ,
+ mhd_H2_ERR_PROTOCOL_ERROR = 0x01u
+ ,
+ mhd_H2_ERR_INTERNAL_ERROR = 0x02u
+ ,
+ mhd_H2_ERR_FLOW_CONTROL_ERROR = 0x03u
+ ,
+ mhd_H2_ERR_SETTINGS_TIMEOUT = 0x04u
+ ,
+ mhd_H2_ERR_STREAM_CLOSED = 0x05u
+ ,
+ mhd_H2_ERR_FRAME_SIZE_ERROR = 0x06u
+ ,
+ mhd_H2_ERR_REFUSED_STREAM = 0x07u
+ ,
+ mhd_H2_ERR_CANCEL = 0x08u
+ ,
+ mhd_H2_ERR_COMPRESSION_ERROR = 0x09u
+ ,
+ mhd_H2_ERR_CONNECT_ERROR = 0x0Au
+ ,
+ mhd_H2_ERR_ENHANCE_YOUR_CALM = 0x0Bu
+ ,
+ mhd_H2_ERR_INADEQUATE_SECURITY = 0x0Cu
+ ,
+ mhd_H2_ERR_HTTP_1_1_REQUIRED = 0x0Du
+#ifndef mhd_USE_ENUM_BASE_T
+ ,
+ /**
+ * Not a real error code, no not use
+ */
+ mhd_H2_ERR_SENTINEL = 0x7FFFFFFFu
+#endif /* ! mhd_USE_ENUM_BASE_T */
+};
+
+
+#endif /* ! MHD_H2_ERR_CODES_H */
diff --git a/src/mhd2/h2/h2_frame_codec.c b/src/mhd2/h2/h2_frame_codec.c
@@ -0,0 +1,1462 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
+/*
+ This file is part of GNU libmicrohttpd.
+ Copyright (C) 2025 Evgeny Grin (Karlson2k)
+
+ GNU libmicrohttpd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ GNU libmicrohttpd is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ Alternatively, you can redistribute GNU libmicrohttpd and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of
+ the License, or (at your option) any later version, together
+ with the eCos exception, as follows:
+
+ As a special exception, if other files instantiate templates or
+ use macros or inline functions from this file, or you compile this
+ file and link it with other works to produce a work based on this
+ file, this file does not by itself cause the resulting work to be
+ covered by the GNU General Public License. However the source code
+ for this file must still be made available in accordance with
+ section (3) of the GNU General Public License v2.
+
+ This exception does not invalidate any other reasons why a work
+ based on this file might be covered by the GNU General Public
+ License.
+
+ You should have received copies of the GNU Lesser General Public
+ License and the GNU General Public License along with this library;
+ if not, see <https://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file src/mhd2/h2/h2_frame_codec.c
+ * @brief Implementation of HTTP/2 frame decoding and encoding functions
+ * @author Karlson2k (Evgeny Grin)
+ *
+ * Details of HTTP/2 frame layout are defined in RFC 9113.
+ */
+
+#include "mhd_sys_options.h"
+
+#include "sys_bool_type.h"
+#include "sys_base_types.h"
+
+#include "mhd_assert.h"
+#include "mhd_unreachable.h"
+
+#include <string.h>
+
+#include "mhd_bithelpers.h"
+
+#include "h2_bit_masks.h"
+
+#include "h2_frame_length.h"
+
+#include "h2_frame_codec.h"
+
+
+/**
+ * HTTP/2 frame flag PRIORITY (0x20) used by HEADERS frames
+ */
+#define mhd_FFLAG_PRIORITY (0x20u)
+/**
+ * HTTP/2 frame flag PADDED (0x08) used by DATA, HEADERS and PUSH_PROMISE frames
+ */
+#define mhd_FFLAG_PADDED (0x08u)
+/**
+ * HTTP/2 frame flag END_HEADERS (0x04) used by HEADERS and CONTINUATION frames
+ */
+#define mhd_FFLAG_END_HEADERS (0x04u)
+/**
+ * HTTP/2 frame flag END_STREAM (0x01) used by DATA and HEADERS frames
+ */
+#define mhd_FFLAG_END_STREAM (0x01u)
+/**
+ * HTTP/2 frame flag ACK (0x01) used by SETTINGS and PING frames
+ */
+#define mhd_FFLAG_ACK (0x01u)
+
+/**
+ * Exclusive bit (0x80000000) for the HEADERS/PRIORITY dependency field.
+ */
+#define mhd_FFLAG_HEADERS_EXCLUSIVE (0x80000000u)
+
+
+/**
+ * Decode a DATA frame.
+ *
+ * Validates sizes data and flags data for valid combinations.
+ *
+ * @param payload_buff_size the available input bytes in the @a payload_buff
+ * @param payload_buff the pointer to the beginning of the frame payload
+ * @param flags the frame flags byte
+ * @param[in,out] frame_info the frame information; updated with DATA details
+ * @param[out] frame_payload set to the final payload slice that starts after
+ * any extra header fields and ends before padding
+ * bytes (if any)
+ * @return #mhd_H2_F_DEC_OK on success,
+ * or a relevant decode error code
+ */
+static MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_IN_SIZE_ (2,1)
+MHD_FN_PAR_INOUT_ (4) MHD_FN_PAR_OUT_ (5)
+enum mhd_H2FrameDecodeResult
+frame_decode_data (size_t payload_buff_size,
+ uint8_t *restrict payload_buff,
+ uint_least8_t flags,
+ union mhd_H2FrameUnion *restrict frame_info,
+ struct mhd_Buffer *restrict frame_payload)
+{
+ struct mhd_H2FrameDataInfo *const data =
+ &(frame_info->data);
+ size_t real_payload_pos;
+
+ mhd_assert (mhd_H2_FRAME_DATA_ID == data->type);
+
+ if (0u == data->stream_id)
+ return mhd_H2_F_DEC_CONN_ERR_PROT;
+
+ data->padded = (0 != (flags & mhd_FFLAG_PADDED));
+ data->end_stream = (0 != (flags & mhd_FFLAG_END_STREAM));
+
+ real_payload_pos = 0u;
+ if (data->padded)
+ {
+ if (real_payload_pos >= data->length)
+ return mhd_H2_F_DEC_CONN_ERR_PROT;
+ if (real_payload_pos >= payload_buff_size)
+ return mhd_H2_F_DEC_F_HEADER_INCOMPLETE;
+
+ data->pad_length = (uint_least8_t) payload_buff[real_payload_pos++];
+ }
+ else
+ data->pad_length = 0u;
+
+ if (data->length < (real_payload_pos + data->pad_length))
+ return mhd_H2_F_DEC_CONN_ERR_PROT;
+ if (payload_buff_size < data->length)
+ return mhd_H2_F_DEC_F_PAYLOAD_INCOMPLETE;
+
+ frame_payload->data = (char *) payload_buff + real_payload_pos;
+ frame_payload->size =
+ (size_t) (data->length - real_payload_pos - data->pad_length);
+
+ return mhd_H2_F_DEC_OK;
+}
+
+
+/**
+ * Decode a HEADERS frame.
+ *
+ * Validates sizes data and flags data for valid combinations.
+ *
+ * @param payload_buff_size the available input bytes in the @a payload_buff
+ * @param payload_buff the pointer to the beginning of the frame payload
+ * @param flags the frame flags byte
+ * @param[in,out] frame_info the frame information;
+ * updated with HEADERS details
+ * @param[out] frame_payload set to the final payload slice that starts after
+ * any extra header fields and ends before padding
+ * bytes (if any)
+ * @return #mhd_H2_F_DEC_OK on success,
+ * or a relevant decode error code
+ */
+static MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_IN_SIZE_ (2,1)
+MHD_FN_PAR_INOUT_ (4) MHD_FN_PAR_OUT_ (5)
+enum mhd_H2FrameDecodeResult
+frame_decode_headers (size_t payload_buff_size,
+ uint8_t *restrict payload_buff,
+ uint_least8_t flags,
+ union mhd_H2FrameUnion *restrict frame_info,
+ struct mhd_Buffer *restrict frame_payload)
+{
+ struct mhd_H2FrameHeadersInfo *const headers =
+ &(frame_info->headers);
+ size_t real_payload_pos;
+
+ mhd_assert (mhd_H2_FRAME_HEADERS_ID == headers->type);
+
+ if (0u == headers->stream_id)
+ return mhd_H2_F_DEC_CONN_ERR_PROT;
+
+ headers->priority = (0 != (flags & mhd_FFLAG_PRIORITY));
+ headers->padded = (0 != (flags & mhd_FFLAG_PADDED));
+ headers->end_headers = (0 != (flags & mhd_FFLAG_END_HEADERS));
+ headers->end_stream = (0 != (flags & mhd_FFLAG_END_STREAM));
+
+ real_payload_pos = 0u;
+ if (headers->padded)
+ {
+ if (real_payload_pos >= headers->length)
+ return mhd_H2_F_DEC_CONN_ERR_PROT;
+ if (real_payload_pos >= payload_buff_size)
+ return mhd_H2_F_DEC_F_HEADER_INCOMPLETE;
+ headers->pad_length = (uint_least8_t) payload_buff[real_payload_pos++];
+ }
+ else
+ headers->pad_length = 0u;
+
+ if (headers->priority)
+ {
+ uint_least32_t stream_dep_id;
+ if ((real_payload_pos + 5u) > headers->length)
+ return mhd_H2_F_DEC_CONN_ERR_PROT;
+ if ((real_payload_pos + 5u) > payload_buff_size)
+ return mhd_H2_F_DEC_F_HEADER_INCOMPLETE;
+ stream_dep_id = mhd_GET_32BIT_BE_UNALIGN (payload_buff + real_payload_pos);
+ real_payload_pos += 4u;
+ headers->exclusive = (0 != (stream_dep_id & mhd_FFLAG_HEADERS_EXCLUSIVE));
+ headers->stream_dependency = (uint_least32_t)
+ (stream_dep_id & mhd_H2_STREAM_ID_MASK);
+ if (headers->stream_id == headers->stream_dependency)
+ return mhd_H2_F_DEC_STREAM_ERR_PROT;
+ /* Use "on-wire" 'weight' format. Add one to get a real number.
+ Do not bother handling calculations here as it is deprecated anyway. */
+ headers->weight = payload_buff[real_payload_pos++];
+ }
+ else
+ {
+ headers->exclusive = false;
+ headers->stream_dependency = 0u;
+ headers->weight = 0u;
+ }
+
+ if (headers->length < (real_payload_pos + headers->pad_length))
+ return mhd_H2_F_DEC_CONN_ERR_PROT;
+ if (payload_buff_size < headers->length)
+ return mhd_H2_F_DEC_F_PAYLOAD_INCOMPLETE;
+
+ frame_payload->data = (char *) payload_buff + real_payload_pos;
+ frame_payload->size =
+ (size_t) (headers->length - real_payload_pos - headers->pad_length);
+
+ return mhd_H2_F_DEC_OK;
+}
+
+
+/**
+ * Decode a PRIORITY frame.
+ *
+ * Validates the fixed size of the frame.
+ *
+ * @param payload_buff_size the available input bytes in the @a payload_buff
+ * @param payload_buff the pointer to the beginning of the frame payload
+ * @param[in,out] frame_info the frame information;
+ * updated with PRIORITY details
+ * @return #mhd_H2_F_DEC_OK on success,
+ * or a relevant decode error code
+ */
+static MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_IN_SIZE_ (2,1)
+MHD_FN_PAR_INOUT_ (3)
+enum mhd_H2FrameDecodeResult
+frame_decode_priority (size_t payload_buff_size,
+ uint8_t *restrict payload_buff,
+ union mhd_H2FrameUnion *restrict frame_info)
+{
+ struct mhd_H2FramePriorityInfo *const priority =
+ &(frame_info->priority);
+ uint_least32_t stream_dep_id;
+
+ mhd_assert (mhd_H2_FRAME_PRIORITY_ID == priority->type);
+
+ if (0u == priority->stream_id)
+ return mhd_H2_F_DEC_CONN_ERR_PROT;
+ if (5u != priority->length)
+ return mhd_H2_F_DEC_STREAM_ERR_F_SIZE;
+ if (5u > payload_buff_size)
+ return mhd_H2_F_DEC_F_HEADER_INCOMPLETE;
+
+ stream_dep_id = mhd_GET_32BIT_BE_UNALIGN (payload_buff + 0u);
+ priority->exclusive = (0 != (stream_dep_id & mhd_FFLAG_HEADERS_EXCLUSIVE));
+ priority->stream_dependency = (uint_least32_t)
+ (stream_dep_id & mhd_H2_STREAM_ID_MASK);
+ /* Use "on-wire" 'weight' format. Add one to get a real number.
+ Do not bother handling calculations here as it is deprecated anyway. */
+ priority->weight = payload_buff[4];
+
+ if (priority->stream_id == priority->stream_dependency)
+ return mhd_H2_F_DEC_STREAM_ERR_PROT;
+
+ return mhd_H2_F_DEC_OK;
+}
+
+
+/**
+ * Decode an RST_STREAM frame.
+ *
+ * Validates the fixed size of the frame.
+ *
+ * @param payload_buff_size the available input bytes in the @a payload_buff
+ * @param payload_buff the pointer to the beginning of the frame payload
+ * @param[in,out] frame_info the frame information;
+ * updated with RST_STREAM details
+ * @return #mhd_H2_F_DEC_OK on success,
+ * or a relevant decode error code
+ */
+static MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_IN_SIZE_ (2,1)
+MHD_FN_PAR_INOUT_ (3)
+enum mhd_H2FrameDecodeResult
+frame_decode_rst_stream (size_t payload_buff_size,
+ uint8_t *restrict payload_buff,
+ union mhd_H2FrameUnion *restrict frame_info)
+{
+ struct mhd_H2FrameRstStreamInfo *const rst_stream =
+ &(frame_info->rst_stream);
+ uint_fast32_t load_buff32b;
+
+ mhd_assert (mhd_H2_FRAME_RST_STREAM_ID == rst_stream->type);
+
+ if (0u == rst_stream->stream_id)
+ return mhd_H2_F_DEC_CONN_ERR_PROT;
+ if (4u != rst_stream->length)
+ return mhd_H2_F_DEC_STREAM_ERR_F_SIZE;
+ if (4u > payload_buff_size)
+ return mhd_H2_F_DEC_F_HEADER_INCOMPLETE;
+
+ load_buff32b = mhd_GET_32BIT_BE_UNALIGN (payload_buff + 0u);
+ rst_stream->error_code = (enum mhd_H2ErrorCode) load_buff32b;
+
+ return mhd_H2_F_DEC_OK;
+}
+
+
+/**
+ * Decode a SETTINGS frame.
+ *
+ * Validates stream identifier (must be zero), ACK semantics and that the
+ * length is a multiple of 6; exposes raw SETTINGS pairs via @a frame_payload
+ *
+ * @param payload_buff_size the available input bytes in the @a payload_buff
+ * @param payload_buff the pointer to the beginning of the frame payload
+ * @param flags the frame flags byte
+ * @param[in,out] frame_info the frame information;
+ * updated with SETTINGS details
+ * @param[out] frame_payload set to the contiguous sequence of 6-byte pairs
+ * @return #mhd_H2_F_DEC_OK on success,
+ * or a relevant decode error code
+ */
+static MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_IN_SIZE_ (2,1)
+MHD_FN_PAR_INOUT_ (4) MHD_FN_PAR_OUT_ (5)
+enum mhd_H2FrameDecodeResult
+frame_decode_settings (size_t payload_buff_size,
+ uint8_t *restrict payload_buff,
+ uint_least8_t flags,
+ union mhd_H2FrameUnion *restrict frame_info,
+ struct mhd_Buffer *restrict frame_payload)
+{
+ struct mhd_H2FrameSettingsInfo *const settings =
+ &(frame_info->settings);
+
+ mhd_assert (mhd_H2_FRAME_SETTINGS_ID == settings->type);
+
+ if (0u != settings->stream_id)
+ return mhd_H2_F_DEC_CONN_ERR_PROT;
+
+ settings->ack = (0 != (flags & mhd_FFLAG_ACK));
+
+ if (settings->ack && (0u != settings->length))
+ return mhd_H2_F_DEC_CONN_ERR_F_SIZE;
+
+ if (0u != (settings->length % 6))
+ return mhd_H2_F_DEC_CONN_ERR_F_SIZE;
+
+ if (payload_buff_size < settings->length)
+ return mhd_H2_F_DEC_F_PAYLOAD_INCOMPLETE;
+
+ frame_payload->data = (char *) payload_buff;
+ frame_payload->size = settings->length;
+
+ return mhd_H2_F_DEC_OK;
+}
+
+
+/**
+ * Decode a PUSH_PROMISE frame.
+ *
+ * Validates sizes and exposes the header block fragment via @a frame_payload.
+ *
+ * @param payload_buff_size the available input bytes in the @a payload_buff
+ * @param payload_buff the pointer to the beginning of the frame payload
+ * @param flags the frame flags byte
+ * @param[in,out] frame_info the frame information;
+ * updated with PUSH_PROMISE details
+ * @param[out] frame_payload set to the header block fragment slice within
+ * payload
+ * @return #mhd_H2_F_DEC_OK on success,
+ * or a relevant decode error code
+ */
+static MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_IN_SIZE_ (2,1)
+MHD_FN_PAR_INOUT_ (4) MHD_FN_PAR_OUT_ (5)
+enum mhd_H2FrameDecodeResult
+frame_decode_push_promise (size_t payload_buff_size,
+ uint8_t *restrict payload_buff,
+ uint_least8_t flags,
+ union mhd_H2FrameUnion *restrict frame_info,
+ struct mhd_Buffer *restrict frame_payload)
+{
+ struct mhd_H2FramePushPromiseInfo *const push_promise =
+ &(frame_info->push_promise);
+ size_t real_payload_pos;
+
+ mhd_assert (mhd_H2_FRAME_PUSH_PROMISE_ID == push_promise->type);
+
+ push_promise->padded = (0 != (flags & mhd_FFLAG_PADDED));
+ push_promise->end_headers = (0 != (flags & mhd_FFLAG_END_HEADERS));
+
+ real_payload_pos = 0u;
+ if (push_promise->padded)
+ {
+ if (real_payload_pos >= push_promise->length)
+ return mhd_H2_F_DEC_CONN_ERR_PROT;
+ if (real_payload_pos >= payload_buff_size)
+ return mhd_H2_F_DEC_F_HEADER_INCOMPLETE;
+ push_promise->pad_length =
+ (uint_least8_t) payload_buff[real_payload_pos++];
+ }
+ else
+ push_promise->pad_length = 0u;
+
+ if (real_payload_pos + 4u > push_promise->length)
+ return mhd_H2_F_DEC_CONN_ERR_PROT;
+ if (real_payload_pos + 4u > payload_buff_size)
+ return mhd_H2_F_DEC_F_HEADER_INCOMPLETE;
+
+ push_promise->promised_stream_id =
+ (uint_least32_t)
+ (mhd_GET_32BIT_BE_UNALIGN (payload_buff + real_payload_pos)
+ & mhd_H2_STREAM_ID_MASK);
+ real_payload_pos += 4u;
+
+ if (push_promise->length < (real_payload_pos + push_promise->pad_length))
+ return mhd_H2_F_DEC_CONN_ERR_PROT;
+ if (0u == push_promise->stream_id)
+ return mhd_H2_F_DEC_CONN_ERR_PROT;
+ if (0u == push_promise->promised_stream_id)
+ return mhd_H2_F_DEC_CONN_ERR_PROT;
+ if (payload_buff_size < push_promise->length)
+ return mhd_H2_F_DEC_F_PAYLOAD_INCOMPLETE;
+
+ frame_payload->data = (char *) payload_buff + real_payload_pos;
+ frame_payload->size =
+ (size_t)
+ (push_promise->length - real_payload_pos - push_promise->pad_length);
+
+ return mhd_H2_F_DEC_OK;
+}
+
+
+/**
+ * Decode a PING frame.
+ *
+ * Validates stream identifier (must be zero) and fixed size (8).
+ *
+ * @param payload_buff_size the available input bytes in the @a payload_buff
+ * @param payload_buff the pointer to the beginning of the frame payload
+ * @param flags the frame flags byte
+ * @param[in,out] frame_info the frame information;
+ * updated with PING details
+ * @return #mhd_H2_F_DEC_OK on success,
+ * or a relevant decode error code
+ */
+static MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_IN_SIZE_ (2,1)
+MHD_FN_PAR_INOUT_ (4)
+enum mhd_H2FrameDecodeResult
+frame_decode_ping (size_t payload_buff_size,
+ uint8_t *restrict payload_buff,
+ uint_least8_t flags,
+ union mhd_H2FrameUnion *restrict frame_info)
+{
+ struct mhd_H2FramePingInfo *const ping =
+ &(frame_info->ping);
+
+ mhd_assert (mhd_H2_FRAME_PING_ID == ping->type);
+
+ ping->ack = (0 != (flags & mhd_FFLAG_ACK));
+
+ if (0u != ping->stream_id)
+ return mhd_H2_F_DEC_CONN_ERR_PROT;
+ if (8u != ping->length)
+ return mhd_H2_F_DEC_CONN_ERR_F_SIZE;
+ if (8u > payload_buff_size)
+ return mhd_H2_F_DEC_F_HEADER_INCOMPLETE;
+
+ memcpy (ping->opaque_data,
+ payload_buff,
+ 8u);
+
+ return mhd_H2_F_DEC_OK;
+}
+
+
+/**
+ * Decode a GOAWAY frame.
+ *
+ * Validates stream identifier (must be zero) and that length is at least 8.
+ *
+ * @param payload_buff_size the available input bytes in the @a payload_buff
+ * @param payload_buff the pointer to the beginning of the frame payload
+ * @param[in,out] frame_info the frame information;
+ * updated with GOAWAY details
+ * @param[out] frame_payload set to the optional debug data slice
+ * @return #mhd_H2_F_DEC_OK on success,
+ * or a relevant decode error code
+ */
+static MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_IN_SIZE_ (2,1)
+MHD_FN_PAR_INOUT_ (3) MHD_FN_PAR_OUT_ (4)
+enum mhd_H2FrameDecodeResult
+frame_decode_goaway (size_t payload_buff_size,
+ uint8_t *restrict payload_buff,
+ union mhd_H2FrameUnion *restrict frame_info,
+ struct mhd_Buffer *restrict frame_payload)
+{
+ struct mhd_H2FrameGoawayInfo *const goaway =
+ &(frame_info->goaway);
+ size_t real_payload_pos;
+ uint_fast32_t load_buff32b;
+
+ mhd_assert (mhd_H2_FRAME_GOAWAY_ID == goaway->type);
+
+ if (0u != goaway->stream_id)
+ return mhd_H2_F_DEC_CONN_ERR_PROT;
+ if (8u > goaway->length)
+ return mhd_H2_F_DEC_CONN_ERR_F_SIZE;
+ if (8u > payload_buff_size)
+ return mhd_H2_F_DEC_F_HEADER_INCOMPLETE;
+
+ real_payload_pos = 0u;
+ goaway->last_stream_id =
+ (uint_least32_t)
+ (mhd_GET_32BIT_BE_UNALIGN (payload_buff + real_payload_pos)
+ & mhd_MASK_31BITS);
+ real_payload_pos += 4u;
+ load_buff32b = mhd_GET_32BIT_BE_UNALIGN (payload_buff + real_payload_pos);
+ goaway->error_code = (enum mhd_H2ErrorCode) load_buff32b;
+ real_payload_pos += 4u;
+
+ frame_payload->data = (char *) payload_buff + real_payload_pos;
+ frame_payload->size = (size_t) (goaway->length - real_payload_pos);
+
+ return mhd_H2_F_DEC_OK;
+}
+
+
+/**
+ * Decode a WINDOW_UPDATE frame.
+ *
+ * Validates fixed size (4) and that the increment is non-zero.
+ *
+ * @param payload_buff_size the available input bytes in the @a payload_buff
+ * @param payload_buff the pointer to the beginning of the frame payload
+ * @param[in,out] frame_info the frame information;
+ * updated with WINDOW_UPDATE details
+ * @return #mhd_H2_F_DEC_OK on success,
+ * or a relevant decode error code
+ */
+static MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_IN_SIZE_ (2,1)
+MHD_FN_PAR_INOUT_ (3)
+enum mhd_H2FrameDecodeResult
+frame_decode_window_update (size_t payload_buff_size,
+ uint8_t *restrict payload_buff,
+ union mhd_H2FrameUnion *restrict frame_info)
+{
+ struct mhd_H2FrameWindowUpdateInfo *const window_update =
+ &(frame_info->window_update);
+
+ mhd_assert (mhd_H2_FRAME_WINDOW_UPDATE_ID == window_update->type);
+
+ if (4u != window_update->length)
+ return mhd_H2_F_DEC_CONN_ERR_F_SIZE;
+ if (4u > payload_buff_size)
+ return mhd_H2_F_DEC_F_HEADER_INCOMPLETE;
+
+ window_update->window_size_increment =
+ (uint_least32_t)
+ (mhd_GET_32BIT_BE_UNALIGN (payload_buff + 0u) & mhd_MASK_31BITS);
+
+ if (0u == window_update->window_size_increment)
+ return (0u == window_update->stream_id) ?
+ mhd_H2_F_DEC_CONN_ERR_PROT : mhd_H2_F_DEC_STREAM_ERR_PROT;
+
+ return mhd_H2_F_DEC_OK;
+}
+
+
+/**
+ * Decode a CONTINUATION frame.
+ *
+ * @param payload_buff_size the available input bytes in the @a payload_buff
+ * @param payload_buff the pointer to the beginning of the frame payload
+ * @param flags the frame flags byte
+ * @param[in,out] frame_info the frame information;
+ * updated with CONTINUATION details
+ * @param[out] frame_payload set to the continuation fragment slice
+ * @return #mhd_H2_F_DEC_OK on success,
+ * or a relevant decode error code
+ */
+static MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_IN_SIZE_ (2,1)
+MHD_FN_PAR_INOUT_ (4) MHD_FN_PAR_OUT_ (5)
+enum mhd_H2FrameDecodeResult
+frame_decode_continuation (size_t payload_buff_size,
+ uint8_t *restrict payload_buff,
+ uint_least8_t flags,
+ union mhd_H2FrameUnion *restrict frame_info,
+ struct mhd_Buffer *restrict frame_payload)
+{
+ struct mhd_H2FrameContinuationInfo *const continuation =
+ &(frame_info->continuation);
+
+ mhd_assert (mhd_H2_FRAME_CONTINUATION_ID == continuation->type);
+
+ if (0u == continuation->stream_id)
+ return mhd_H2_F_DEC_CONN_ERR_PROT;
+
+ continuation->end_headers = (0 != (flags & mhd_FFLAG_END_HEADERS));
+
+ if (payload_buff_size < continuation->length)
+ return mhd_H2_F_DEC_F_PAYLOAD_INCOMPLETE;
+
+ frame_payload->data = (char *) payload_buff;
+ frame_payload->size = continuation->length;
+
+ return mhd_H2_F_DEC_OK;
+}
+
+
+/**
+ * Decode an unknown frame type as opaque bytes.
+ *
+ * Exposes the entire payload via @a frame_payload if fully available
+ *
+ * @param payload_buff_size the available input bytes in the @a payload_buff
+ * @param payload_buff the pointer to the beginning of the frame payload
+ * @param[in,out] frame_info the frame information;
+ * only selector fields are used
+ * @param[out] frame_payload set to the raw payload slice
+ * @return #mhd_H2_F_DEC_OK on success,
+ * or a relevant decode error code
+ */
+static MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_IN_SIZE_ (2,1)
+MHD_FN_PAR_INOUT_ (3) MHD_FN_PAR_OUT_ (4)
+enum mhd_H2FrameDecodeResult
+frame_decode_unknown_type (size_t payload_buff_size,
+ uint8_t *restrict payload_buff,
+ union mhd_H2FrameUnion *restrict frame_info,
+ struct mhd_Buffer *restrict frame_payload)
+{
+ if (payload_buff_size < frame_info->selector.length)
+ return mhd_H2_F_DEC_F_PAYLOAD_INCOMPLETE;
+
+ frame_payload->data = (char *) payload_buff;
+ frame_payload->size = frame_info->selector.length;
+
+ return mhd_H2_F_DEC_OK;
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_IN_SIZE_ (2,1)
+MHD_FN_PAR_OUT_ (4) MHD_FN_PAR_OUT_ (5) enum mhd_H2FrameDecodeResult
+mhd_h2_frame_decode (size_t buff_size,
+ uint8_t *restrict buff,
+ uint_least32_t max_frame_size,
+ union mhd_H2FrameUnion *restrict frame_info,
+ struct mhd_Buffer *restrict frame_payload)
+{
+ uint_fast32_t len_and_type;
+ uint_least32_t length;
+ uint_least8_t type;
+ uint_least8_t flags;
+ uint_least32_t stream_id;
+
+ if (mhd_H2_FR_SIZE_MIN > buff_size)
+ return mhd_H2_F_DEC_F_HEADER_INCOMPLETE;
+
+ len_and_type = mhd_GET_32BIT_BE_UNALIGN (buff + 0u);
+
+ length = (uint_least32_t) (len_and_type >> 8u);
+ type = (uint_least8_t) (len_and_type & 0xFFu);
+
+ flags = buff[4];
+
+ stream_id = (mhd_GET_32BIT_BE_UNALIGN (buff + 5u) & mhd_H2_STREAM_ID_MASK);
+
+ frame_info->selector.length = length;
+ frame_info->selector.type = (enum mhd_H2FrameIDs) type;
+ frame_info->selector.stream_id = stream_id;
+
+ if (max_frame_size < length)
+ return mhd_H2_F_DEC_CONN_ERR_F_SIZE;
+
+ switch ((enum mhd_H2FrameIDs) type)
+ {
+ case mhd_H2_FRAME_IDS_DATA_ID:
+ return frame_decode_data (buff_size - mhd_H2_FR_SIZE_MIN,
+ buff + mhd_H2_FR_SIZE_MIN,
+ flags,
+ frame_info,
+ frame_payload);
+ case mhd_H2_FRAME_IDS_HEADERS_ID:
+ return frame_decode_headers (buff_size - mhd_H2_FR_SIZE_MIN,
+ buff + mhd_H2_FR_SIZE_MIN,
+ flags,
+ frame_info,
+ frame_payload);
+ case mhd_H2_FRAME_IDS_PRIORITY_ID:
+ frame_payload->size = 0u;
+ frame_payload->data = NULL;
+ return frame_decode_priority (buff_size - mhd_H2_FR_SIZE_MIN,
+ buff + mhd_H2_FR_SIZE_MIN,
+ frame_info);
+ case mhd_H2_FRAME_IDS_RST_STREAM_ID:
+ frame_payload->size = 0u;
+ frame_payload->data = NULL;
+ return frame_decode_rst_stream (buff_size - mhd_H2_FR_SIZE_MIN,
+ buff + mhd_H2_FR_SIZE_MIN,
+ frame_info);
+ case mhd_H2_FRAME_IDS_SETTINGS_ID:
+ return frame_decode_settings (buff_size - mhd_H2_FR_SIZE_MIN,
+ buff + mhd_H2_FR_SIZE_MIN,
+ flags,
+ frame_info,
+ frame_payload);
+ case mhd_H2_FRAME_IDS_PUSH_PROMISE_ID:
+ return frame_decode_push_promise (buff_size - mhd_H2_FR_SIZE_MIN,
+ buff + mhd_H2_FR_SIZE_MIN,
+ flags,
+ frame_info,
+ frame_payload);
+ case mhd_H2_FRAME_IDS_PING_ID:
+ frame_payload->size = 0u;
+ frame_payload->data = NULL;
+ return frame_decode_ping (buff_size - mhd_H2_FR_SIZE_MIN,
+ buff + mhd_H2_FR_SIZE_MIN,
+ flags,
+ frame_info);
+ case mhd_H2_FRAME_IDS_GOAWAY_ID:
+ return frame_decode_goaway (buff_size - mhd_H2_FR_SIZE_MIN,
+ buff + mhd_H2_FR_SIZE_MIN,
+ frame_info,
+ frame_payload);
+ case mhd_H2_FRAME_IDS_WINDOW_UPDATE_ID:
+ frame_payload->size = 0u;
+ frame_payload->data = NULL;
+ return frame_decode_window_update (buff_size - mhd_H2_FR_SIZE_MIN,
+ buff + mhd_H2_FR_SIZE_MIN,
+ frame_info);
+ case mhd_H2_FRAME_IDS_CONTINUATION_ID:
+ return frame_decode_continuation (buff_size - mhd_H2_FR_SIZE_MIN,
+ buff + mhd_H2_FR_SIZE_MIN,
+ flags,
+ frame_info,
+ frame_payload);
+ default:
+ break;
+ }
+ return frame_decode_unknown_type (buff_size - mhd_H2_FR_SIZE_MIN,
+ buff + mhd_H2_FR_SIZE_MIN,
+ frame_info,
+ frame_payload);
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_IN_ (1)
+size_t
+mhd_h2_frame_get_extra_hdr_size (
+ const union mhd_H2FrameUnion *restrict frame_info)
+{
+ size_t extra;
+ extra = 0u;
+ switch (frame_info->selector.type)
+ {
+ case mhd_H2_FRAME_IDS_DATA_ID:
+ if (frame_info->data.padded)
+ extra += 1u;
+ break;
+ case mhd_H2_FRAME_IDS_HEADERS_ID:
+ if (frame_info->headers.padded)
+ extra += 1u;
+ if (frame_info->headers.priority)
+ extra += 5u;
+ break;
+ case mhd_H2_FRAME_IDS_PRIORITY_ID:
+ extra += 5u;
+ break;
+ case mhd_H2_FRAME_IDS_RST_STREAM_ID:
+ extra += 4u;
+ break;
+ case mhd_H2_FRAME_IDS_SETTINGS_ID:
+ break;
+ case mhd_H2_FRAME_IDS_PUSH_PROMISE_ID:
+ if (frame_info->push_promise.padded)
+ extra += 1u;
+ extra += 4u;
+ break;
+ case mhd_H2_FRAME_IDS_PING_ID:
+ extra += mhd_H2_FR_FIXED_LEN_PING;
+ break;
+ case mhd_H2_FRAME_IDS_GOAWAY_ID:
+ extra += 8u;
+ break;
+ case mhd_H2_FRAME_IDS_WINDOW_UPDATE_ID:
+ extra += mhd_H2_FR_FIXED_LEN_WINDOW_UPDATE;
+ break;
+ case mhd_H2_FRAME_IDS_CONTINUATION_ID:
+ break;
+ default:
+ break;
+ }
+ return extra;
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_IN_ (1)
+size_t
+mhd_h2_frame_get_padding_size (
+ const union mhd_H2FrameUnion *restrict frame_info)
+{
+ switch (frame_info->selector.type)
+ {
+ case mhd_H2_FRAME_IDS_DATA_ID:
+ mhd_assert ((0u == frame_info->data.pad_length) || \
+ frame_info->data.padded);
+ return frame_info->data.pad_length;
+ case mhd_H2_FRAME_IDS_HEADERS_ID:
+ mhd_assert ((0u == frame_info->headers.pad_length) || \
+ frame_info->headers.padded);
+ return frame_info->headers.pad_length;
+ case mhd_H2_FRAME_IDS_PUSH_PROMISE_ID:
+ mhd_assert ((0u == frame_info->push_promise.pad_length) || \
+ frame_info->push_promise.padded);
+ return frame_info->push_promise.pad_length;
+ case mhd_H2_FRAME_IDS_PRIORITY_ID:
+ case mhd_H2_FRAME_IDS_RST_STREAM_ID:
+ case mhd_H2_FRAME_IDS_SETTINGS_ID:
+ case mhd_H2_FRAME_IDS_PING_ID:
+ case mhd_H2_FRAME_IDS_GOAWAY_ID:
+ case mhd_H2_FRAME_IDS_WINDOW_UPDATE_ID:
+ case mhd_H2_FRAME_IDS_CONTINUATION_ID:
+ default:
+ break;
+ }
+ return 0u;
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_INOUT_ (1) size_t
+mhd_h2_frame_set_payload_size (union mhd_H2FrameUnion *restrict frame_info,
+ size_t payload_size)
+{
+ uint_least32_t fr_length;
+
+ mhd_assert (frame_info->selector.length == \
+ (frame_info->selector.length & mhd_H2_FR_LENGTH_MASK));
+ mhd_assert (frame_info->selector.type == \
+ (((uint_least64_t) frame_info->selector.type) & 0xFFu));
+#ifndef NDEBUG
+ switch (frame_info->selector.type)
+ {
+ case mhd_H2_FRAME_IDS_PRIORITY_ID:
+ case mhd_H2_FRAME_IDS_RST_STREAM_ID:
+ case mhd_H2_FRAME_IDS_PING_ID:
+ case mhd_H2_FRAME_IDS_WINDOW_UPDATE_ID:
+ mhd_assert (0u == payload_size);
+ break;
+ case mhd_H2_FRAME_IDS_DATA_ID:
+ case mhd_H2_FRAME_IDS_HEADERS_ID:
+ case mhd_H2_FRAME_IDS_SETTINGS_ID:
+ case mhd_H2_FRAME_IDS_PUSH_PROMISE_ID:
+ case mhd_H2_FRAME_IDS_GOAWAY_ID:
+ case mhd_H2_FRAME_IDS_CONTINUATION_ID:
+ default:
+ break;
+ }
+#endif /* ! NDEBUG */
+ mhd_assert (frame_info->selector.length == \
+ (frame_info->selector.length & mhd_H2_FR_LENGTH_MASK));
+
+ fr_length = (uint_least32_t)
+ (mhd_h2_frame_get_extra_hdr_size (frame_info)
+ + mhd_h2_frame_get_padding_size (frame_info)
+ + payload_size);
+
+ mhd_assert (fr_length == (fr_length & mhd_H2_FR_LENGTH_MASK));
+
+ frame_info->selector.length = fr_length;
+
+ return (size_t) (mhd_H2_FR_HDR_BASE_SIZE + (size_t) fr_length);
+}
+
+
+/**
+ * Encode DATA extra header and flags.
+ *
+ * Does not write payload or trailing pad bytes.
+ *
+ * @param frame_info the DATA frame information
+ * @param[out] flags the pointer to the header flags byte to update
+ * @param out_extra_hdr_size available space in @a out_extra_hdr
+ * @param[out] out_extra_hdr the output buffer for extra header bytes
+ * @return the size of the frame basic header plus the size of the frame extra
+ * header written,
+ * or 0 if the output buffer is too small
+ */
+static MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_IN_ (1)
+MHD_FN_PAR_OUT_ (2) MHD_FN_PAR_OUT_SIZE_ (4,3) size_t
+frame_hdr_encode_data (const union mhd_H2FrameUnion *restrict frame_info,
+ uint8_t *restrict flags,
+ const size_t out_extra_hdr_size,
+ uint8_t *restrict out_extra_hdr)
+{
+ const struct mhd_H2FrameDataInfo *const data =
+ &(frame_info->data);
+ size_t extra_hdr_pos;
+
+ mhd_assert (mhd_H2_FRAME_DATA_ID == data->type);
+
+ mhd_assert (0u != data->stream_id);
+ mhd_assert ((0u == data->pad_length) ||
+ (data->padded));
+
+ *flags = (uint8_t) (data->padded ? mhd_FFLAG_PADDED : 0u);
+ *flags |= (uint8_t) (data->end_stream ? mhd_FFLAG_END_STREAM : 0u);
+
+ extra_hdr_pos = 0u;
+ if (data->padded)
+ {
+ if (out_extra_hdr_size <= extra_hdr_pos)
+ return 0u;
+ out_extra_hdr[extra_hdr_pos++] = (uint8_t) data->pad_length;
+ }
+
+ mhd_assert (data->length >= (extra_hdr_pos + data->pad_length));
+
+ return mhd_H2_FR_HDR_BASE_SIZE + extra_hdr_pos;
+}
+
+
+/**
+ * Encode HEADERS extra header and flags.
+ *
+ * @param frame_info the HEADERS frame information
+ * @param[out] flags the pointer to the header flags byte to update
+ * @param out_extra_hdr_size available space in @a out_extra_hdr
+ * @param[out] out_extra_hdr the output buffer for extra header bytes
+ * @return the size of the frame basic header plus the size of the frame extra
+ * header written,
+ * or 0 if the output buffer is too small
+ */
+static MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_IN_ (1)
+MHD_FN_PAR_OUT_ (2) MHD_FN_PAR_OUT_SIZE_ (4,3) size_t
+frame_hdr_encode_headers (const union mhd_H2FrameUnion *restrict frame_info,
+ uint8_t *restrict flags,
+ const size_t out_extra_hdr_size,
+ uint8_t *restrict out_extra_hdr)
+{
+ const struct mhd_H2FrameHeadersInfo *const headers =
+ &(frame_info->headers);
+ size_t extra_hdr_pos;
+
+ mhd_assert (mhd_H2_FRAME_HEADERS_ID == headers->type);
+
+ mhd_assert (0u != headers->stream_id);
+ mhd_assert ((0u == headers->pad_length) ||
+ (headers->padded));
+
+ *flags = (uint8_t) (headers->priority ? mhd_FFLAG_PRIORITY : 0u);
+ *flags |= (uint8_t) (headers->padded ? mhd_FFLAG_PADDED : 0u);
+ *flags |= (uint8_t) (headers->end_headers ? mhd_FFLAG_END_HEADERS : 0u);
+ *flags |= (uint8_t) (headers->end_stream ? mhd_FFLAG_END_STREAM : 0u);
+
+ extra_hdr_pos = 0u;
+ if (headers->padded)
+ {
+ if (out_extra_hdr_size <= extra_hdr_pos)
+ return 0u;
+ out_extra_hdr[extra_hdr_pos++] = (uint8_t) headers->pad_length;
+ }
+
+ if (headers->priority)
+ {
+ uint32_t excl_n_strm_dep;
+ if (out_extra_hdr_size < (extra_hdr_pos + 5u))
+ return 0u;
+
+ excl_n_strm_dep = (uint32_t)
+ (headers->stream_dependency & mhd_H2_STREAM_ID_MASK);
+ excl_n_strm_dep |= (uint32_t)
+ (headers->exclusive ? mhd_FFLAG_HEADERS_EXCLUSIVE : 0u);
+ mhd_PUT_32BIT_BE_UNALIGN (out_extra_hdr + extra_hdr_pos,
+ excl_n_strm_dep);
+ extra_hdr_pos += 4u;
+
+ /* Use "on-wire" 'weight' format. */
+ out_extra_hdr[extra_hdr_pos++] = (uint8_t) headers->weight;
+ }
+
+ mhd_assert (headers->length >= (extra_hdr_pos + headers->pad_length));
+
+ return mhd_H2_FR_HDR_BASE_SIZE + extra_hdr_pos;
+}
+
+
+/**
+ * Encode PRIORITY payload into the extra header area.
+ *
+ * @param frame_info the PRIORITY frame information
+ * @param out_extra_hdr_size available space in @a out_extra_hdr
+ * @param[out] out_extra_hdr the output buffer for the 5-byte payload
+ * @return the size of the frame basic header plus the size of the frame extra
+ * header written,
+ * or 0 if the output buffer is too small
+ */
+static MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_IN_ (1)
+MHD_FN_PAR_OUT_SIZE_ (3,2) size_t
+frame_hdr_encode_priority (const union mhd_H2FrameUnion *restrict frame_info,
+ const size_t out_extra_hdr_size,
+ uint8_t *restrict out_extra_hdr)
+{
+ const struct mhd_H2FramePriorityInfo *const priority =
+ &(frame_info->priority);
+ uint32_t excl_n_strm_dep;
+ size_t extra_hdr_pos;
+
+ mhd_assert (mhd_H2_FRAME_PRIORITY_ID == priority->type);
+
+ mhd_assert (0u != priority->stream_id);
+ mhd_assert (priority->stream_id != priority->stream_dependency);
+
+ extra_hdr_pos = 0u;
+
+ if (out_extra_hdr_size < (extra_hdr_pos + 5u))
+ return 0u;
+
+ excl_n_strm_dep = (uint32_t)
+ (priority->stream_dependency & mhd_H2_STREAM_ID_MASK);
+ excl_n_strm_dep |= (uint32_t)
+ (priority->exclusive ? mhd_FFLAG_HEADERS_EXCLUSIVE : 0u);
+ mhd_PUT_32BIT_BE_UNALIGN (out_extra_hdr + extra_hdr_pos,
+ excl_n_strm_dep);
+ extra_hdr_pos += 4u;
+
+ /* Use "on-wire" 'weight' format. */
+ out_extra_hdr[extra_hdr_pos++] = (uint8_t) priority->weight;
+
+ mhd_assert (priority->length == extra_hdr_pos);
+ mhd_assert (mhd_H2_FR_FIXED_LEN_PRIORITY == extra_hdr_pos);
+
+ return mhd_H2_FR_HDR_BASE_SIZE + extra_hdr_pos;
+}
+
+
+/**
+ * Encode RST_STREAM payload into the extra header area.
+ *
+ * @param frame_info the RST_STREAM frame information
+ * @param out_extra_hdr_size available space in @a out_extra_hdr
+ * @param[out] out_extra_hdr the output buffer for the 4-byte payload
+ * @return the size of the frame basic header plus the size of the frame extra
+ * header written,
+ * or 0 if the output buffer is too small
+ */
+static MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_IN_ (1)
+MHD_FN_PAR_OUT_SIZE_ (3,2) size_t
+frame_hdr_encode_rst_stream (const union mhd_H2FrameUnion *restrict frame_info,
+ const size_t out_extra_hdr_size,
+ uint8_t *restrict out_extra_hdr)
+{
+ const struct mhd_H2FrameRstStreamInfo *const rst_stream =
+ &(frame_info->rst_stream);
+ size_t extra_hdr_pos;
+
+ mhd_assert (mhd_H2_FRAME_RST_STREAM_ID == rst_stream->type);
+
+ mhd_assert (0u != rst_stream->stream_id);
+
+ extra_hdr_pos = 0u;
+
+ if (out_extra_hdr_size < (extra_hdr_pos + 4u))
+ return 0u;
+
+ mhd_PUT_32BIT_BE_UNALIGN (out_extra_hdr + extra_hdr_pos,
+ (uint32_t) rst_stream->error_code);
+ extra_hdr_pos += 4u;
+
+ mhd_assert (rst_stream->length == extra_hdr_pos);
+ mhd_assert (mhd_H2_FR_FIXED_LEN_RST_STREAM == extra_hdr_pos);
+
+ return mhd_H2_FR_HDR_BASE_SIZE + extra_hdr_pos;
+}
+
+
+/**
+ * Encode SETTINGS header flags.
+ *
+ * SETTINGS has no extra header bytes. Any SETTINGS parameters belong to the
+ * payload and are not written by this function.
+ *
+ * @param frame_info the SETTINGS frame information
+ * @param[out] flags the pointer to the header flags byte to update
+ * @return the size of the frame basic header plus the size of the frame extra
+ * header written
+ */
+static MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_IN_ (1)
+MHD_FN_PAR_OUT_ (2) size_t
+frame_hdr_encode_settings (const union mhd_H2FrameUnion *restrict frame_info,
+ uint8_t *restrict flags)
+{
+ const struct mhd_H2FrameSettingsInfo *const settings =
+ &(frame_info->settings);
+
+ mhd_assert (mhd_H2_FRAME_SETTINGS_ID == settings->type);
+
+ mhd_assert (0u == settings->stream_id);
+ mhd_assert ((! settings->ack) || (0u == settings->length));
+ mhd_assert (0u == (settings->length % 6));
+
+ *flags = (uint8_t) (settings->ack ? mhd_FFLAG_ACK : 0u);
+
+ return mhd_H2_FR_HDR_BASE_SIZE;
+}
+
+
+/**
+ * Encode PUSH_PROMISE extra header and flags.
+ *
+ * This function does not write the header block fragment or trailing
+ * pad bytes.
+ *
+ * @param frame_info the PUSH_PROMISE frame information
+ * @param[out] flags the pointer to the header flags byte to update
+ * @param out_extra_hdr_size available space in @a out_extra_hdr
+ * @param[out] out_extra_hdr the output buffer for extra header bytes
+ * @return the size of the frame basic header plus the size of the frame extra
+ * header written,
+ * or 0 if the output buffer is too small
+ */
+static MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_IN_ (1)
+MHD_FN_PAR_OUT_ (2) MHD_FN_PAR_OUT_SIZE_ (4,3) size_t
+frame_hdr_encode_push_promise (
+ const union mhd_H2FrameUnion *restrict frame_info,
+ uint8_t *restrict flags,
+ const size_t out_extra_hdr_size,
+ uint8_t *restrict out_extra_hdr)
+{
+ const struct mhd_H2FramePushPromiseInfo *const push_promise =
+ &(frame_info->push_promise);
+ size_t extra_hdr_pos;
+
+ mhd_assert (mhd_H2_FRAME_PUSH_PROMISE_ID == push_promise->type);
+
+ mhd_assert (0u != push_promise->stream_id);
+ mhd_assert ((0u == push_promise->pad_length) ||
+ (push_promise->padded));
+ mhd_assert (0u != push_promise->promised_stream_id);
+ mhd_assert (push_promise->promised_stream_id ==
+ (push_promise->promised_stream_id & mhd_H2_STREAM_ID_MASK));
+
+ *flags = (uint8_t) (push_promise->padded ? mhd_FFLAG_PADDED : 0u);
+ *flags |= (uint8_t) (push_promise->end_headers ? mhd_FFLAG_END_HEADERS : 0u);
+
+ extra_hdr_pos = 0u;
+ if (push_promise->padded)
+ {
+ if (out_extra_hdr_size <= extra_hdr_pos)
+ return 0u;
+ out_extra_hdr[extra_hdr_pos++] = (uint8_t) push_promise->pad_length;
+ }
+
+ if (out_extra_hdr_size < (extra_hdr_pos + 4u))
+ return 0u;
+
+ mhd_PUT_32BIT_BE_UNALIGN (out_extra_hdr + extra_hdr_pos,
+ (uint32_t) (push_promise->promised_stream_id
+ & mhd_H2_STREAM_ID_MASK));
+ extra_hdr_pos += 4u;
+
+ mhd_assert (push_promise->length >=
+ (extra_hdr_pos + push_promise->pad_length));
+
+ return mhd_H2_FR_HDR_BASE_SIZE + extra_hdr_pos;
+}
+
+
+/**
+ * Encode PING payload and flags.
+ *
+ * @param frame_info the PING frame information
+ * @param[out] flags the pointer to the header flags byte to update
+ * @param out_extra_hdr_size available space in @a out_extra_hdr
+ * @param[out] out_extra_hdr the output buffer for the 8-byte payload
+ * @return the size of the frame basic header plus the size of the frame extra
+ * header written,
+ * or 0 if the output buffer is too small
+ */
+static MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_IN_ (1)
+MHD_FN_PAR_OUT_ (2) MHD_FN_PAR_OUT_SIZE_ (4,3) size_t
+frame_hdr_encode_ping (const union mhd_H2FrameUnion *restrict frame_info,
+ uint8_t *restrict flags,
+ const size_t out_extra_hdr_size,
+ uint8_t *restrict out_extra_hdr)
+{
+ const struct mhd_H2FramePingInfo *const ping =
+ &(frame_info->ping);
+ size_t extra_hdr_pos;
+
+ mhd_assert (mhd_H2_FRAME_PING_ID == ping->type);
+
+ mhd_assert (0u == ping->stream_id);
+
+ *flags = (uint8_t) (ping->ack ? mhd_FFLAG_ACK : 0u);
+
+ extra_hdr_pos = 0u;
+
+ if (out_extra_hdr_size < (extra_hdr_pos + 8u))
+ return 0u;
+
+ memcpy (out_extra_hdr,
+ ping->opaque_data,
+ 8u);
+ extra_hdr_pos += 8u;
+
+ mhd_assert (ping->length == extra_hdr_pos);
+ mhd_assert (mhd_H2_FR_FIXED_LEN_PING == extra_hdr_pos);
+
+ return mhd_H2_FR_HDR_BASE_SIZE + extra_hdr_pos;
+}
+
+
+/**
+ * Encode GOAWAY fixed fields into the extra header area.
+ *
+ * Optional debug data belongs to the payload and is not written by this
+ * function.
+ *
+ * @param frame_info the GOAWAY frame information
+ * @param out_extra_hdr_size available space in @a out_extra_hdr
+ * @param[out] out_extra_hdr the output buffer for the 8-byte fixed fields
+ * @return the size of the frame basic header plus the size of the frame extra
+ * header written,
+ * or 0 if the output buffer is too small
+ */
+static MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_IN_ (1)
+MHD_FN_PAR_OUT_SIZE_ (3,2) size_t
+frame_hdr_encode_goaway (const union mhd_H2FrameUnion *restrict frame_info,
+ const size_t out_extra_hdr_size,
+ uint8_t *restrict out_extra_hdr)
+{
+ const struct mhd_H2FrameGoawayInfo *const goaway =
+ &(frame_info->goaway);
+ size_t extra_hdr_pos;
+
+ mhd_assert (mhd_H2_FRAME_GOAWAY_ID == goaway->type);
+
+ mhd_assert (0u == goaway->stream_id);
+
+ extra_hdr_pos = 0u;
+
+ if (out_extra_hdr_size < (extra_hdr_pos + 8u))
+ return 0u;
+
+ mhd_PUT_32BIT_BE_UNALIGN (out_extra_hdr + extra_hdr_pos,
+ (uint32_t) (goaway->last_stream_id
+ & mhd_H2_STREAM_ID_MASK));
+ extra_hdr_pos += 4u;
+
+ mhd_PUT_32BIT_BE_UNALIGN (out_extra_hdr + extra_hdr_pos,
+ (uint32_t) goaway->error_code);
+ extra_hdr_pos += 4u;
+
+ mhd_assert (goaway->length >= extra_hdr_pos);
+
+ return mhd_H2_FR_HDR_BASE_SIZE + extra_hdr_pos;
+}
+
+
+/**
+ * Encode WINDOW_UPDATE payload into the extra header area.
+ *
+ * @param frame_info the WINDOW_UPDATE frame information
+ * @param out_extra_hdr_size available space in @a out_extra_hdr
+ * @param[out] out_extra_hdr the output buffer for the 4-byte payload
+ * @return the size of the frame basic header plus the size of the frame extra
+ * header written,
+ * or 0 if the output buffer is too small
+ */
+static MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_IN_ (1)
+MHD_FN_PAR_OUT_SIZE_ (3,2) size_t
+frame_hdr_encode_window_update (
+ const union mhd_H2FrameUnion *restrict frame_info,
+ const size_t out_extra_hdr_size,
+ uint8_t *restrict out_extra_hdr)
+{
+ const struct mhd_H2FrameWindowUpdateInfo *const window_update =
+ &(frame_info->window_update);
+ size_t extra_hdr_pos;
+
+ mhd_assert (mhd_H2_FRAME_WINDOW_UPDATE_ID == window_update->type);
+ mhd_assert (0u != window_update->window_size_increment);
+ mhd_assert (window_update->window_size_increment ==
+ (window_update->window_size_increment & mhd_MASK_31BITS));
+
+ extra_hdr_pos = 0u;
+
+ if (out_extra_hdr_size < (extra_hdr_pos + 4u))
+ return 0u;
+
+ mhd_PUT_32BIT_BE_UNALIGN (out_extra_hdr + extra_hdr_pos,
+ (uint32_t) (window_update->window_size_increment
+ & mhd_MASK_31BITS));
+ extra_hdr_pos += 4u;
+
+ mhd_assert (window_update->length >= extra_hdr_pos);
+ mhd_assert (mhd_H2_FR_FIXED_LEN_WINDOW_UPDATE == extra_hdr_pos);
+
+ return mhd_H2_FR_HDR_BASE_SIZE + extra_hdr_pos;
+}
+
+
+/**
+ * Encode CONTINUATION header flags.
+ *
+ * CONTINUATION has no extra header bytes.
+ *
+ * @param frame_info the CONTINUATION frame information
+ * @param[out] flags the pointer to the header flags byte to update
+ * @return the size of the frame basic header plus the size of the frame extra
+ * header written
+ */
+static MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_IN_ (1)
+MHD_FN_PAR_OUT_ (2) size_t
+frame_hdr_encode_continuation (
+ const union mhd_H2FrameUnion *restrict frame_info,
+ uint8_t *restrict flags)
+{
+ const struct mhd_H2FrameContinuationInfo *const continuation =
+ &(frame_info->continuation);
+
+ mhd_assert (mhd_H2_FRAME_CONTINUATION_ID == continuation->type);
+
+ mhd_assert (0u != continuation->stream_id);
+
+ *flags = (uint8_t) (continuation->end_headers ? mhd_FFLAG_END_HEADERS : 0u);
+
+ return mhd_H2_FR_HDR_BASE_SIZE;
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_IN_ (1)
+MHD_FN_PAR_OUT_SIZE_ (3,2) size_t
+mhd_h2_frame_hdr_encode (const union mhd_H2FrameUnion *restrict frame_info,
+ size_t out_buff_size,
+ uint8_t *restrict out_buff)
+{
+ uint32_t len_and_type;
+
+ mhd_assert (frame_info->selector.length == \
+ (frame_info->selector.length & mhd_H2_FR_LENGTH_MASK));
+ mhd_assert (frame_info->selector.type == \
+ (((uint_least64_t) frame_info->selector.type) & 0xFFu));
+ mhd_assert (frame_info->selector.stream_id == \
+ (frame_info->selector.stream_id & mhd_H2_STREAM_ID_MASK));
+
+ if (mhd_H2_FR_SIZE_MIN > out_buff_size)
+ return 0u;
+
+ len_and_type = (uint_least8_t) (frame_info->selector.type & 0xFFu);
+ len_and_type |=
+ (uint32_t)
+ ((((uint_least32_t) frame_info->selector.length) << 8u)
+ & mhd_H2_FR_LENGTH_MASK);
+
+ mhd_PUT_32BIT_BE_UNALIGN (out_buff + 0u,
+ len_and_type);
+ out_buff[4] = 0u; /* flags */
+ mhd_PUT_32BIT_BE_UNALIGN (out_buff + 5u,
+ frame_info->selector.stream_id
+ & mhd_H2_STREAM_ID_MASK);
+
+ switch (frame_info->selector.type)
+ {
+ case mhd_H2_FRAME_IDS_DATA_ID:
+ return frame_hdr_encode_data (frame_info,
+ out_buff + 4u,
+ out_buff_size - mhd_H2_FR_HDR_BASE_SIZE,
+ out_buff + mhd_H2_FR_HDR_BASE_SIZE);
+ case mhd_H2_FRAME_IDS_HEADERS_ID:
+ return frame_hdr_encode_headers (frame_info,
+ out_buff + 4u,
+ out_buff_size - mhd_H2_FR_HDR_BASE_SIZE,
+ out_buff + mhd_H2_FR_HDR_BASE_SIZE);
+ case mhd_H2_FRAME_IDS_PRIORITY_ID:
+ return frame_hdr_encode_priority (frame_info,
+ out_buff_size - mhd_H2_FR_HDR_BASE_SIZE,
+ out_buff + mhd_H2_FR_HDR_BASE_SIZE);
+ case mhd_H2_FRAME_IDS_RST_STREAM_ID:
+ return frame_hdr_encode_rst_stream (frame_info,
+ out_buff_size - mhd_H2_FR_HDR_BASE_SIZE,
+ out_buff + mhd_H2_FR_HDR_BASE_SIZE);
+ case mhd_H2_FRAME_IDS_SETTINGS_ID:
+ return frame_hdr_encode_settings (frame_info,
+ out_buff + 4u);
+ case mhd_H2_FRAME_IDS_PUSH_PROMISE_ID:
+ return
+ frame_hdr_encode_push_promise (frame_info,
+ out_buff + 4u,
+ out_buff_size - mhd_H2_FR_HDR_BASE_SIZE,
+ out_buff + mhd_H2_FR_HDR_BASE_SIZE);
+ case mhd_H2_FRAME_IDS_PING_ID:
+ return frame_hdr_encode_ping (frame_info,
+ out_buff + 4u,
+ out_buff_size - mhd_H2_FR_HDR_BASE_SIZE,
+ out_buff + mhd_H2_FR_HDR_BASE_SIZE);
+ case mhd_H2_FRAME_IDS_GOAWAY_ID:
+ return frame_hdr_encode_goaway (frame_info,
+ out_buff_size - mhd_H2_FR_HDR_BASE_SIZE,
+ out_buff + mhd_H2_FR_HDR_BASE_SIZE);
+ case mhd_H2_FRAME_IDS_WINDOW_UPDATE_ID:
+ return
+ frame_hdr_encode_window_update (frame_info,
+ out_buff_size - mhd_H2_FR_HDR_BASE_SIZE,
+ out_buff + mhd_H2_FR_HDR_BASE_SIZE);
+ case mhd_H2_FRAME_IDS_CONTINUATION_ID:
+ return
+ frame_hdr_encode_continuation (frame_info,
+ out_buff + 4u);
+ default:
+ break;
+ }
+ mhd_UNREACHABLE_D ("Unknown frame types should not be sent");
+ return mhd_H2_FR_HDR_BASE_SIZE;
+}
diff --git a/src/mhd2/h2/h2_frame_codec.h b/src/mhd2/h2/h2_frame_codec.h
@@ -0,0 +1,251 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
+/*
+ This file is part of GNU libmicrohttpd.
+ Copyright (C) 2025 Evgeny Grin (Karlson2k)
+
+ GNU libmicrohttpd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ GNU libmicrohttpd is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ Alternatively, you can redistribute GNU libmicrohttpd and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of
+ the License, or (at your option) any later version, together
+ with the eCos exception, as follows:
+
+ As a special exception, if other files instantiate templates or
+ use macros or inline functions from this file, or you compile this
+ file and link it with other works to produce a work based on this
+ file, this file does not by itself cause the resulting work to be
+ covered by the GNU General Public License. However the source code
+ for this file must still be made available in accordance with
+ section (3) of the GNU General Public License v2.
+
+ This exception does not invalidate any other reasons why a work
+ based on this file might be covered by the GNU General Public
+ License.
+
+ You should have received copies of the GNU Lesser General Public
+ License and the GNU General Public License along with this library;
+ if not, see <https://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file src/mhd2/h2/h2_frame_codec.h
+ * @brief Declarations of HTTP/2 frame decoding and encoding functions
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+#ifndef MHD_H2_FRAME_CODEC_H
+#define MHD_H2_FRAME_CODEC_H 1
+
+#include "mhd_sys_options.h"
+
+#include "sys_base_types.h"
+
+#include "mhd_buffer.h"
+#include "h2_frame_types.h"
+
+#if defined(_MSC_FULL_VER)
+#pragma warning(push)
+/* Disable C4505 "unreferenced local function has been removed" */
+#pragma warning(disable:4505)
+#endif /* _MSC_FULL_VER */
+
+/**
+ * The basic (mandatory) HTTP/2 frame header size
+ */
+#define mhd_H2_FR_HDR_BASE_SIZE (9u)
+
+/**
+ * The minimum HTTP/2 frame size
+ */
+#define mhd_H2_FR_SIZE_MIN mhd_H2_FR_HDR_BASE_SIZE
+
+/**
+ * Maximum extra frame header size for known frame types.
+ */
+#define mhd_H2_FR_HDR_EXTRA_SIZE_MAX (8u)
+
+/**
+ * Result of frame decode
+ */
+enum MHD_FIXED_ENUM_ mhd_H2FrameDecodeResult
+{
+ /**
+ * Frame is successfully decoded
+ */
+ mhd_H2_F_DEC_OK = 0
+ ,
+ /**
+ * Frame header is completely decoded, but frame payload is incomplete
+ * Frame information is valid.
+ */
+ mhd_H2_F_DEC_F_PAYLOAD_INCOMPLETE
+ ,
+ /**
+ * Frame header information is not complete.
+ * Not enough data to decode frame header.
+ */
+ mhd_H2_F_DEC_F_HEADER_INCOMPLETE
+ ,
+ /**
+ * Stream error of type "Frame size error"
+ */
+ mhd_H2_F_DEC_STREAM_ERR_F_SIZE
+ ,
+ /**
+ * Stream error of type "Protocol error"
+ */
+ mhd_H2_F_DEC_STREAM_ERR_PROT
+ ,
+ /**
+ * Connection error of type "Frame size error"
+ */
+ mhd_H2_F_DEC_CONN_ERR_F_SIZE
+ ,
+ /**
+ * Connection error of type "Protocol error"
+ */
+ mhd_H2_F_DEC_CONN_ERR_PROT
+};
+
+#define mhd_H2_FRAME_DEC_ERR_IS_HARD(res) \
+ (mhd_H2_F_DEC_F_HEADER_INCOMPLETE < (res))
+
+/**
+ * Decode an HTTP/2 frame.
+ *
+ * If result is #mhd_H2_F_DEC_OK or #mhd_H2_F_DEC_F_PAYLOAD_INCOMPLETE then
+ * @a frame_info has full information about known frame types.
+ * When result is not #mhd_H2_F_DEC_OK, #mhd_H2_F_DEC_F_PAYLOAD_INCOMPLETE or
+ * #mhd_H2_F_DEC_F_HEADER_INCOMPLETE, the only valid data in the @a frame_info
+ * is "length", "type" and "stream_id".
+ * @param buff_size the size of the data available to decode
+ * @param buff the data to decode
+ * @param max_frame_size the maximum allowed frame size
+ * @param[out] frame_info set to frame information
+ * @param[out] frame_payload on successful decode set to the frame payload
+ * size and location
+ * @return #mhd_H2_F_DEC_OK on success,
+ * the error code otherwise
+ */
+MHD_INTERNAL enum mhd_H2FrameDecodeResult
+mhd_h2_frame_decode (size_t buff_size,
+ uint8_t *restrict buff,
+ uint_least32_t max_frame_size,
+ union mhd_H2FrameUnion *restrict frame_info,
+ struct mhd_Buffer *restrict frame_payload)
+MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_IN_SIZE_(2,1)
+MHD_FN_PAR_OUT_(4) MHD_FN_PAR_OUT_ (5);
+
+
+/**
+ * Find an amount of extra bytes required to encode the HTTP/2 frame.
+ *
+ * Each frame is encoded to the basic frame header size
+ * #mhd_H2_FR_HDR_BASE_SIZE plus optional extra bytes (depending on frame
+ * flags), plus the frame payload (only for relevant frame types), plus
+ * optional padding at the end.
+ *
+ * This function calculates the total extra bytes for known frame types based
+ * on the frame header type and flags. This extra size is counted as a part
+ * of the frame "length".
+ * @param frame_info the information about the frame; only type and flags are
+ * checked
+ * @return the number of extra bytes needed to write the frame header, which is
+ * a value in range 0 .. #mhd_H2_FR_HDR_EXTRA_SIZE_MAX;
+ * does not include the basic frame header
+ * size #mhd_H2_FR_HDR_BASE_SIZE
+ */
+MHD_INTERNAL size_t
+mhd_h2_frame_get_extra_hdr_size (
+ const union mhd_H2FrameUnion *restrict frame_info)
+MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_IN_ (1);
+
+
+/**
+ * Get the size of the padding (if any) set in the @a frame_info.
+ *
+ * The padding is counted as a part of frame "length".
+ * @param frame_info the information about frame, only type, flags and
+ * padding size (if any) are checked
+ * @return the padding size used at the end of the frame
+ */
+MHD_INTERNAL size_t
+mhd_h2_frame_get_padding_size (
+ const union mhd_H2FrameUnion *restrict frame_info)
+MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_IN_ (1);
+
+
+/**
+ * Calculate full frame size
+ * @param frame_info the information about frame
+ * @return the total size of the frame, including frame header and payload
+ */
+mhd_static_inline MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_IN_ (1) size_t
+mhd_h2_frame_get_total_size (const union mhd_H2FrameUnion *restrict frame_info)
+{
+ mhd_assert ((mhd_h2_frame_get_extra_hdr_size (frame_info)
+ + mhd_h2_frame_get_padding_size (frame_info))
+ <= frame_info->selector.length);
+ return (size_t) (mhd_H2_FR_HDR_BASE_SIZE + frame_info->selector.length);
+}
+
+
+/**
+ * Set 'length' member in the HTTP/2 frame.
+ * This function takes into account the frame type, the size of the extra
+ * frame header (if any), the padding (if allowed by frame type and set in
+ * @a frame_info) and the payload size.
+ *
+ * @param[in,out] frame_info the information about frame: the type, all flags
+ * and padding size (if any) must be set; the 'length'
+ * is modified
+ * @param real_payload_size the size of the real payload of the frame (the
+ * part of the frame after extra header bytes and
+ * before the padding (if any) bytes;
+ * must be zero for frames without payload support;
+ * must fit 24 bits length together with padding and
+ * extra header
+ * @return the total size of the updated frame: the basis header plus the value
+ * of the frame 'length' field
+ */
+MHD_INTERNAL size_t
+mhd_h2_frame_set_payload_size (union mhd_H2FrameUnion *restrict frame_info,
+ size_t real_payload_size)
+MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_INOUT_ (1);
+
+/**
+ * Encode the HTTP/2 frame header into the output buffer.
+ *
+ * This function writes the HTTP/2 basic frame header and extra frame header
+ * (if any, for know frame types only) using data from @a frame_info.
+ * @param frame_info the frame information to encode
+ * @param out_buff_size the size of @a out_buff in bytes
+ * @param[out] out_buff the output buffer to receive the encoded header
+ * @return the number of bytes written on success
+ * (always #mhd_H2_FR_HDR_BASE_SIZE or more),
+ * or 0 if @a out_buff_size is too small (the output buffer may be
+ * alerted in case of this error)
+ */
+MHD_INTERNAL size_t
+mhd_h2_frame_hdr_encode (const union mhd_H2FrameUnion *restrict frame_info,
+ size_t out_buff_size,
+ uint8_t *restrict out_buff)
+MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_IN_ (1)
+MHD_FN_PAR_OUT_SIZE_ (3,2);
+
+#if defined(_MSC_FULL_VER)
+/* Restore warnings */
+#pragma warning(pop)
+#endif /* _MSC_FULL_VER */
+
+#endif /* ! MHD_H2_FRAME_CODEC_H */
diff --git a/src/mhd2/h2/h2_frame_init.h b/src/mhd2/h2/h2_frame_init.h
@@ -0,0 +1,215 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
+/*
+ This file is part of GNU libmicrohttpd.
+ Copyright (C) 2025 Evgeny Grin (Karlson2k)
+
+ GNU libmicrohttpd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ GNU libmicrohttpd is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ Alternatively, you can redistribute GNU libmicrohttpd and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of
+ the License, or (at your option) any later version, together
+ with the eCos exception, as follows:
+
+ As a special exception, if other files instantiate templates or
+ use macros or inline functions from this file, or you compile this
+ file and link it with other works to produce a work based on this
+ file, this file does not by itself cause the resulting work to be
+ covered by the GNU General Public License. However the source code
+ for this file must still be made available in accordance with
+ section (3) of the GNU General Public License v2.
+
+ This exception does not invalidate any other reasons why a work
+ based on this file might be covered by the GNU General Public
+ License.
+
+ You should have received copies of the GNU Lesser General Public
+ License and the GNU General Public License along with this library;
+ if not, see <https://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file src/mhd2/h2/h2_frame_init.h
+ * @brief Declarations of HTTP/2 frame decoding and encoding functions
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+#ifndef MHD_H2_FRAME_INIT_H
+#define MHD_H2_FRAME_INIT_H 1
+
+#include "mhd_sys_options.h"
+
+#include "sys_bool_type.h"
+#include "sys_base_types.h"
+
+#include <string.h>
+
+#include "h2_frame_types.h"
+#include "h2_frame_length.h"
+
+#if defined(_MSC_FULL_VER)
+#pragma warning(push)
+/* Disable C4505 "unreferenced local function has been removed" */
+#pragma warning(disable:4505)
+#endif /* _MSC_FULL_VER */
+
+mhd_static_inline struct mhd_H2FrameDataInfo*
+mhd_h2_frame_init_data (union mhd_H2FrameUnion *restrict frame_info,
+ uint_least32_t stream_id,
+ bool end_stream)
+{
+ struct mhd_H2FrameDataInfo *const data = &(frame_info->data);
+ data->type = mhd_H2_FRAME_DATA_ID;
+ data->length = 0u; /* To be set later */
+ data->stream_id = stream_id;
+
+ data->padded = false;
+ data->end_stream = end_stream;
+
+ data->pad_length = 0u;
+
+ return data;
+}
+
+
+mhd_static_inline struct mhd_H2FrameHeadersInfo*
+mhd_h2_frame_init_headers (union mhd_H2FrameUnion *restrict frame_info,
+ uint_least32_t stream_id,
+ bool end_headers,
+ bool end_stream)
+{
+ struct mhd_H2FrameHeadersInfo *const headers = &(frame_info->headers);
+ headers->type = mhd_H2_FRAME_HEADERS_ID;
+ headers->length = 0u; /* To be set later */
+ headers->stream_id = stream_id;
+
+ headers->priority = false;
+ headers->padded = false;
+ headers->end_headers = end_headers;
+ headers->end_stream = end_stream;
+
+ headers->pad_length = 0u;
+ headers->exclusive = false;
+ headers->stream_dependency = 0u;
+ headers->weight = 0u;
+
+ return headers;
+}
+
+
+mhd_static_inline struct mhd_H2FrameRstStreamInfo*
+mhd_h2_frame_init_rst_stream (union mhd_H2FrameUnion *restrict frame_info,
+ uint_least32_t stream_id,
+ enum mhd_H2ErrorCode error_code)
+{
+ struct mhd_H2FrameRstStreamInfo *const rst_stream = &(frame_info->rst_stream);
+ rst_stream->type = mhd_H2_FRAME_RST_STREAM_ID;
+ rst_stream->length = mhd_H2_FR_FIXED_LEN_RST_STREAM; /* Fixed size */
+ rst_stream->stream_id = stream_id;
+
+ rst_stream->error_code = error_code;
+
+ return rst_stream;
+}
+
+
+mhd_static_inline struct mhd_H2FrameSettingsInfo*
+mhd_h2_frame_init_settings (union mhd_H2FrameUnion *restrict frame_info,
+ bool ack)
+{
+ struct mhd_H2FrameSettingsInfo *const settings = &(frame_info->settings);
+ settings->type = mhd_H2_FRAME_SETTINGS_ID;
+ settings->length = 0u; /* Could be increased later */
+ settings->stream_id = 0u;
+
+ settings->ack = ack;
+
+ return settings;
+}
+
+
+mhd_static_inline struct mhd_H2FramePingInfo*
+mhd_h2_frame_init_ping (union mhd_H2FrameUnion *restrict frame_info,
+ bool ack,
+ const uint8_t opaque_data[MHD_FN_PAR_FIX_ARR_SIZE_ (8)])
+{
+ struct mhd_H2FramePingInfo *const ping = &(frame_info->ping);
+ ping->type = mhd_H2_FRAME_PING_ID;
+ ping->length = mhd_H2_FR_FIXED_LEN_PING; /* Fixed size */
+ ping->stream_id = 0u;
+
+ ping->ack = ack;
+
+ memcpy (ping->opaque_data,
+ opaque_data,
+ 8u);
+
+ return ping;
+}
+
+
+mhd_static_inline struct mhd_H2FrameGoawayInfo*
+mhd_h2_frame_init_goaway (union mhd_H2FrameUnion *restrict frame_info,
+ uint_least32_t last_stream_id,
+ enum mhd_H2ErrorCode error_code)
+{
+ struct mhd_H2FrameGoawayInfo *const goaway = &(frame_info->goaway);
+ goaway->type = mhd_H2_FRAME_GOAWAY_ID;
+ goaway->length = 8u; /* Could be increased later */
+ goaway->stream_id = 0u;
+
+ goaway->last_stream_id = last_stream_id;
+ goaway->error_code = error_code;
+
+ return goaway;
+}
+
+
+mhd_static_inline struct mhd_H2FrameWindowUpdateInfo*
+mhd_h2_frame_init_window_update (union mhd_H2FrameUnion *restrict frame_info,
+ uint_least32_t stream_id,
+ uint_least32_t window_size_increment)
+{
+ struct mhd_H2FrameWindowUpdateInfo *const window_update =
+ &(frame_info->window_update);
+ window_update->type = mhd_H2_FRAME_WINDOW_UPDATE_ID;
+ window_update->length = mhd_H2_FR_FIXED_LEN_WINDOW_UPDATE; /* Fixed size */
+ window_update->stream_id = stream_id;
+
+ window_update->window_size_increment = window_size_increment;
+
+ return window_update;
+}
+
+
+mhd_static_inline struct mhd_H2FrameContinuationInfo*
+mhd_h2_frame_init_continuation (union mhd_H2FrameUnion *restrict frame_info,
+ uint_least32_t stream_id,
+ bool end_headers)
+{
+ struct mhd_H2FrameContinuationInfo *const continuation =
+ &(frame_info->continuation);
+ continuation->type = mhd_H2_FRAME_CONTINUATION_ID;
+ continuation->length = 0u; /* To be set later */
+ continuation->stream_id = stream_id;
+
+ continuation->end_headers = end_headers;
+
+ return continuation;
+}
+
+
+#if defined(_MSC_FULL_VER)
+/* Restore warnings */
+#pragma warning(pop)
+#endif /* _MSC_FULL_VER */
+
+#endif /* ! MHD_H2_FRAME_INIT_H */
diff --git a/src/mhd2/h2/h2_frame_length.h b/src/mhd2/h2/h2_frame_length.h
@@ -0,0 +1,58 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
+/*
+ This file is part of GNU libmicrohttpd.
+ Copyright (C) 2025 Evgeny Grin (Karlson2k)
+
+ GNU libmicrohttpd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ GNU libmicrohttpd is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ Alternatively, you can redistribute GNU libmicrohttpd and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of
+ the License, or (at your option) any later version, together
+ with the eCos exception, as follows:
+
+ As a special exception, if other files instantiate templates or
+ use macros or inline functions from this file, or you compile this
+ file and link it with other works to produce a work based on this
+ file, this file does not by itself cause the resulting work to be
+ covered by the GNU General Public License. However the source code
+ for this file must still be made available in accordance with
+ section (3) of the GNU General Public License v2.
+
+ This exception does not invalidate any other reasons why a work
+ based on this file might be covered by the GNU General Public
+ License.
+
+ You should have received copies of the GNU Lesser General Public
+ License and the GNU General Public License along with this library;
+ if not, see <https://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file src/mhd2/h2/h2_frame_length.h
+ * @brief HTTP2 frames length for fixed-size frames
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+#ifndef MHD_H2_FRAME_LENGTH_H
+#define MHD_H2_FRAME_LENGTH_H 1
+
+#include "mhd_sys_options.h"
+
+#define mhd_H2_FR_FIXED_LEN_PRIORITY (5u)
+
+#define mhd_H2_FR_FIXED_LEN_RST_STREAM (4u)
+
+#define mhd_H2_FR_FIXED_LEN_PING (8u)
+
+#define mhd_H2_FR_FIXED_LEN_WINDOW_UPDATE (4u)
+
+#endif /* ! MHD_H2_FRAME_LENGTH_H */
diff --git a/src/mhd2/h2/h2_frame_types.h b/src/mhd2/h2/h2_frame_types.h
@@ -0,0 +1,275 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
+/*
+ This file is part of GNU libmicrohttpd.
+ Copyright (C) 2025 Evgeny Grin (Karlson2k)
+
+ GNU libmicrohttpd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ GNU libmicrohttpd is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ Alternatively, you can redistribute GNU libmicrohttpd and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of
+ the License, or (at your option) any later version, together
+ with the eCos exception, as follows:
+
+ As a special exception, if other files instantiate templates or
+ use macros or inline functions from this file, or you compile this
+ file and link it with other works to produce a work based on this
+ file, this file does not by itself cause the resulting work to be
+ covered by the GNU General Public License. However the source code
+ for this file must still be made available in accordance with
+ section (3) of the GNU General Public License v2.
+
+ This exception does not invalidate any other reasons why a work
+ based on this file might be covered by the GNU General Public
+ License.
+
+ You should have received copies of the GNU Lesser General Public
+ License and the GNU General Public License along with this library;
+ if not, see <https://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file src/mhd2/h2/h2_frame_types.h
+ * @brief Definitions of HTTP/2 frame types
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+#ifndef MHD_H2_FRAME_TYPES_H
+#define MHD_H2_FRAME_TYPES_H 1
+
+#include "mhd_sys_options.h"
+
+#include "sys_bool_type.h"
+#include "sys_base_types.h"
+
+#include "h2_err_codes.h"
+
+
+enum MHD_FIXED_ENUM_ mhd_H2FrameDataID
+mhd_ENUM_BASE_T (uint_least8_t)
+{
+ mhd_H2_FRAME_DATA_ID = 0x00u
+};
+
+struct mhd_H2FrameDataInfo
+{
+ uint_least32_t length;
+ enum mhd_H2FrameDataID type;
+ uint_least32_t stream_id;
+
+ bool padded; /* Changes extra header size */
+ bool end_stream;
+
+ uint_least8_t pad_length;
+};
+
+
+enum MHD_FIXED_ENUM_ mhd_H2FrameHeadersID
+mhd_ENUM_BASE_T (uint_least8_t)
+{
+ mhd_H2_FRAME_HEADERS_ID = 0x01u
+};
+
+struct mhd_H2FrameHeadersInfo
+{
+ uint_least32_t length;
+ enum mhd_H2FrameHeadersID type;
+ uint_least32_t stream_id;
+
+ bool priority; /* Changes extra header size */
+ bool padded; /* Changes extra header size */
+ bool end_headers;
+ bool end_stream;
+
+ uint_least8_t pad_length;
+ bool exclusive;
+ uint_least32_t stream_dependency;
+ uint_least8_t weight; /* "on-wire" format, 0..255 */
+};
+
+
+enum MHD_FIXED_ENUM_ mhd_H2FramePriorityID
+mhd_ENUM_BASE_T (uint_least8_t)
+{
+ mhd_H2_FRAME_PRIORITY_ID = 0x02u
+};
+
+struct mhd_H2FramePriorityInfo
+{
+ uint_least32_t length;
+ enum mhd_H2FramePriorityID type;
+ uint_least32_t stream_id;
+
+ bool exclusive;
+ uint_least32_t stream_dependency;
+ uint_least8_t weight; /* "on-wire" format, 0..255 */
+};
+
+
+enum MHD_FIXED_ENUM_ mhd_H2FrameRstStreamID
+mhd_ENUM_BASE_T (uint_least8_t)
+{
+ mhd_H2_FRAME_RST_STREAM_ID = 0x03u
+};
+
+struct mhd_H2FrameRstStreamInfo
+{
+ uint_least32_t length;
+ enum mhd_H2FrameRstStreamID type;
+ uint_least32_t stream_id;
+
+ enum mhd_H2ErrorCode error_code; // TODO: support 4 > sizeof(int)
+};
+
+
+enum MHD_FIXED_ENUM_ mhd_H2FrameSettingsID
+mhd_ENUM_BASE_T (uint_least8_t)
+{
+ mhd_H2_FRAME_SETTINGS_ID = 0x04u
+};
+
+struct mhd_H2FrameSettingsInfo
+{
+ uint_least32_t length;
+ enum mhd_H2FrameSettingsID type;
+ uint_least32_t stream_id;
+
+ bool ack;
+};
+
+
+enum MHD_FIXED_ENUM_ mhd_H2FramePushPromiseID
+mhd_ENUM_BASE_T (uint_least8_t)
+{
+ mhd_H2_FRAME_PUSH_PROMISE_ID = 0x05u
+};
+
+struct mhd_H2FramePushPromiseInfo
+{
+ uint_least32_t length;
+ enum mhd_H2FramePushPromiseID type;
+ uint_least32_t stream_id;
+
+ bool padded; /* Changes extra header size */
+ bool end_headers;
+
+ uint_least8_t pad_length;
+ uint_least32_t promised_stream_id;
+};
+
+
+enum MHD_FIXED_ENUM_ mhd_H2FramePingID
+mhd_ENUM_BASE_T (uint_least8_t)
+{
+ mhd_H2_FRAME_PING_ID = 0x06u
+};
+
+struct mhd_H2FramePingInfo
+{
+ uint_least32_t length;
+ enum mhd_H2FramePingID type;
+ uint_least32_t stream_id;
+
+ bool ack;
+
+ uint8_t opaque_data[8];
+};
+
+
+enum MHD_FIXED_ENUM_ mhd_H2FrameGoawayID
+mhd_ENUM_BASE_T (uint_least8_t)
+{
+ mhd_H2_FRAME_GOAWAY_ID = 0x07u
+};
+
+struct mhd_H2FrameGoawayInfo
+{
+ uint_least32_t length;
+ enum mhd_H2FrameGoawayID type;
+ uint_least32_t stream_id;
+
+ uint_least32_t last_stream_id;
+ enum mhd_H2ErrorCode error_code; // TODO: support 4 > sizeof(int)
+};
+
+
+enum MHD_FIXED_ENUM_ mhd_H2FrameWindowUpdateID
+mhd_ENUM_BASE_T (uint_least8_t)
+{
+ mhd_H2_FRAME_WINDOW_UPDATE_ID = 0x08u
+};
+
+struct mhd_H2FrameWindowUpdateInfo
+{
+ uint_least32_t length;
+ enum mhd_H2FrameWindowUpdateID type;
+ uint_least32_t stream_id;
+
+ uint_least32_t window_size_increment;
+};
+
+
+enum MHD_FIXED_ENUM_ mhd_H2FrameContinuationID
+mhd_ENUM_BASE_T (uint_least8_t)
+{
+ mhd_H2_FRAME_CONTINUATION_ID = 0x09u
+};
+
+struct mhd_H2FrameContinuationInfo
+{
+ uint_least32_t length;
+ enum mhd_H2FrameContinuationID type;
+ uint_least32_t stream_id;
+
+ bool end_headers;
+};
+
+
+enum mhd_H2FrameIDs
+mhd_ENUM_BASE_T (uint_least8_t)
+{
+ mhd_H2_FRAME_IDS_DATA_ID = mhd_H2_FRAME_DATA_ID,
+ mhd_H2_FRAME_IDS_HEADERS_ID = mhd_H2_FRAME_HEADERS_ID,
+ mhd_H2_FRAME_IDS_PRIORITY_ID = mhd_H2_FRAME_PRIORITY_ID,
+ mhd_H2_FRAME_IDS_RST_STREAM_ID = mhd_H2_FRAME_RST_STREAM_ID,
+ mhd_H2_FRAME_IDS_SETTINGS_ID = mhd_H2_FRAME_SETTINGS_ID,
+ mhd_H2_FRAME_IDS_PUSH_PROMISE_ID = mhd_H2_FRAME_PUSH_PROMISE_ID,
+ mhd_H2_FRAME_IDS_PING_ID = mhd_H2_FRAME_PING_ID,
+ mhd_H2_FRAME_IDS_GOAWAY_ID = mhd_H2_FRAME_GOAWAY_ID,
+ mhd_H2_FRAME_IDS_WINDOW_UPDATE_ID = mhd_H2_FRAME_WINDOW_UPDATE_ID,
+ mhd_H2_FRAME_IDS_CONTINUATION_ID = mhd_H2_FRAME_CONTINUATION_ID
+};
+
+struct mhd_H2FrameSelector
+{
+ uint_least32_t length;
+ enum mhd_H2FrameIDs type;
+ uint_least32_t stream_id;
+};
+
+
+union mhd_H2FrameUnion
+{
+ struct mhd_H2FrameDataInfo data;
+ struct mhd_H2FrameHeadersInfo headers;
+ struct mhd_H2FramePriorityInfo priority;
+ struct mhd_H2FrameRstStreamInfo rst_stream;
+ struct mhd_H2FrameSettingsInfo settings;
+ struct mhd_H2FramePushPromiseInfo push_promise;
+ struct mhd_H2FramePingInfo ping;
+ struct mhd_H2FrameGoawayInfo goaway;
+ struct mhd_H2FrameWindowUpdateInfo window_update;
+ struct mhd_H2FrameContinuationInfo continuation;
+
+ struct mhd_H2FrameSelector selector;
+};
+
+#endif /* ! MHD_H2_FRAME_TYPES_H */
diff --git a/src/mhd2/h2/h2_proc_conn.c b/src/mhd2/h2/h2_proc_conn.c
@@ -0,0 +1,201 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
+/*
+ This file is part of GNU libmicrohttpd.
+ Copyright (C) 2025 Evgeny Grin (Karlson2k)
+
+ GNU libmicrohttpd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ GNU libmicrohttpd is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ Alternatively, you can redistribute GNU libmicrohttpd and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of
+ the License, or (at your option) any later version, together
+ with the eCos exception, as follows:
+
+ As a special exception, if other files instantiate templates or
+ use macros or inline functions from this file, or you compile this
+ file and link it with other works to produce a work based on this
+ file, this file does not by itself cause the resulting work to be
+ covered by the GNU General Public License. However the source code
+ for this file must still be made available in accordance with
+ section (3) of the GNU General Public License v2.
+
+ This exception does not invalidate any other reasons why a work
+ based on this file might be covered by the GNU General Public
+ License.
+
+ You should have received copies of the GNU Lesser General Public
+ License and the GNU General Public License along with this library;
+ if not, see <https://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file src/mhd2/h2/h2_proc_conn.c
+ * @brief Implementation of HTTP/2 connection processing functions
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+#include "mhd_sys_options.h"
+
+#include "sys_bool_type.h"
+#include "sys_base_types.h"
+
+#include "mhd_assert.h"
+#include "mhd_unreachable.h"
+
+#include "mhd_connection.h"
+
+#include "h2_frame_types.h"
+
+#include "h2_frame_init.h"
+#include "h2_frame_codec.h"
+
+#include "h2_proc_settings.h"
+#include "h2_conn_streams.h"
+#include "h2_proc_out.h"
+
+#include "h2_proc_conn.h"
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool
+mhd_h2_conn_process_first_fr (struct MHD_Connection *restrict c)
+{
+ union mhd_H2FrameUnion first_fr;
+ struct mhd_Buffer payload;
+ enum mhd_H2FrameDecodeResult dec_res;
+ bool ret;
+
+ mhd_assert (mhd_C_IS_HTTP2 (c));
+ mhd_assert (! c->h2.state.init.got_setns);
+
+ dec_res = mhd_h2_frame_decode (c->read_buffer_offset - c->h2.buff.r_cur_frame,
+ (uint8_t *) c->read_buffer
+ + c->h2.buff.r_cur_frame,
+ mhd_H2_STNG_DEF_MAX_FRAME_SIZE, /* Settings are not yet ACKed */
+ &first_fr,
+ &payload);
+ if (mhd_H2_FRAME_DEC_ERR_IS_HARD (dec_res))
+ {
+ mhd_h2_conn_finish (c,
+ mhd_H2_F_DEC_CONN_ERR_F_SIZE == dec_res ?
+ mhd_H2_ERR_FRAME_SIZE_ERROR : mhd_H2_ERR_PROTOCOL_ERROR,
+ true);
+ return false;
+ }
+
+ if (mhd_H2_F_DEC_OK != dec_res)
+ return false; /* Not yet complete */
+
+ if ((mhd_H2_FRAME_IDS_SETTINGS_ID != first_fr.selector.type) ||
+ (first_fr.settings.ack))
+ {
+ mhd_h2_conn_finish (c,
+ mhd_H2_ERR_PROTOCOL_ERROR,
+ true);
+ return false;
+ }
+
+ ret = mhd_h2_proc_first_settings (c,
+ &payload);
+
+ c->h2.buff.r_cur_frame += mhd_h2_frame_get_total_size (&first_fr);
+
+ return ret;
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool
+mhd_h2_conn_process_in_goaway (struct MHD_Connection *restrict c,
+ uint_least32_t last_stream_id,
+ enum mhd_H2ErrorCode err)
+{
+ c->h2.state.recvd_goaway.occurred = true;
+ c->h2.state.recvd_goaway.code = err;
+ c->h2.peer.stream_id_limit = last_stream_id;
+
+ // TODO: close all streams with higher IDs
+
+ return true;
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool
+mhd_h2_conn_update_stream_init_window (struct MHD_Connection *restrict c,
+ uint_least32_t init_wind_size)
+{
+ mhd_assert (0x7FFFFFFFu >= init_wind_size);
+ (void) c; // TODO: change window in all streams
+
+ c->h2.peer.stream_init_win_sz = init_wind_size;
+ return true;
+}
+
+
+static MHD_FN_PAR_NONNULL_ALL_ bool
+conn_win_update (struct MHD_Connection *restrict c)
+{
+ mhd_assert (0 <= c->h2.state.recv_window);
+ /* Dumb algorithm: if receive windows is less than three quarters of the full
+ * window size, then bump to the full size. */
+ if ((c->h2.rcv_cfg.conn_full_win_sz - c->h2.rcv_cfg.conn_full_win_sz / 4) >=
+ (uint_least32_t) c->h2.state.recv_window)
+ {
+ const uint_least32_t incr =
+ (uint_least32_t)
+ (c->h2.rcv_cfg.conn_full_win_sz
+ - (uint_least32_t) c->h2.state.recv_window);
+ mhd_assert (0x7FFFFFFFu >= incr);
+ if (! mhd_h2_q_window_update (c,
+ 0u,
+ incr))
+ return false;
+ c->h2.state.recv_window = (int_least32_t) c->h2.rcv_cfg.conn_full_win_sz;
+ }
+ return true;
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool
+mhd_h2_conn_process_changes (struct MHD_Connection *restrict c)
+{
+ if (! conn_win_update (c))
+ return false;
+
+ if (! mhd_h2_conn_maintain_streams_all (c))
+ return false;
+
+ return true;
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool
+mhd_h2_conn_finish (struct MHD_Connection *restrict c,
+ enum mhd_H2ErrorCode err,
+ bool forced)
+{
+ if (! mhd_h2_q_goaway (c,
+ err))
+ {
+ if (! forced)
+ return false;
+
+ c->h2.state.sent_goaway.occurred = true;
+ c->h2.state.sent_goaway.code = (uint_least32_t) err;
+
+ c->h_layer.state = mhd_HTTP_LAYER_BROKEN;
+ return true;
+ }
+
+ c->h2.state.sent_goaway.occurred = true;
+ c->h2.state.sent_goaway.code = (uint_least32_t) err;
+
+ c->h_layer.state = mhd_HTTP_LAYER_CLOSING;
+ return true;
+}
diff --git a/src/mhd2/h2/h2_proc_conn.h b/src/mhd2/h2/h2_proc_conn.h
@@ -0,0 +1,110 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
+/*
+ This file is part of GNU libmicrohttpd.
+ Copyright (C) 2025 Evgeny Grin (Karlson2k)
+
+ GNU libmicrohttpd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ GNU libmicrohttpd is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ Alternatively, you can redistribute GNU libmicrohttpd and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of
+ the License, or (at your option) any later version, together
+ with the eCos exception, as follows:
+
+ As a special exception, if other files instantiate templates or
+ use macros or inline functions from this file, or you compile this
+ file and link it with other works to produce a work based on this
+ file, this file does not by itself cause the resulting work to be
+ covered by the GNU General Public License. However the source code
+ for this file must still be made available in accordance with
+ section (3) of the GNU General Public License v2.
+
+ This exception does not invalidate any other reasons why a work
+ based on this file might be covered by the GNU General Public
+ License.
+
+ You should have received copies of the GNU Lesser General Public
+ License and the GNU General Public License along with this library;
+ if not, see <https://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file src/mhd2/h2/h2_proc_conn.h
+ * @brief Declarations of HTTP/2 connection processing functions
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+#ifndef MHD_H2_PROC_CONN_H
+#define MHD_H2_PROC_CONN_H 1
+
+#include "mhd_sys_options.h"
+
+#include "sys_bool_type.h"
+#include "sys_base_types.h"
+
+#include "h2_err_codes.h"
+
+struct MHD_Connection; /* forward declaration */
+struct mhd_Buffer; /* forward declaration */
+union mhd_H2FrameUnion; /* forward declaration */
+
+/**
+ * Process the first incoming frame
+ * @param c the connection to process
+ * @return 'true' if successfully processed, HTTP/2 connection is fully ready;
+ * 'false' if HTTP/2 connection cannot be used (not ready or broken)
+ */
+MHD_INTERNAL bool
+mhd_h2_conn_process_first_fr (struct MHD_Connection *restrict c)
+MHD_FN_PAR_NONNULL_ALL_;
+
+
+/**
+ * Process all changes in the connection.
+ * Create outgoing control frames as necessary, handle errors.
+ * @param c the connection to process
+ * @return 'true' if successfully processed, HTTP/2 processing may continue
+ * 'false' if not enough output buffer to send the control frames or
+ * if connection is broken, HTTP/2 procession may not continue
+ */
+MHD_INTERNAL bool
+mhd_h2_conn_process_changes (struct MHD_Connection *restrict c)
+MHD_FN_PAR_NONNULL_ALL_;
+
+
+MHD_INTERNAL bool
+mhd_h2_conn_process_in_goaway (struct MHD_Connection *restrict c,
+ uint_least32_t last_stream_id,
+ enum mhd_H2ErrorCode err)
+MHD_FN_PAR_NONNULL_ALL_;
+
+
+MHD_INTERNAL bool
+mhd_h2_conn_update_stream_init_window (struct MHD_Connection *restrict c,
+ uint_least32_t init_wind_size)
+MHD_FN_PAR_NONNULL_ALL_;
+
+/**
+ * Transition connection to closing state, queue relevant frame for peer.
+ * @param c the connection to process
+ * @param err the error code to use
+ * @param forced if 'true' connection will start closing even if there is
+ * no space to form peer notification frame
+ * @return 'true' if succeed,
+ * 'false' if output buffer has no space
+ */
+MHD_INTERNAL bool
+mhd_h2_conn_finish (struct MHD_Connection *restrict c,
+ enum mhd_H2ErrorCode err,
+ bool forced)
+MHD_FN_PAR_NONNULL_ALL_;
+
+#endif /* ! MHD_H2_PROC_CONN_H */
diff --git a/src/mhd2/h2/h2_proc_in.c b/src/mhd2/h2/h2_proc_in.c
@@ -0,0 +1,273 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
+/*
+ This file is part of GNU libmicrohttpd.
+ Copyright (C) 2025 Evgeny Grin (Karlson2k)
+
+ GNU libmicrohttpd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ GNU libmicrohttpd is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ Alternatively, you can redistribute GNU libmicrohttpd and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of
+ the License, or (at your option) any later version, together
+ with the eCos exception, as follows:
+
+ As a special exception, if other files instantiate templates or
+ use macros or inline functions from this file, or you compile this
+ file and link it with other works to produce a work based on this
+ file, this file does not by itself cause the resulting work to be
+ covered by the GNU General Public License. However the source code
+ for this file must still be made available in accordance with
+ section (3) of the GNU General Public License v2.
+
+ This exception does not invalidate any other reasons why a work
+ based on this file might be covered by the GNU General Public
+ License.
+
+ You should have received copies of the GNU Lesser General Public
+ License and the GNU General Public License along with this library;
+ if not, see <https://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file src/mhd2/h2/h2_proc_in.c
+ * @brief Implementation of HTTP/2 connection incoming data processing functions
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+#include "mhd_sys_options.h"
+
+#include "sys_bool_type.h"
+#include "sys_base_types.h"
+
+#include "mhd_assert.h"
+#include "mhd_unreachable.h"
+
+#include "mhd_buffer.h"
+#include "mhd_connection.h"
+
+#include <string.h>
+
+#include "h2_frame_types.h"
+#include "h2_frame_codec.h"
+
+#include "h2_conn_streams.h"
+#include "h2_proc_settings.h"
+#include "h2_proc_out.h"
+#include "h2_proc_conn.h"
+
+#include "h2_proc_in.h"
+
+
+enum mhd_H2ProcInFrameResult
+{
+ mhd_H2_PROC_IN_FRAME_CONTINUE = mhd_H2_STNGS_PROC_OK,
+ mhd_H2_PROC_IN_FRAME_BLOCKED_BY_OUT = mhd_H2_STNGS_PROC_NO_OUT_BUFF,
+ mhd_H2_PROC_IN_FRAME_CONN_BROKEN = mhd_H2_STNGS_PROC_STNGS_ERR
+};
+
+/**
+ * Process incoming frame
+ * @param c the connection to use
+ * @param h2frame the frame information
+ * @param payload the frame payload (excluding known extra headers)
+ * @return 'true' if frame was successfully and completely processed,
+ * 'false' if frame should be processed again later or if connection
+ * if in closing or broken state (and no incoming frames should be
+ * processed anymore)
+ */
+static MHD_FN_PAR_NONNULL_ALL_ bool
+process_inc_frame (struct MHD_Connection *restrict c,
+ const union mhd_H2FrameUnion *h2frame,
+ struct mhd_Buffer *payload)
+{
+ if (0u != c->h2.state.continuation_stream_id)
+ {
+ if ((mhd_H2_FRAME_IDS_CONTINUATION_ID != h2frame->selector.type)
+ || (c->h2.state.continuation_stream_id !=
+ h2frame->continuation.stream_id))
+ return ! mhd_h2_conn_finish (c,
+ mhd_H2_ERR_PROTOCOL_ERROR,
+ false);
+ }
+ else
+ mhd_assert (0u == c->h2.buff.unproc_hdrs_size);
+
+ switch (h2frame->selector.type)
+ {
+ case mhd_H2_FRAME_IDS_DATA_ID:
+ mhd_assert (0);
+ return mhd_h2_conn_finish (c,
+ mhd_H2_ERR_PROTOCOL_ERROR,
+ false);
+ case mhd_H2_FRAME_IDS_HEADERS_ID:
+ return mhd_h2_conn_streamid_in_headers (c,
+ h2frame->headers.stream_id,
+ h2frame->headers.end_stream,
+ h2frame->headers.end_headers,
+ payload);
+ case mhd_H2_FRAME_IDS_RST_STREAM_ID:
+ return mhd_h2_conn_streamid_in_rst_stream (c,
+ h2frame->rst_stream.stream_id,
+ h2frame->rst_stream.error_code);
+ return mhd_H2_PROC_IN_FRAME_CONTINUE;
+ case mhd_H2_FRAME_IDS_SETTINGS_ID:
+ return (mhd_H2_STNGS_PROC_OK == mhd_h2_proc_new_settings (c,
+ payload));
+ case mhd_H2_FRAME_IDS_PUSH_PROMISE_ID:
+ return ! mhd_h2_conn_finish (c,
+ mhd_H2_ERR_PROTOCOL_ERROR,
+ false);
+ case mhd_H2_FRAME_IDS_PING_ID:
+ return mhd_h2_q_ping (c,
+ true,
+ h2frame->ping.opaque_data);
+ case mhd_H2_FRAME_IDS_GOAWAY_ID:
+ return mhd_h2_conn_process_in_goaway (c,
+ h2frame->goaway.last_stream_id,
+ h2frame->goaway.error_code);
+ case mhd_H2_FRAME_IDS_WINDOW_UPDATE_ID:
+ if (0u != h2frame->window_update.stream_id)
+ {
+ return
+ mhd_h2_conn_streamid_window_incr (
+ c,
+ h2frame->window_update.stream_id,
+ h2frame->window_update.window_size_increment);
+ }
+ if ((h2frame->window_update.window_size_increment
+ + (uint_least32_t) c->h2.state.send_window) > 0x7FFFFFFFu)
+ return ! mhd_h2_conn_finish (c,
+ mhd_H2_ERR_FLOW_CONTROL_ERROR,
+ false);
+ c->h2.state.send_window +=
+ (int_least32_t) h2frame->window_update.window_size_increment;
+ return true;
+ case mhd_H2_FRAME_IDS_CONTINUATION_ID:
+ return
+ mhd_h2_conn_streamid_in_continuation (c,
+ h2frame->continuation.stream_id,
+ h2frame->continuation.end_headers,
+ payload);
+ case mhd_H2_FRAME_IDS_PRIORITY_ID:
+ default:
+ break; /* Ignored */
+ }
+ /* Ignored types of frame */
+ return true;
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool
+mhd_h2_conn_process_in_data (struct MHD_Connection *restrict c)
+{
+ mhd_assert (mhd_HTTP_LAYER_CONNECTED == c->h_layer.state);
+
+ mhd_assert (c->read_buffer_size >= c->read_buffer_offset);
+ mhd_assert (c->read_buffer_offset >= c->h2.buff.r_cur_frame);
+
+ if (c->h2.buff.r_cur_frame == c->read_buffer_offset)
+ return true; /* Shortcut, nothing to process */
+
+ do
+ {
+ union mhd_H2FrameUnion h2frame;
+ enum mhd_H2FrameDecodeResult dec_res;
+ struct mhd_Buffer payload;
+ bool proc_next;
+ uint8_t *const frame_buff =
+ (uint8_t *) c->read_buffer + c->h2.buff.r_cur_frame;
+ const size_t buff_left = c->read_buffer_offset - c->h2.buff.r_cur_frame;
+
+ proc_next = true;
+ dec_res = mhd_h2_frame_decode (buff_left,
+ frame_buff,
+ c->h2.rcv_cfg.max_frame_size,
+ &h2frame,
+ &payload);
+ switch (dec_res)
+ {
+ case mhd_H2_F_DEC_OK:
+ proc_next = process_inc_frame (c,
+ &h2frame,
+ &payload);
+ proc_next = (proc_next && (mhd_HTTP_LAYER_CONNECTED == c->h_layer.state));
+ if (c->h2.state.top_seen_stream_id < h2frame.selector.stream_id)
+ c->h2.state.top_seen_stream_id = h2frame.selector.stream_id;
+ break;
+ case mhd_H2_F_DEC_F_PAYLOAD_INCOMPLETE:
+ case mhd_H2_F_DEC_F_HEADER_INCOMPLETE:
+ proc_next = false;
+ break;
+ case mhd_H2_F_DEC_STREAM_ERR_F_SIZE:
+ case mhd_H2_F_DEC_STREAM_ERR_PROT:
+ if (mhd_h2_frame_get_total_size (&h2frame) > buff_left)
+ {
+ /* The frame is not yet complete */
+ proc_next = false;
+ break;
+ }
+ proc_next =
+ mhd_h2_conn_streamid_abort (
+ c,
+ h2frame.selector.stream_id,
+ (mhd_H2_F_DEC_STREAM_ERR_F_SIZE == dec_res) ?
+ mhd_H2_ERR_FRAME_SIZE_ERROR : mhd_H2_ERR_PROTOCOL_ERROR);
+ if (c->h2.state.top_seen_stream_id < h2frame.selector.stream_id)
+ c->h2.state.top_seen_stream_id = h2frame.selector.stream_id;
+ break;
+ case mhd_H2_F_DEC_CONN_ERR_F_SIZE:
+ mhd_h2_conn_finish (c,
+ mhd_H2_ERR_FRAME_SIZE_ERROR,
+ false);
+ return false;
+ case mhd_H2_F_DEC_CONN_ERR_PROT:
+ mhd_h2_conn_finish (c,
+ mhd_H2_ERR_PROTOCOL_ERROR,
+ false);
+ return false;
+ break;
+ default:
+ break; /* ignore unknown types */
+ }
+
+ if (! proc_next)
+ break;
+
+ mhd_assert (mhd_HTTP_LAYER_CONNECTED == c->h_layer.state);
+
+ mhd_assert ((c->read_buffer_offset - c->h2.buff.r_cur_frame) >=
+ mhd_h2_frame_get_total_size (&h2frame));
+
+ c->h2.buff.r_cur_frame += mhd_h2_frame_get_total_size (&h2frame);
+ } while (c->h2.buff.r_cur_frame < c->read_buffer_offset);
+
+ mhd_assert (c->read_buffer_offset >= c->h2.buff.r_cur_frame);
+ if (mhd_HTTP_LAYER_CONNECTED == c->h_layer.state)
+ {
+ const size_t data_left = c->read_buffer_offset - c->h2.buff.r_cur_frame;
+
+ mhd_assert (data_left <= c->read_buffer_offset);
+
+ memmove (c->read_buffer,
+ c->read_buffer + c->h2.buff.unproc_hdrs_pos,
+ c->h2.buff.unproc_hdrs_size);
+ c->h2.buff.unproc_hdrs_pos = 0u;
+
+ memmove (c->read_buffer + c->h2.buff.unproc_hdrs_size,
+ c->read_buffer + c->h2.buff.r_cur_frame,
+ data_left);
+
+ c->h2.buff.r_cur_frame = c->h2.buff.unproc_hdrs_size;
+ c->read_buffer_offset = c->h2.buff.r_cur_frame + data_left;
+ }
+
+ return (mhd_HTTP_LAYER_CONNECTED == c->h_layer.state);
+}
diff --git a/src/mhd2/h2/h2_proc_in.h b/src/mhd2/h2/h2_proc_in.h
@@ -0,0 +1,58 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
+/*
+ This file is part of GNU libmicrohttpd.
+ Copyright (C) 2025 Evgeny Grin (Karlson2k)
+
+ GNU libmicrohttpd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ GNU libmicrohttpd is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ Alternatively, you can redistribute GNU libmicrohttpd and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of
+ the License, or (at your option) any later version, together
+ with the eCos exception, as follows:
+
+ As a special exception, if other files instantiate templates or
+ use macros or inline functions from this file, or you compile this
+ file and link it with other works to produce a work based on this
+ file, this file does not by itself cause the resulting work to be
+ covered by the GNU General Public License. However the source code
+ for this file must still be made available in accordance with
+ section (3) of the GNU General Public License v2.
+
+ This exception does not invalidate any other reasons why a work
+ based on this file might be covered by the GNU General Public
+ License.
+
+ You should have received copies of the GNU Lesser General Public
+ License and the GNU General Public License along with this library;
+ if not, see <https://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file src/mhd2/h2/h2_proc_in.h
+ * @brief Declarations of HTTP/2 connection incoming data processing functions
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+#ifndef MHD_H2_PROC_IN_H
+#define MHD_H2_PROC_IN_H 1
+
+#include "mhd_sys_options.h"
+
+#include "sys_bool_type.h"
+
+struct MHD_Connection; /* forward declaration */
+
+MHD_INTERNAL bool
+mhd_h2_conn_process_in_data (struct MHD_Connection *restrict c)
+MHD_FN_PAR_NONNULL_ALL_;
+
+#endif /* ! MHD_H2_PROC_IN_H */
diff --git a/src/mhd2/h2/h2_proc_out.c b/src/mhd2/h2/h2_proc_out.c
@@ -0,0 +1,267 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
+/*
+ This file is part of GNU libmicrohttpd.
+ Copyright (C) 2025 Evgeny Grin (Karlson2k)
+
+ GNU libmicrohttpd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ GNU libmicrohttpd is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ Alternatively, you can redistribute GNU libmicrohttpd and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of
+ the License, or (at your option) any later version, together
+ with the eCos exception, as follows:
+
+ As a special exception, if other files instantiate templates or
+ use macros or inline functions from this file, or you compile this
+ file and link it with other works to produce a work based on this
+ file, this file does not by itself cause the resulting work to be
+ covered by the GNU General Public License. However the source code
+ for this file must still be made available in accordance with
+ section (3) of the GNU General Public License v2.
+
+ This exception does not invalidate any other reasons why a work
+ based on this file might be covered by the GNU General Public
+ License.
+
+ You should have received copies of the GNU Lesser General Public
+ License and the GNU General Public License along with this library;
+ if not, see <https://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file src/mhd2/h2/h2_proc_out.c
+ * @brief Implementation of HTTP/2 connection outgoing data processing functions
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+#include "mhd_sys_options.h"
+
+#include "sys_bool_type.h"
+#include "sys_base_types.h"
+
+#include "mhd_assert.h"
+
+#include "mhd_connection.h"
+
+#include "h2_err_codes.h"
+#include "h2_frame_types.h"
+#include "h2_frame_init.h"
+#include "h2_frame_codec.h"
+
+#include "h2_proc_in.h"
+
+#include "h2_proc_out.h"
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool
+mhd_h2_out_buff_has_space_sz (struct MHD_Connection *restrict c,
+ size_t space_needed)
+{
+ size_t have_buff_space;
+ mhd_assert (c->write_buffer_size >= c->write_buffer_append_offset);
+
+ mhd_assert (! c->h2.dbg.w_buff_updating);
+ mhd_H2_W_BUFF_UPDATING_SET (&(c->h2));
+
+ have_buff_space = c->write_buffer_size - c->write_buffer_append_offset;
+
+ mhd_H2_W_BUFF_UPDATING_CLEAR (&(c->h2));
+
+ return (space_needed <= have_buff_space);
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool
+mhd_h2_out_buff_has_space_fr (struct MHD_Connection *restrict c,
+ union mhd_H2FrameUnion *restrict h2frame)
+{
+ return mhd_h2_out_buff_has_space_sz (c,
+ mhd_h2_frame_get_total_size (h2frame));
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool
+mhd_h2_out_buff_acquire_fr_w_payload_l (
+ struct MHD_Connection *restrict c,
+ const union mhd_H2FrameUnion *restrict h2frame,
+ uint_least32_t full_payload_limit,
+ struct mhd_Buffer *restrict buff,
+ size_t *restrict payload_offset)
+{
+ const size_t extra_hdr_size = mhd_h2_frame_get_extra_hdr_size (h2frame);
+ const size_t padding_size = mhd_h2_frame_get_padding_size (h2frame);
+ size_t w_buff_space;
+
+ mhd_assert (! c->h2.dbg.w_buff_updating);
+ mhd_H2_W_BUFF_UPDATING_SET (&(c->h2));
+ mhd_assert (c->write_buffer_size >= c->write_buffer_append_offset);
+
+ if (full_payload_limit > c->h2.peer.max_frame_size)
+ full_payload_limit = (uint_least32_t) c->h2.peer.max_frame_size;
+
+ w_buff_space = c->write_buffer_size - c->write_buffer_append_offset;
+ if (((mhd_H2_FR_HDR_BASE_SIZE + extra_hdr_size + padding_size)
+ >= w_buff_space) ||
+ ((extra_hdr_size + padding_size) >= full_payload_limit))
+ {
+ mhd_H2_W_BUFF_UPDATING_CLEAR (&(c->h2));
+ return false;
+ }
+
+ mhd_assert (c->h2.peer.max_frame_size > mhd_H2_FR_HDR_BASE_SIZE);
+
+ if (full_payload_limit < (w_buff_space - mhd_H2_FR_HDR_BASE_SIZE))
+ w_buff_space = full_payload_limit + mhd_H2_FR_HDR_BASE_SIZE;
+
+ buff->data = c->write_buffer + c->write_buffer_append_offset;
+ buff->size = w_buff_space - padding_size;
+ *payload_offset = mhd_H2_FR_HDR_BASE_SIZE + extra_hdr_size;
+
+ return true;
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool
+mhd_h2_out_buff_acquire_fr_w_payload (
+ struct MHD_Connection *restrict c,
+ const union mhd_H2FrameUnion *restrict h2frame,
+ struct mhd_Buffer *restrict buff,
+ size_t *restrict payload_offset)
+{
+ return
+ mhd_h2_out_buff_acquire_fr_w_payload_l (c,
+ h2frame,
+ 0xFFFFFFFFu,
+ buff,
+ payload_offset);
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void
+mhd_h2_out_buff_unlock (struct MHD_Connection *restrict c,
+ size_t size_used)
+{
+ mhd_assert (c->h2.dbg.w_buff_updating);
+ mhd_assert (c->write_buffer_size >= size_used);
+ mhd_assert ((c->write_buffer_size - size_used)
+ >= c->write_buffer_append_offset);
+
+ c->write_buffer_append_offset += size_used;
+ mhd_H2_W_BUFF_UPDATING_CLEAR (&(c->h2));
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool
+mhd_h2_q_frame_no_payload (struct MHD_Connection *restrict c,
+ union mhd_H2FrameUnion *restrict h2frame)
+{
+ size_t fr_hdr_size;
+ size_t w_buff_space;
+ bool succeed;
+
+ mhd_assert (mhd_h2_frame_get_extra_hdr_size (h2frame) ==
+ h2frame->selector.length);
+
+ fr_hdr_size = mhd_H2_FR_HDR_BASE_SIZE
+ + mhd_h2_frame_get_extra_hdr_size (h2frame);
+
+ mhd_assert (! c->h2.dbg.w_buff_updating);
+ mhd_H2_W_BUFF_UPDATING_SET (&(c->h2));
+
+ succeed = false;
+ w_buff_space = c->write_buffer_size - c->write_buffer_append_offset;
+ if (fr_hdr_size <= w_buff_space)
+ {
+ uint8_t *w_buff;
+ size_t written;
+ w_buff = (uint8_t *) c->write_buffer + c->write_buffer_append_offset;
+
+ written = mhd_h2_frame_hdr_encode (h2frame,
+ w_buff_space,
+ w_buff);
+ mhd_assert (fr_hdr_size == written);
+ c->write_buffer_append_offset += written;
+
+ succeed = true;
+ }
+
+ mhd_H2_W_BUFF_UPDATING_CLEAR (&(c->h2));
+ return succeed;
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool
+mhd_h2_q_rst_stream (struct MHD_Connection *restrict c,
+ uint_least32_t stream_id,
+ enum mhd_H2ErrorCode err)
+{
+ union mhd_H2FrameUnion h2frame;
+
+ mhd_assert (0u != stream_id);
+ if (c->h2.state.top_rst_stream_id < stream_id)
+ c->h2.state.top_rst_stream_id = stream_id;
+
+ mhd_h2_frame_init_rst_stream (&h2frame,
+ stream_id,
+ err);
+
+ return mhd_h2_q_frame_no_payload (c,
+ &h2frame);
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool
+mhd_h2_q_ping (struct MHD_Connection *restrict c,
+ bool ack,
+ const uint8_t opaque_data[MHD_FN_PAR_FIX_ARR_SIZE_ (8)])
+{
+ union mhd_H2FrameUnion h2frame;
+
+ mhd_h2_frame_init_ping (&h2frame,
+ ack,
+ opaque_data);
+
+ return mhd_h2_q_frame_no_payload (c,
+ &h2frame);
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool
+mhd_h2_q_goaway (struct MHD_Connection *restrict c,
+ enum mhd_H2ErrorCode err)
+{
+ union mhd_H2FrameUnion h2frame;
+
+ mhd_h2_frame_init_goaway (&h2frame,
+ c->h2.state.top_proc_stream_id,
+ err);
+
+ return mhd_h2_q_frame_no_payload (c,
+ &h2frame);
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool
+mhd_h2_q_window_update (struct MHD_Connection *restrict c,
+ uint_least32_t stream_id,
+ uint_least32_t win_size_incr)
+{
+ union mhd_H2FrameUnion h2frame;
+
+ mhd_assert (0u != win_size_incr);
+
+ mhd_h2_frame_init_window_update (&h2frame,
+ stream_id,
+ win_size_incr);
+
+ return mhd_h2_q_frame_no_payload (c,
+ &h2frame);
+}
diff --git a/src/mhd2/h2/h2_proc_out.h b/src/mhd2/h2/h2_proc_out.h
@@ -0,0 +1,228 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
+/*
+ This file is part of GNU libmicrohttpd.
+ Copyright (C) 2025 Evgeny Grin (Karlson2k)
+
+ GNU libmicrohttpd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ GNU libmicrohttpd is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ Alternatively, you can redistribute GNU libmicrohttpd and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of
+ the License, or (at your option) any later version, together
+ with the eCos exception, as follows:
+
+ As a special exception, if other files instantiate templates or
+ use macros or inline functions from this file, or you compile this
+ file and link it with other works to produce a work based on this
+ file, this file does not by itself cause the resulting work to be
+ covered by the GNU General Public License. However the source code
+ for this file must still be made available in accordance with
+ section (3) of the GNU General Public License v2.
+
+ This exception does not invalidate any other reasons why a work
+ based on this file might be covered by the GNU General Public
+ License.
+
+ You should have received copies of the GNU Lesser General Public
+ License and the GNU General Public License along with this library;
+ if not, see <https://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file src/mhd2/h2/h2_proc_out.h
+ * @brief Declarations of HTTP/2 connection outgoing data processing functions
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+#ifndef MHD_H2_PROC_OUT_H
+#define MHD_H2_PROC_OUT_H 1
+
+#include "mhd_sys_options.h"
+
+#include "sys_bool_type.h"
+#include "sys_base_types.h"
+
+#include "h2_err_codes.h"
+
+struct MHD_Connection; /* forward declaration */
+struct mhd_Buffer; /* forward declaration */
+union mhd_H2FrameUnion; /* forward declaration */
+
+/**
+ * Check whether the output buffer has at least specified free space.
+ * @param c the connection to check
+ * @param space_needed the amount of free space needed
+ * @return 'true' if output buffer has enough space,
+ * 'false' otherwise
+ */
+MHD_INTERNAL bool
+mhd_h2_out_buff_has_space_sz (struct MHD_Connection *restrict c,
+ size_t space_needed)
+MHD_FN_PAR_NONNULL_ALL_;
+
+/**
+ * Check whether the output buffer has enough space to add the frame (together
+ * with optional payload).
+ * @param c the connection to check
+ * @param h2frame the frame to use, the 'type', all flags and the 'length'
+ * must be set
+ * @return 'true' if output buffer has enough space,
+ * 'false' otherwise
+ */
+MHD_INTERNAL bool
+mhd_h2_out_buff_has_space_fr (struct MHD_Connection *restrict c,
+ union mhd_H2FrameUnion *restrict h2frame)
+MHD_FN_PAR_NONNULL_ALL_;
+
+
+/**
+ * Acquire the output buffer for specified frame with undefined payload size.
+ *
+ * The @a h2frame must have all information set except the length of the frame.
+ * The provided @a buff is not larger than maximum frame size allowed for
+ * connection. If @a h2frame has padding, then the size for the padding is
+ * reserved at the end of the @a buff (but not included to the @a buff size).
+ *
+ * In the debug builds this function "locks" the output buffer if succeed.
+ * The caller must always call #mhd_h2_out_buff_unlock() after finishing
+ * working with the buffer even if nothing is written to the buffer.
+ *
+ * @param c the connection to use
+ * @param h2frame the frame to use, the 'type' and all flags must be set,
+ * the 'length' is ignored
+ * @param[out] buff set to the acquired space in the buffer, the size is always
+ * larger than the basic header and the extra header;
+ * the space reserved for the padding is not included
+ * @param[out] payload_offset set to the offset where to put the frame payload
+ * @return 'true' if output buffer has enough space for the frame header and at
+ * least one byte for the payload,
+ * 'false' otherwise
+ */
+MHD_INTERNAL bool
+mhd_h2_out_buff_acquire_fr_w_payload (
+ struct MHD_Connection *restrict c,
+ const union mhd_H2FrameUnion *restrict h2frame,
+ struct mhd_Buffer *restrict buff,
+ size_t *restrict payload_offset)
+MHD_FN_PAR_NONNULL_ALL_;
+
+/**
+ * Acquire the output buffer for specified frame with undefined payload size.
+ *
+ * Version of #mhd_h2_out_buff_acquire_fr_w_payload() with predefined limit
+ * for the frame full payload size.
+ *
+ * @param c the connection to use
+ * @param h2frame the frame to use, the 'type' and all flags must be set,
+ * the 'length' is ignored
+ * @param full_payload_limit the maximum size of the frame (excluding base
+ * frame header)
+ * @param[out] buff set to the acquired space in the buffer, the size is always
+ * larger than the basic header and the extra header;
+ * the space reserved for the padding is not included
+ * @param[out] payload_offset set to the offset where to put the frame payload
+ * @return 'true' if output buffer has enough space for the frame header and at
+ * least one byte for the payload,
+ * 'false' otherwise
+ */
+MHD_INTERNAL bool
+mhd_h2_out_buff_acquire_fr_w_payload_l (
+ struct MHD_Connection *restrict c,
+ const union mhd_H2FrameUnion *restrict h2frame,
+ uint_least32_t full_payload_limit,
+ struct mhd_Buffer *restrict buff,
+ size_t *restrict payload_offset)
+MHD_FN_PAR_NONNULL_ALL_;
+
+
+/**
+ * Finish writing the data to the output buffer.
+ * The output buffer must be previously acquired by calling
+ * #mhd_h2_out_buff_acquire_fr_w_payload().
+ * @param c the connection to use
+ * @param size_used the size of the frame written; if frame had padding then
+ * it could be larger (by the padding size) than the size of
+ * the buffer provided by #mhd_h2_out_buff_acquire_fr_w_payload()
+ */
+MHD_INTERNAL void
+mhd_h2_out_buff_unlock (struct MHD_Connection *restrict c,
+ size_t size_used)
+MHD_FN_PAR_NONNULL_ALL_;
+
+/**
+ * Queue to the sending buffer a frame without payload
+ * @param c the connection to use
+ * @param h2frame the frame to queue
+ * @return 'true' if the frame is successfully added,
+ * 'false' if sending buffer has not enough space
+ */
+MHD_INTERNAL bool
+mhd_h2_q_frame_no_payload (struct MHD_Connection *restrict c,
+ union mhd_H2FrameUnion *restrict h2frame)
+MHD_FN_PAR_NONNULL_ALL_;
+
+/**
+ * Queue to the sending buffer a RST_STREAM frame
+ * @param c the connection to use
+ * @param stream_id the stream ID for the frame
+ * @param err the HTTP/2 error code
+ * @return 'true' if the frame is successfully added,
+ * 'false' if sending buffer has not enough space
+ */
+MHD_INTERNAL bool
+mhd_h2_q_rst_stream (struct MHD_Connection *restrict c,
+ uint_least32_t stream_id,
+ enum mhd_H2ErrorCode err)
+MHD_FN_PAR_NONNULL_ALL_;
+
+/**
+ * Queue to the sending buffer a PING frame
+ * @param c the connection to use
+ * @param ack the value of the ACK flag in the frame
+ * @param opaque the PING opaque data
+ * @return 'true' if the frame is successfully added,
+ * 'false' if sending buffer has not enough space
+ */
+MHD_INTERNAL bool
+mhd_h2_q_ping (struct MHD_Connection *restrict c,
+ bool ack,
+ const uint8_t opaque[MHD_FN_PAR_FIX_ARR_SIZE_ (8)])
+MHD_FN_PAR_NONNULL_ALL_;
+
+
+/**
+ * Queue to the sending buffer a GOAWAY frame without additional debug info
+ * @param c the connection to use
+ * @param err the HTTP/2 error code
+ * @return 'true' if the frame is successfully added,
+ * 'false' if sending buffer has not enough space
+ */
+MHD_INTERNAL bool
+mhd_h2_q_goaway (struct MHD_Connection *restrict c,
+ enum mhd_H2ErrorCode err)
+MHD_FN_PAR_NONNULL_ALL_;
+
+/**
+ * Queue to the sending buffer a WINDOW_UPDATE frame
+ * @param c the connection to use
+ * @param stream_id the stream ID for the frame
+ * @param win_size_incr the Window Size Increment value;
+ * must not be zero, must fit 31 bits
+ * @return 'true' if the frame is successfully added,
+ * 'false' if sending buffer has not enough space
+ */
+MHD_INTERNAL bool
+mhd_h2_q_window_update (struct MHD_Connection *restrict c,
+ uint_least32_t stream_id,
+ uint_least32_t win_size_incr)
+MHD_FN_PAR_NONNULL_ALL_;
+
+#endif /* ! MHD_H2_PROC_OUT_H */
diff --git a/src/mhd2/h2/h2_proc_settings.c b/src/mhd2/h2/h2_proc_settings.c
@@ -0,0 +1,323 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
+/*
+ This file is part of GNU libmicrohttpd.
+ Copyright (C) 2025 Evgeny Grin (Karlson2k)
+
+ GNU libmicrohttpd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ GNU libmicrohttpd is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ Alternatively, you can redistribute GNU libmicrohttpd and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of
+ the License, or (at your option) any later version, together
+ with the eCos exception, as follows:
+
+ As a special exception, if other files instantiate templates or
+ use macros or inline functions from this file, or you compile this
+ file and link it with other works to produce a work based on this
+ file, this file does not by itself cause the resulting work to be
+ covered by the GNU General Public License. However the source code
+ for this file must still be made available in accordance with
+ section (3) of the GNU General Public License v2.
+
+ This exception does not invalidate any other reasons why a work
+ based on this file might be covered by the GNU General Public
+ License.
+
+ You should have received copies of the GNU Lesser General Public
+ License and the GNU General Public License along with this library;
+ if not, see <https://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file src/mhd2/h2/h2_proc_settings.c
+ * @brief Implementation of HTTP/2 connection settings processing functions
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+#include "mhd_sys_options.h"
+
+#include "sys_bool_type.h"
+#include "sys_base_types.h"
+
+#include "mhd_assert.h"
+
+#include "mhd_connection.h"
+
+#include "h2_frame_types.h"
+#include "h2_frame_init.h"
+#include "h2_settings.h"
+
+#include "h2_frame_codec.h"
+#include "hpack/mhd_hpack_codec.h"
+#include "h2_proc_conn.h"
+#include "h2_proc_out.h"
+
+#include "h2_proc_settings.h"
+
+
+static bool
+h2_has_space_for_ack (struct MHD_Connection *restrict c)
+{
+#ifndef NDEBUG
+ if (1)
+ {
+ union mhd_H2FrameUnion h2frame;
+
+ mhd_h2_frame_init_settings (&h2frame,
+ true);
+ mhd_assert (0u == mhd_h2_frame_get_extra_hdr_size (&h2frame));
+ }
+#endif /* NDEBUG */
+ return mhd_h2_out_buff_has_space_sz (c,
+ mhd_H2_FR_HDR_BASE_SIZE);
+}
+
+
+static bool
+h2_q_settings_ack (struct MHD_Connection *restrict c)
+{
+ union mhd_H2FrameUnion h2frame;
+
+ mhd_h2_frame_init_settings (&h2frame,
+ true);
+ /* The actual size should match free space check */
+ mhd_assert (0u == mhd_h2_frame_get_extra_hdr_size (&h2frame));
+
+ return mhd_h2_q_frame_no_payload (c,
+ &h2frame);
+}
+
+
+MHD_INTERNAL bool
+mhd_h2_proc_first_settings (struct MHD_Connection *restrict c,
+ const struct mhd_Buffer *restrict stngs_payload)
+{
+ bool ack_succeed;
+
+ /* The payload size must be checked by frame decoder */
+ mhd_assert (0u == (stngs_payload->size % 6u));
+
+ mhd_assert (h2_has_space_for_ack (c));
+
+ if (stngs_payload->size > 6u)
+ {
+ size_t pos;
+
+ for (pos = 0u; pos < (stngs_payload->size - 5u); pos += 6u)
+ {
+ struct mhd_H2Setting stng;
+ mhd_h2_setting_decode ((uint8_t *) stngs_payload->data + pos,
+ &stng);
+
+ switch (stng.identifier)
+ {
+ case mhd_H2_STNGS_HEADER_TABLE_SIZE:
+ if (mhd_DTBL_MAX_SIZE >= stng.value)
+ {
+ if (4096u >= stng.value) // TODO: take the limit from the daemon
+ mhd_hpack_enc_set_dyn_size (&(c->h2.hk_enc),
+ (size_t) stng.value);
+ }
+ break;
+ case mhd_H2_STNGS_ENABLE_PUSH:
+ /* Ignored */
+ break;
+ case mhd_H2_STNGS_CONCURRENT_STREAMS:
+ c->h2.peer.max_concur_streams = stng.value;
+ break;
+ case mhd_H2_STNGS_INITIAL_WINDOW_SIZE:
+ if (0x7FFFFFFFu < stng.value)
+ {
+ mhd_h2_conn_finish (c,
+ mhd_H2_ERR_FLOW_CONTROL_ERROR,
+ true);
+ return false; /* Failure exit point */
+ }
+
+ /* Set the initial size. No streams should be modified as no
+ streams have been started yet. */
+ c->h2.peer.stream_init_win_sz = stng.value;
+ break;
+ case mhd_H2_STNGS_MAX_FRAME_SIZE:
+ if ((mhd_H2_STNG_MIN_MAX_FRAME_SIZE > stng.value) ||
+ (mhd_H2_STNG_MAX_MAX_FRAME_SIZE < stng.value))
+ {
+ mhd_h2_conn_finish (c,
+ mhd_H2_ERR_PROTOCOL_ERROR,
+ true);
+ return false; /* Failure exit point */
+ }
+#if 0 // TODO: use limit from the daemon settings
+ if (mhd_H2_STNG_DEF_MAX_FRAME_SIZE >= stng.value)
+ c->h2.peer.max_frame_size = stng.value;
+#endif
+ break;
+ case mhd_H2_STNGS_MAX_HEADER_LIST_SIZE:
+ c->h2.peer.max_header_list = stng.value;
+ break;
+ case mhd_H2_STNGS_ENABLE_CONNECT_PROTOCOL:
+ case mhd_H2_STNGS_NO_RFC7540_PRIORITIES:
+ /* Ignored */
+ break;
+#ifndef mhd_USE_ENUM_BASE_T
+ case mhd_H2_STNGS_SENTINEL:
+#endif /*mhd_USE_ENUM_BASE_T */
+ default:
+ /* Unknown setting ignored */
+ break;
+ }
+ }
+ }
+ c->h2.state.init.got_setns = true;
+
+ ack_succeed = h2_q_settings_ack (c);
+ mhd_assert (ack_succeed);
+ (void) ack_succeed;
+
+ return true; /* Success exit point */
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ enum mhd_H2SettingsProcessResult
+mhd_h2_proc_new_settings (struct MHD_Connection *restrict c,
+ const struct mhd_Buffer *restrict stngs_payload)
+{
+ (void) c; (void) stngs_payload; // TODO: implement
+ return mhd_H2_STNGS_PROC_STNGS_ERR;
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool
+mhd_h2_q_settings_first_fr (struct MHD_Connection *restrict c)
+{
+ size_t fr_hdr_size;
+ size_t final_fr_hdr_size;
+ size_t payload_space;
+ uint8_t *payload;
+ size_t payload_pos;
+ bool set_all_settings;
+ struct mhd_Buffer buff;
+ union mhd_H2FrameUnion h2frame;
+
+ mhd_h2_frame_init_settings (&h2frame,
+ false);
+
+ /* This is the first data sent by the server */
+ mhd_assert (0u == c->write_buffer_append_offset);
+
+ if (! mhd_h2_out_buff_acquire_fr_w_payload (c,
+ &h2frame,
+ &buff,
+ &fr_hdr_size))
+ {
+ mhd_H2_W_BUFF_UPDATING_CLEAR (&(c->h2));
+ mhd_h2_conn_finish (c,
+ mhd_H2_ERR_INTERNAL_ERROR,
+ true);
+ return false;
+ }
+
+ payload = (uint8_t *) buff.data + fr_hdr_size;
+ payload_space = buff.size - fr_hdr_size;
+
+ if (payload_space != (payload_space & 0xFFFFFFFFu))
+ payload_space = 0xFFFFFFFFu;
+
+ payload_pos = 0u;
+
+ // TODO: use configurable values for settings
+ if (payload_space >= payload_pos + mhd_H2_SETTING_SIZE)
+ {
+ mhd_h2_setting_encode3 (mhd_H2_STNGS_HEADER_TABLE_SIZE,
+ (uint_least32_t) c->h2.hk_dec.max_allowed_dyn_size,
+ payload + payload_pos);
+ payload_pos += mhd_H2_SETTING_SIZE;
+ }
+
+ if (payload_space >= payload_pos + mhd_H2_SETTING_SIZE)
+ {
+ mhd_h2_setting_encode3 (mhd_H2_STNGS_ENABLE_PUSH,
+ 0u,
+ payload + payload_pos);
+ payload_pos += mhd_H2_SETTING_SIZE;
+ }
+
+ if (payload_space >= payload_pos + mhd_H2_SETTING_SIZE)
+ {
+ mhd_assert (0x7FFFFFFFu >= c->h2.rcv_cfg.stream_init_win_sz);
+ mhd_h2_setting_encode3 (mhd_H2_STNGS_INITIAL_WINDOW_SIZE,
+ c->h2.rcv_cfg.stream_init_win_sz,
+ payload + payload_pos);
+ payload_pos += mhd_H2_SETTING_SIZE;
+ }
+
+ if (payload_space >= payload_pos + mhd_H2_SETTING_SIZE)
+ {
+ mhd_assert (0x7FFFFFFFu >= c->h2.rcv_cfg.max_frame_size);
+ mhd_h2_setting_encode3 (mhd_H2_STNGS_MAX_FRAME_SIZE,
+ c->h2.rcv_cfg.max_frame_size,
+ payload + payload_pos);
+ payload_pos += mhd_H2_SETTING_SIZE;
+ }
+
+ if (payload_space >= payload_pos + mhd_H2_SETTING_SIZE)
+ {
+ mhd_h2_setting_encode3 (mhd_H2_STNGS_MAX_HEADER_LIST_SIZE,
+ c->h2.rcv_cfg.max_header_list,
+ payload + payload_pos);
+ payload_pos += mhd_H2_SETTING_SIZE;
+ }
+
+ if (payload_space >= payload_pos + mhd_H2_SETTING_SIZE)
+ {
+ mhd_h2_setting_encode3 (mhd_H2_STNGS_ENABLE_CONNECT_PROTOCOL,
+ 0u,
+ payload + payload_pos);
+ payload_pos += mhd_H2_SETTING_SIZE;
+ }
+
+ if (payload_space >= payload_pos + mhd_H2_SETTING_SIZE)
+ {
+ mhd_h2_setting_encode3 (mhd_H2_STNGS_NO_RFC7540_PRIORITIES,
+ 1u,
+ payload + payload_pos);
+ payload_pos += mhd_H2_SETTING_SIZE;
+ set_all_settings = true;
+ }
+ else
+ set_all_settings = false;
+
+ if (! set_all_settings)
+ {
+ mhd_H2_W_BUFF_UPDATING_CLEAR (&(c->h2));
+ mhd_h2_conn_finish (c,
+ mhd_H2_ERR_INTERNAL_ERROR,
+ true);
+ return false;
+ }
+
+ mhd_assert (0u == payload_pos % mhd_H2_SETTING_SIZE);
+
+ mhd_h2_frame_set_payload_size (&h2frame,
+ payload_pos);
+
+ final_fr_hdr_size = mhd_h2_frame_hdr_encode (&h2frame,
+ buff.size,
+ (uint8_t *) buff.data);
+ mhd_assert (fr_hdr_size == final_fr_hdr_size);
+ mhd_h2_out_buff_unlock (c,
+ final_fr_hdr_size + payload_pos);
+
+ c->h2.state.init.sent_setns = true;
+ ++c->h2.state.sent_setns_noakc;
+
+ return true;
+}
diff --git a/src/mhd2/h2/h2_proc_settings.h b/src/mhd2/h2/h2_proc_settings.h
@@ -0,0 +1,119 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
+/*
+ This file is part of GNU libmicrohttpd.
+ Copyright (C) 2025 Evgeny Grin (Karlson2k)
+
+ GNU libmicrohttpd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ GNU libmicrohttpd is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ Alternatively, you can redistribute GNU libmicrohttpd and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of
+ the License, or (at your option) any later version, together
+ with the eCos exception, as follows:
+
+ As a special exception, if other files instantiate templates or
+ use macros or inline functions from this file, or you compile this
+ file and link it with other works to produce a work based on this
+ file, this file does not by itself cause the resulting work to be
+ covered by the GNU General Public License. However the source code
+ for this file must still be made available in accordance with
+ section (3) of the GNU General Public License v2.
+
+ This exception does not invalidate any other reasons why a work
+ based on this file might be covered by the GNU General Public
+ License.
+
+ You should have received copies of the GNU Lesser General Public
+ License and the GNU General Public License along with this library;
+ if not, see <https://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file src/mhd2/h2/h2_proc_settings.h
+ * @brief Declarations of HTTP/2 connection settings processing functions
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+#ifndef MHD_H2_PROC_SETTINGS_H
+#define MHD_H2_PROC_SETTINGS_H 1
+
+#include "mhd_sys_options.h"
+
+#include "sys_bool_type.h"
+
+/**
+ * Default value of Initial Window Size
+ */
+#define mhd_H2_STNG_DEF_INIT_WIN_SIZE (65535u)
+
+/**
+ * Minimal allowed value of Maximum Frame Size
+ */
+#define mhd_H2_STNG_MIN_MAX_FRAME_SIZE (16384u)
+
+/**
+ * Maximum allowed value of Maximum Frame Size
+ */
+#define mhd_H2_STNG_MAX_MAX_FRAME_SIZE (16777215u)
+
+/**
+ * Default value of Maximum Frame Size
+ */
+#define mhd_H2_STNG_DEF_MAX_FRAME_SIZE mhd_H2_STNG_MIN_MAX_FRAME_SIZE
+
+struct MHD_Connection; /* forward declaration */
+struct mhd_Buffer; /* forward declaration */
+
+enum mhd_H2SettingsProcessResult
+{
+ /**
+ * Settings processed.
+ * Settings ACK queued.
+ */
+ mhd_H2_STNGS_PROC_OK = 0
+ ,
+ /**
+ * No output buffer space to queue settings ACK.
+ * The frame should be processed later again
+ */
+ mhd_H2_STNGS_PROC_NO_OUT_BUFF
+ ,
+ /**
+ * Settings error.
+ * GOAWAY frame queued, connection marked as closing
+ */
+ mhd_H2_STNGS_PROC_STNGS_ERR
+};
+
+/**
+ * Process first settings frame sent by peer
+ * @param c the connection to use
+ * @param stngs_payload the payload of the settings frame
+ * @return 'true' if setting successfully processed;
+ * 'false' on failure, GOAWAY queued if possible and connection
+ * is marked as "closing" or "broken".
+ */
+MHD_INTERNAL bool
+mhd_h2_proc_first_settings (struct MHD_Connection *restrict c,
+ const struct mhd_Buffer *restrict stngs_payload)
+MHD_FN_PAR_NONNULL_ALL_;
+
+MHD_INTERNAL enum mhd_H2SettingsProcessResult
+mhd_h2_proc_new_settings (struct MHD_Connection *restrict c,
+ const struct mhd_Buffer *restrict stngs_payload)
+MHD_FN_PAR_NONNULL_ALL_;
+
+
+MHD_INTERNAL bool
+mhd_h2_q_settings_first_fr (struct MHD_Connection *restrict c)
+MHD_FN_PAR_NONNULL_ALL_;
+
+#endif /* ! MHD_H2_PROC_SETTINGS_H */
diff --git a/src/mhd2/h2/h2_reply_funcs.c b/src/mhd2/h2/h2_reply_funcs.c
@@ -0,0 +1,599 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
+/*
+ This file is part of GNU libmicrohttpd.
+ Copyright (C) 2025 Evgeny Grin (Karlson2k)
+
+ GNU libmicrohttpd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ GNU libmicrohttpd is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ Alternatively, you can redistribute GNU libmicrohttpd and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of
+ the License, or (at your option) any later version, together
+ with the eCos exception, as follows:
+
+ As a special exception, if other files instantiate templates or
+ use macros or inline functions from this file, or you compile this
+ file and link it with other works to produce a work based on this
+ file, this file does not by itself cause the resulting work to be
+ covered by the GNU General Public License. However the source code
+ for this file must still be made available in accordance with
+ section (3) of the GNU General Public License v2.
+
+ This exception does not invalidate any other reasons why a work
+ based on this file might be covered by the GNU General Public
+ License.
+
+ You should have received copies of the GNU Lesser General Public
+ License and the GNU General Public License along with this library;
+ if not, see <https://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file src/mhd2/h2/h2_reply_funcs.c
+ * @brief Definitions of HTTP/2 reply sending functions
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+#include "mhd_sys_options.h"
+
+#include "sys_bool_type.h"
+#include "sys_base_types.h"
+
+#include "mhd_str_macros.h"
+
+#include "mhd_constexpr.h"
+
+#include "mhd_assert.h"
+#include "mhd_unreachable.h"
+
+#include "mhd_buffer.h"
+#include "mhd_response.h"
+#include "mhd_connection.h"
+#include "mhd_daemon.h"
+
+#include "mhd_str.h"
+#include "mhd_read_file.h"
+
+#include "stream_process_reply.h"
+
+#include "h2_conn_data.h"
+#include "h2_stream_data.h"
+
+#include "h2_frame_init.h"
+#include "h2_proc_conn.h"
+#include "h2_proc_out.h"
+
+#include "h2_frame_codec.h"
+
+#include "hpack/mhd_hpack_codec.h"
+
+
+#include "h2_reply_funcs.h"
+
+struct mhd_H2Stream; /* Forward declaration */
+
+/* local wrapper */
+mhd_static_inline MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_INOUT_ (1) MHD_FN_PAR_IN_ (2) MHD_FN_PAR_IN_ (3)
+MHD_FN_PAR_OUT_SIZE_ (6,5) MHD_FN_PAR_OUT_ (7) bool
+enc_field (struct mhd_HpackEncContext *restrict hk_enc,
+ const struct mhd_BufferConst *restrict name,
+ const struct mhd_BufferConst *restrict value,
+ enum mhd_HpackEncPolicy enc_pol,
+ const size_t out_buff_size,
+ uint8_t *restrict out_buff,
+ size_t *restrict bytes_encoded)
+{
+ enum mhd_HpackEncResult enc_res;
+
+ enc_res = mhd_hpack_enc_field (hk_enc,
+ name,
+ value,
+ enc_pol,
+ out_buff_size,
+ out_buff,
+ bytes_encoded);
+
+ mhd_assert (mhd_HPACK_ENC_RES_ALLOC_ERR != enc_res);
+
+ return (mhd_HPACK_ENC_RES_OK == enc_res);
+}
+
+
+static MHD_FN_PAR_NONNULL_ALL_ size_t
+stream_headers_encode (struct mhd_H2Stream *restrict s,
+ struct mhd_Buffer *restrict pl,
+ bool *restrict fields_complete)
+{
+ struct mhd_HpackEncContext *const hk_enc = &(s->c->h2.hk_enc);
+ struct MHD_Response *const r = s->rpl.response;
+ uint8_t *restrict buff = (uint8_t *) pl->data;
+ size_t pos;
+ size_t pos_incr;
+ size_t fld_num;
+ enum mhd_HpackEncResult enc_res;
+ struct mhd_ResponseHeader *hdr;
+
+ *fields_complete = false; /* Could be updated at the end */
+ pos = 0u;
+ fld_num = 0u;
+
+ /* Pseudo-header */
+ if (fld_num >= s->rpl.fields.num_sent)
+ {
+ enc_res = mhd_hpack_enc_ph_status (hk_enc,
+ (uint_fast16_t) s->rpl.response->sc,
+ mhd_HPACK_ENC_PFS_POL_NORMAL,
+ pl->size - pos,
+ buff + pos,
+ &pos_incr);
+ mhd_assert (mhd_HPACK_ENC_RES_ALLOC_ERR != enc_res);
+ if (mhd_HPACK_ENC_RES_OK != enc_res)
+ return pos;
+
+ pos += pos_incr;
+ ++(s->rpl.fields.num_sent);
+ }
+ ++fld_num;
+
+ /* "date" header */
+
+ if ( (! r->cfg.has_hdr_date) &&
+ (! s->c->daemon->req_cfg.suppress_date) )
+ {
+ if (fld_num >= s->rpl.fields.num_sent)
+ {
+ char val_buff[30];
+ if (mhd_build_date_str (val_buff))
+ {
+ static const struct mhd_BufferConst hdr_name =
+ mhd_MSTR_INIT ("date");
+ struct mhd_BufferConst hdr_val;
+
+ hdr_val.data = val_buff;
+ hdr_val.size = 29u;
+
+ if (! enc_field (hk_enc,
+ &hdr_name,
+ &hdr_val,
+ 1 >= s->c->h2.streams.num_streams ?
+ mhd_HPACK_ENC_POL_LOW_PRIO : mhd_HPACK_ENC_POL_NEUTRAL,
+ pl->size - pos,
+ buff + pos,
+ &pos_incr))
+ return pos;
+
+ pos += pos_incr;
+ ++(s->rpl.fields.num_sent);
+ }
+ }
+ ++fld_num;
+ }
+
+ /* "content-length" header */
+ if (s->rpl.fields.auto_cntn_len)
+ {
+ if (fld_num >= s->rpl.fields.num_sent)
+ {
+ static const struct mhd_BufferConst hdr_name =
+ mhd_MSTR_INIT ("content-length");
+ char val_buff[21]; /* Maximum supported value is 18446744073709551615 */
+ struct mhd_BufferConst hdr_val;
+
+ mhd_assert (MHD_SIZE_UNKNOWN > r->cntn_size);
+ hdr_val.data = val_buff;
+ hdr_val.size = mhd_uint64_to_str (r->cntn_size,
+ val_buff,
+ sizeof(val_buff));
+ mhd_assert (0u != hdr_val.size);
+
+ if (! enc_field (hk_enc,
+ &hdr_name,
+ &hdr_val,
+ r->reuse.reusable ?
+ mhd_HPACK_ENC_POL_NEUTRAL : mhd_HPACK_ENC_POL_LOW_PRIO,
+ pl->size - pos,
+ buff + pos,
+ &pos_incr))
+ return pos;
+
+ pos += pos_incr;
+ ++(s->rpl.fields.num_sent);
+ }
+ ++fld_num;
+ }
+
+ /* User headers */
+
+ for (hdr = mhd_DLINKEDL_GET_FIRST (r, headers);
+ NULL != hdr;
+ hdr = mhd_DLINKEDL_GET_NEXT (hdr, headers))
+ {
+ if (NULL == hdr->h2.name.data)
+ continue; /* The header is HTTP/1.x only */
+
+ if (fld_num >= s->rpl.fields.num_sent)
+ {
+ if (! enc_field (hk_enc,
+ &(hdr->h2.name),
+ &(hdr->h2.value),
+ mhd_HPACK_ENC_POL_NEUTRAL,
+ pl->size - pos,
+ buff + pos,
+ &pos_incr))
+ return pos;
+ pos += pos_incr;
+ ++(s->rpl.fields.num_sent);
+ }
+ ++fld_num;
+ }
+
+ *fields_complete = true;
+ return pos;
+}
+
+
+static MHD_FN_PAR_NONNULL_ALL_ bool
+stream_headers_send (struct mhd_H2Stream *s)
+{
+ union mhd_H2FrameUnion h2frame;
+ struct mhd_H2FrameHeadersInfo *hdrs;
+ struct mhd_H2FrameContinuationInfo *cont;
+ struct mhd_Buffer buff;
+ struct mhd_Buffer payload;
+ size_t payload_offset;
+ bool *complete_header;
+ size_t payload_used;
+
+ mhd_assert (mhd_H2_RPL_STAGE_HEADERS_INCOMPLETE == s->rpl.stage);
+
+ if (0u == s->rpl.fields.num_sent)
+ {
+ hdrs = mhd_h2_frame_init_headers (&h2frame,
+ s->stream_id,
+ false, /* could be updated below */
+ ! s->rpl.send_content);
+ cont = NULL;
+ complete_header = &(hdrs->end_headers);
+ }
+ else
+ {
+ hdrs = NULL;
+ cont = mhd_h2_frame_init_continuation (&h2frame,
+ s->stream_id,
+ false); /* could be updated below */
+ complete_header = &(cont->end_headers);
+ }
+
+ if (! mhd_h2_out_buff_acquire_fr_w_payload (s->c,
+ &h2frame,
+ &buff,
+ &payload_offset))
+ return false;
+
+ payload.data = buff.data + payload_offset;
+ payload.size = buff.size - payload_offset;
+
+ payload_used = stream_headers_encode (s,
+ &payload,
+ complete_header);
+
+ if (0u != payload_used)
+ {
+ const size_t full_fr_size = mhd_h2_frame_set_payload_size (&h2frame,
+ payload_used);
+ const size_t final_fr_hdr_size =
+ mhd_h2_frame_hdr_encode (&h2frame,
+ payload_offset,
+ (uint8_t*) buff.data);
+ mhd_assert (payload_offset == final_fr_hdr_size);
+ (void) final_fr_hdr_size;
+
+ mhd_h2_out_buff_unlock (s->c,
+ full_fr_size);
+ if (*complete_header)
+ {
+ s->rpl.stage = s->rpl.send_content ?
+ mhd_H2_RPL_STAGE_HEADERS_COMPLETE :
+ mhd_H2_RPL_STAGE_END_STREAM;
+ }
+ return true; /* Success exit point */
+ }
+
+ mhd_h2_out_buff_unlock (s->c,
+ 0u);
+
+ if (((s->c->write_buffer_size - s->c->write_buffer_append_offset) >=
+ mhd_H2_FR_HDR_BASE_SIZE + s->c->h2.peer.max_frame_size) ||
+ (0u == s->c->write_buffer_append_offset))
+ {
+ /* The output buffer may contain the maximum size frame, but no single
+ header has been added. It makes no sense to wait more as the
+ response header is too large to be used in this connection. */
+ s->state.mhd_err = mhd_H2_ERR_INTERNAL_ERROR;
+ s->rpl.stage = mhd_H2_RPL_STAGE_BROKEN;
+ return false;
+ }
+
+ return false;
+}
+
+
+static MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_OUT_SIZE_ (4,3)
+MHD_FN_PAR_OUT_ (5) bool
+content_read_iovec (struct MHD_Response *restrict r,
+ uint_fast64_t offset,
+ size_t buff_size,
+ uint8_t *restrict buff,
+ size_t *restrict written)
+{
+ size_t i;
+ uint_fast64_t skipped;
+ const mhd_iovec *const restrict iov = r->cntn.iovec.iov;
+
+ mhd_assert (mhd_RESPONSE_CONTENT_DATA_IOVEC == r->cntn_dtype);
+
+ skipped = 0u;
+
+ for (i = 0u; r->cntn.iovec.cnt > i; ++i)
+ {
+ if (skipped + iov[i].iov_len > offset)
+ break;
+ skipped += iov[i].iov_len;
+ mhd_assert (skipped >= iov[i].iov_len);
+ }
+
+ if (r->cntn.iovec.cnt == i)
+ return false;
+
+ if (1)
+ {
+ size_t elmnt_copy;
+ const size_t elmnt_off = (size_t) (offset - skipped);
+
+ if (elmnt_off != (offset - skipped))
+ return false;
+
+ mhd_assert (0u != iov[i].iov_len);
+
+ elmnt_copy = (size_t) (iov[i].iov_len - elmnt_off);
+ if (buff_size < elmnt_copy)
+ elmnt_copy = buff_size;
+
+ memcpy (buff,
+ ((const uint8_t *) iov[i].iov_base) + elmnt_off,
+ elmnt_copy);
+ *written = elmnt_copy;
+
+ if (elmnt_copy == buff_size)
+ return true;
+
+ ++i;
+ }
+
+ for ((void) i; r->cntn.iovec.cnt > i; ++i)
+ {
+ mhd_assert (0u != iov[i].iov_len);
+ if ((buff_size - *written) <= iov[i].iov_len)
+ {
+ memcpy (buff + *written,
+ iov[i].iov_base,
+ buff_size - *written);
+ *written = buff_size;
+ return true;
+ }
+ memcpy (buff + *written,
+ iov[i].iov_base,
+ (size_t) iov[i].iov_len);
+ *written += (size_t) iov[i].iov_len;
+ mhd_assert (*written > iov[i].iov_len);
+ }
+ return true;
+}
+
+
+static MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_OUT_SIZE_ (4,3)
+MHD_FN_PAR_OUT_ (5) bool
+content_read_file (struct MHD_Response *restrict r,
+ uint_fast64_t offset,
+ size_t buff_size,
+ uint8_t *restrict buff,
+ size_t *restrict written)
+{
+ uint_fast64_t file_off;
+
+ mhd_assert (mhd_RESPONSE_CONTENT_DATA_FILE == r->cntn_dtype);
+ // TODO: support pipe reading without position
+ mhd_assert (! r->cntn.file.is_pipe);
+
+ file_off = offset + r->cntn.file.offset;
+ if (file_off < offset)
+ return false; /* Offset too large */
+
+ return (mhd_FILE_READ_OK ==
+ mhd_read_file (r->cntn.file.fd,
+ file_off,
+ buff_size,
+ (char*) buff,
+ written));
+}
+
+
+mhd_constexpr uint_least32_t min_size_for_data = 128u;
+
+static MHD_FN_PAR_NONNULL_ALL_ bool
+stream_content_send (struct mhd_H2Stream *s)
+{
+ struct MHD_Response *const r = s->rpl.response;
+ union mhd_H2FrameUnion h2frame;
+ struct mhd_H2FrameDataInfo *dat;
+ struct mhd_Buffer buff;
+ uint8_t *pld_buff;
+ size_t pld_buff_size;
+ size_t cntnt_left;
+ size_t payload_offset;
+ size_t payload_used;
+ int_least32_t wndw_limit;
+ uint_least32_t full_payload_limit;
+
+ mhd_assert (mhd_H2_RPL_STAGE_HEADERS_COMPLETE == s->rpl.stage);
+ mhd_assert (s->rpl.send_content);
+ mhd_assert (0u != r->cntn_size);
+ mhd_assert (! r->cfg.head_only);
+
+ if (s->c->h2.state.send_window < s->state.send_window)
+ wndw_limit = s->c->h2.state.send_window;
+ else
+ wndw_limit = s->state.send_window;
+
+ if (0 >= wndw_limit)
+ return false; /* The peer should increment window(s) first */
+
+ full_payload_limit = (uint_least32_t) wndw_limit;
+ if (MHD_SIZE_UNKNOWN != r->cntn_size)
+ {
+ cntnt_left = r->cntn_size - s->rpl.cntn_read_pos;
+ if (cntnt_left < full_payload_limit)
+ full_payload_limit = (uint_least32_t) cntnt_left;
+ }
+ else
+ cntnt_left = MHD_SIZE_UNKNOWN;
+
+ if ((min_size_for_data > full_payload_limit)
+ && (cntnt_left != full_payload_limit))
+ return false;
+
+ dat = mhd_h2_frame_init_data (&h2frame,
+ s->stream_id,
+ false); /* could be updated below */
+
+ if (! mhd_h2_out_buff_acquire_fr_w_payload_l (s->c,
+ &h2frame,
+ full_payload_limit,
+ &buff,
+ &payload_offset))
+ return false;
+
+ pld_buff = (uint8_t*) buff.data + payload_offset;
+ pld_buff_size = buff.size - payload_offset;
+ mhd_assert (mhd_H2_FR_HDR_BASE_SIZE < pld_buff_size);
+
+ mhd_assert (r->cntn_size > s->rpl.cntn_read_pos);
+
+ payload_used = 0u;
+ switch (r->cntn_dtype)
+ {
+ case mhd_RESPONSE_CONTENT_DATA_BUFFER:
+ payload_used = (size_t) full_payload_limit;
+ memcpy (pld_buff,
+ r->cntn.buf + s->rpl.cntn_read_pos,
+ payload_used);
+ break;
+ case mhd_RESPONSE_CONTENT_DATA_IOVEC:
+ if (! content_read_iovec (r,
+ s->rpl.cntn_read_pos,
+ pld_buff_size,
+ pld_buff,
+ &payload_used))
+ payload_used = 0u;
+ break;
+ case mhd_RESPONSE_CONTENT_DATA_FILE:
+ if (! content_read_file (r,
+ s->rpl.cntn_read_pos,
+ pld_buff_size,
+ pld_buff,
+ &payload_used))
+ payload_used = 0u;
+ break;
+ case mhd_RESPONSE_CONTENT_DATA_CALLBACK:
+ s->rpl.stage = mhd_H2_RPL_STAGE_BROKEN;
+ break;
+ case mhd_RESPONSE_CONTENT_DATA_INVALID:
+ default:
+ mhd_UNREACHABLE ();
+ s->rpl.stage = mhd_H2_RPL_STAGE_BROKEN;
+ break;
+ }
+
+ dat->end_stream = (payload_used + s->rpl.cntn_read_pos == r->cntn_size);
+
+ if (0u != payload_used)
+ {
+ const size_t full_fr_size = mhd_h2_frame_set_payload_size (&h2frame,
+ payload_used);
+ const size_t final_fr_hdr_size =
+ mhd_h2_frame_hdr_encode (&h2frame,
+ payload_offset,
+ (uint8_t*) buff.data);
+ mhd_assert (payload_offset == final_fr_hdr_size);
+ (void) final_fr_hdr_size;
+
+ mhd_h2_out_buff_unlock (s->c,
+ full_fr_size);
+ s->c->h2.state.send_window -=
+ (int_least32_t) (full_fr_size - mhd_H2_FR_HDR_BASE_SIZE);
+ mhd_assert (0 <= s->c->h2.state.send_window);
+ s->state.send_window -=
+ (int_least32_t) (full_fr_size - mhd_H2_FR_HDR_BASE_SIZE);
+ mhd_assert (0 <= s->state.send_window);
+
+ return true; /* Success exit point */
+ }
+
+ mhd_h2_out_buff_unlock (s->c,
+ 0u);
+
+ s->state.mhd_err = mhd_H2_ERR_INTERNAL_ERROR;
+ s->rpl.stage = mhd_H2_RPL_STAGE_BROKEN;
+
+ return false;
+
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool
+mhd_h2_stream_reply_send (struct mhd_H2Stream *s)
+{
+ mhd_assert (s->is_h2);
+ mhd_assert (mhd_H2_RPL_STAGE_END_STREAM != s->rpl.stage);
+ mhd_assert (mhd_H2_RPL_STAGE_BROKEN != s->rpl.stage);
+
+ if (mhd_H2_RPL_STAGE_HEADERS_INCOMPLETE == s->rpl.stage)
+ {
+ if (! mhd_hpack_enc_dyn_resize (&(s->c->h2.hk_enc)))
+ {
+ /* Ignore failure of the next function as the connection and stream
+ will be retried next round if connection is not aborted. */
+ mhd_h2_conn_finish (s->c,
+ mhd_H2_ERR_INTERNAL_ERROR,
+ false);
+ return false;
+ }
+
+ if (! stream_headers_send (s))
+ return false;
+
+ if ((mhd_H2_RPL_STAGE_HEADERS_COMPLETE == s->rpl.stage) &&
+ (mhd_RESPONSE_CONTENT_DATA_FILE <= s->rpl.response->cntn_dtype))
+ return true; /* Do not combine with content sending as the data is not ready yet */
+ }
+
+ if (mhd_H2_RPL_STAGE_HEADERS_COMPLETE == s->rpl.stage)
+ {
+ if (! stream_content_send (s))
+ return false;
+ }
+
+ return true;
+
+}
diff --git a/src/mhd2/h2/h2_reply_funcs.h b/src/mhd2/h2/h2_reply_funcs.h
@@ -0,0 +1,58 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
+/*
+ This file is part of GNU libmicrohttpd.
+ Copyright (C) 2025 Evgeny Grin (Karlson2k)
+
+ GNU libmicrohttpd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ GNU libmicrohttpd is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ Alternatively, you can redistribute GNU libmicrohttpd and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of
+ the License, or (at your option) any later version, together
+ with the eCos exception, as follows:
+
+ As a special exception, if other files instantiate templates or
+ use macros or inline functions from this file, or you compile this
+ file and link it with other works to produce a work based on this
+ file, this file does not by itself cause the resulting work to be
+ covered by the GNU General Public License. However the source code
+ for this file must still be made available in accordance with
+ section (3) of the GNU General Public License v2.
+
+ This exception does not invalidate any other reasons why a work
+ based on this file might be covered by the GNU General Public
+ License.
+
+ You should have received copies of the GNU Lesser General Public
+ License and the GNU General Public License along with this library;
+ if not, see <https://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file src/mhd2/h2/h2_reply_funcs.h
+ * @brief Declarations of HTTP/2 reply sending functions
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+#ifndef MHD_H2_REPLY_FUNCS_H
+#define MHD_H2_REPLY_FUNCS_H 1
+
+#include "mhd_sys_options.h"
+
+#include "sys_bool_type.h"
+
+struct mhd_H2Stream; /* Forward declaration */
+
+MHD_INTERNAL bool
+mhd_h2_stream_reply_send (struct mhd_H2Stream *s)
+MHD_FN_PAR_NONNULL_ALL_;
+
+#endif /* ! MHD_H2_REPLY_FUNCS_H */
diff --git a/src/mhd2/h2/h2_req_data.h b/src/mhd2/h2/h2_req_data.h
@@ -0,0 +1,149 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
+/*
+ This file is part of GNU libmicrohttpd.
+ Copyright (C) 2025 Evgeny Grin (Karlson2k)
+
+ GNU libmicrohttpd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ GNU libmicrohttpd is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ Alternatively, you can redistribute GNU libmicrohttpd and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of
+ the License, or (at your option) any later version, together
+ with the eCos exception, as follows:
+
+ As a special exception, if other files instantiate templates or
+ use macros or inline functions from this file, or you compile this
+ file and link it with other works to produce a work based on this
+ file, this file does not by itself cause the resulting work to be
+ covered by the GNU General Public License. However the source code
+ for this file must still be made available in accordance with
+ section (3) of the GNU General Public License v2.
+
+ This exception does not invalidate any other reasons why a work
+ based on this file might be covered by the GNU General Public
+ License.
+
+ You should have received copies of the GNU Lesser General Public
+ License and the GNU General Public License along with this library;
+ if not, see <https://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file src/mhd2/h2/h2_req_data.h
+ * @brief Definition of HTTP/2 request data structure
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+#ifndef MHD_H2_REQ_DATA_H
+#define MHD_H2_REQ_DATA_H 1
+
+#include "mhd_sys_options.h"
+
+#include "sys_bool_type.h"
+#include "sys_base_types.h"
+
+#include "http_method.h"
+
+struct MHD_Connection; /* Forward declaration */
+
+
+enum MHD_FIXED_ENUM_ mhd_H2ReqStage
+{
+ /**
+ * Headers are not completely received.
+ * Processing the opening HEADERS frame or receiving and processing
+ * CONTINUATION frames.
+ */
+ mhd_H2_REQ_STAGE_HEADERS_INCOMPLETE
+ ,
+ mhd_H2_REQ_STAGE_HEADERS_DECODING
+ ,
+ mhd_H2_REQ_STAGE_HEADERS_PROCESSING
+ ,
+ /**
+ * Headers are completely received.
+ * DATA frames or second HEADERS frame (containing trailers) can be received.
+ */
+ mhd_H2_REQ_STAGE_HEADERS_COMPLETE
+ ,
+ /**
+ * Trailers are not completely received.
+ * Processing the second HEADERS frame (which started trailers) or receiving
+ * and processing CONTINUATION frames.
+ */
+ mhd_H2_REQ_STAGE_TRAILERS_INCOMPLETE
+ ,
+ mhd_H2_REQ_STAGE_TRAILERS_DECODING
+ ,
+ mhd_H2_REQ_STAGE_TRAILERS_PROCESSING
+ ,
+ /**
+ * The client must not send any HEADERS or DATA frames.
+ */
+ mhd_H2_REQ_STAGE_END_STREAM
+ ,
+ /**
+ * Any frames ignored with RST_STREAM.
+ */
+ mhd_H2_REQ_STAGE_BROKEN
+
+};
+
+struct mhd_H2RequestData
+{
+ /**
+ * Always 'true'
+ */
+ bool is_http2;
+
+ enum mhd_H2ReqStage stage;
+
+ /**
+ * 'true' when 'end stream' flag was received. The stage could be still
+ * #mhd_H2_REQ_STAGE_HEADERS_INCOMPLETE or
+ * #mhd_H2_REQ_STAGE_TRAILERS_INCOMPLETE as CONTINUATION frames are being
+ * processed
+ */
+ bool got_end_stream;
+
+ enum mhd_HTTP_Method method;
+
+ uint_fast64_t cntn_size;
+
+ /**
+ * Position of ":method" pseudo-header in request items block.
+ * Set to #mhd_H2_REQ_ITEM_POS_INVALID if not available.
+ */
+ size_t pos_method;
+ /**
+ * Position of ":path" pseudo-header in request items block.
+ * Set to #mhd_H2_REQ_ITEM_POS_INVALID if not available.
+ */
+ size_t pos_path;
+ /**
+ * Position of ":authority" pseudo-header or "Host" header in request items
+ * block.
+ * Set to #mhd_H2_REQ_ITEM_POS_INVALID if not available.
+ */
+ size_t pos_authority;
+
+ struct mhd_ApplicationAction app_act;
+
+ /**
+ * Set to 'true' when application gets any information about this
+ * request or stream.
+ */
+ bool app_seen;
+
+ void *app_context;
+};
+
+#endif /* ! MHD_H2_REQ_DATA_H */
diff --git a/src/mhd2/h2/h2_req_fields.c b/src/mhd2/h2/h2_req_fields.c
@@ -0,0 +1,523 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
+/*
+ This file is part of GNU libmicrohttpd.
+ Copyright (C) 2025 Evgeny Grin (Karlson2k)
+
+ GNU libmicrohttpd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ GNU libmicrohttpd is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ Alternatively, you can redistribute GNU libmicrohttpd and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of
+ the License, or (at your option) any later version, together
+ with the eCos exception, as follows:
+
+ As a special exception, if other files instantiate templates or
+ use macros or inline functions from this file, or you compile this
+ file and link it with other works to produce a work based on this
+ file, this file does not by itself cause the resulting work to be
+ covered by the GNU General Public License. However the source code
+ for this file must still be made available in accordance with
+ section (3) of the GNU General Public License v2.
+
+ This exception does not invalidate any other reasons why a work
+ based on this file might be covered by the GNU General Public
+ License.
+
+ You should have received copies of the GNU Lesser General Public
+ License and the GNU General Public License along with this library;
+ if not, see <https://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file src/mhd2/h2/h2_dec_fields.c
+ * @brief Implementation of HTTP/2 request fields functions
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+#include "mhd_sys_options.h"
+
+#include "sys_bool_type.h"
+#include "sys_base_types.h"
+
+#include <string.h>
+
+#include "mhd_assert.h"
+#include "mhd_unreachable.h"
+#include "mhd_assume.h"
+
+#include "mhd_constexpr.h"
+#include "mhd_buffer.h"
+#include "mhd_str_types.h"
+#include "mhd_str_macros.h"
+
+#include "mhd_connection.h"
+#include "h2_conn_data.h"
+
+#include "h2_stream_data.h"
+
+#include "mhd_str.h"
+
+#include "stream_process_request.h"
+
+#include "h2_req_item_kinds.h"
+#include "h2_req_item_struct.h"
+#include "h2_req_items_funcs.h"
+
+#include "hpack/mhd_hpack_codec.h"
+
+#include "h2_proc_conn.h"
+#include "h2_conn_streams.h"
+
+#include "h2_req_fields.h"
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_INOUT_ (1) MHD_FN_PAR_IN_ (2)
+MHD_FN_PAR_INOUT_ (4) MHD_FN_PAR_OUT_ (5) enum mhd_H2DecFieldsResult
+mhd_h2_req_fields_decode (struct mhd_HpackDecContext *restrict hk_dec,
+ const struct mhd_Buffer *restrict enc_data,
+ bool are_trailers,
+ struct mhd_H2ReqItemsBlock *restrict ib,
+ size_t *restrict left_unprocessed)
+{
+ mhd_constexpr unsigned int max_no_fields = 8u;
+ const enum mhd_H2RequestItemKind field_kind =
+ are_trailers ? mhd_H2_RIK_TRAILER : mhd_H2_RIK_HEADER;
+ size_t pos;
+ unsigned int no_fields;
+ enum mhd_H2DecFieldsResult ret;
+
+ pos = 0u;
+ no_fields = 0u;
+ ret = mhd_H2_DEC_FIELDS_OK;
+
+ while (pos < enc_data->size)
+ {
+ size_t name_len;
+ size_t val_len;
+ size_t pos_incr;
+ enum mhd_HpackDecResult res;
+ struct mhd_Buffer buff;
+
+ mhd_assert (mhd_H2_DEC_FIELDS_OK == ret);
+
+ if (! mhd_h2_items_get_buff_new_item (ib,
+ &buff))
+ return mhd_H2_DEC_FIELDS_NO_SPACE;
+
+ res = mhd_hpack_dec_data (hk_dec,
+ enc_data->size - pos,
+ (const uint8_t *) enc_data->data + pos,
+ buff.size,
+ buff.data,
+ &name_len,
+ &val_len,
+ &pos_incr);
+
+ if (! mhd_HPACK_DEC_RES_IS_ERR (res))
+ {
+ mhd_assert (0u != pos_incr);
+ pos += pos_incr;
+ }
+
+ switch (res)
+ {
+ case mhd_HPACK_DEC_RES_NO_NEW_FIELD:
+ if (max_no_fields < ++no_fields)
+ {
+ ret = mhd_H2_DEC_FIELDS_PROT_ABUSE;
+ break;
+ }
+ mhd_h2_items_cancel_new_item_buff (ib);
+ continue;
+ case mhd_HPACK_DEC_RES_NEW_FIELD:
+ mhd_assert (buff.size >= (name_len + val_len + 2u));
+ mhd_assert (0 == buff.data[name_len]);
+ mhd_assert (0 == buff.data[name_len + 1 + val_len]);
+ mhd_h2_items_add_new_item_buff (ib,
+ name_len,
+ val_len,
+ (':' == buff.data[0]) ?
+ mhd_H2_RIK_PSEUDOHEADER : field_kind);
+ continue;
+ case mhd_HPACK_DEC_RES_INCOMPLETE:
+ mhd_assert (mhd_H2_DEC_FIELDS_OK == ret);
+ break;
+ case mhd_HPACK_DEC_RES_ALLOC_ERR:
+ ret = mhd_H2_DEC_FIELDS_INT_ERR;
+ break;
+ case mhd_HPACK_DEC_RES_BUFFER_TOO_SMALL: // TODO: support "minimal" decoding for decoder state updates
+ case mhd_HPACK_DEC_RES_STRING_TOO_LONG:
+ ret = mhd_H2_DEC_FIELDS_NO_SPACE;
+ break;
+ case mhd_HPACK_DEC_RES_NUMBER_TOO_LONG:
+ case mhd_HPACK_DEC_RES_DYN_SIZE_UPD_TOO_LARGE:
+ ret = mhd_H2_DEC_FIELDS_PROT_ABUSE;
+ break;
+ case mhd_HPACK_DEC_RES_DYN_SIZE_UPD_MISSING:
+ case mhd_HPACK_DEC_RES_HUFFMAN_ERR:
+ case mhd_HPACK_DEC_RES_HPACK_BAD_IDX:
+ case mhd_HPACK_DEC_RES_HPACK_ERR:
+ ret = mhd_H2_DEC_FIELDS_BROKEN_DATA;
+ break;
+ case mhd_HPACK_DEC_RES_INTERNAL_ERR:
+ default:
+ mhd_UNREACHABLE_D ("Impossible value");
+ ret = mhd_H2_DEC_FIELDS_INT_ERR;
+ break;
+ }
+ mhd_h2_items_cancel_new_item_buff (ib);
+ break; /* Break the loop */
+ }
+
+ mhd_assert (pos <= enc_data->size);
+ *left_unprocessed = enc_data->size - pos;
+
+ return ret;
+}
+
+
+static inline MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_INOUT_ (1) bool
+req_validate_fields_chars (struct mhd_H2Stream *restrict s)
+{
+ // TODO: implement checking all field chars for validity, RFC 9113 Section 8.2.1
+ (void) s;
+ return true;
+}
+
+
+static inline MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_INOUT_ (1)
+MHD_FN_PAR_OUT_ (2) bool
+req_pseudoheaders_preprocess (struct mhd_H2Stream *restrict s,
+ size_t *pos)
+{
+ static const struct MHD_String ph_method = mhd_MSTR_INIT (":method");
+ static const struct MHD_String ph_scheme = mhd_MSTR_INIT (":scheme");
+ static const struct MHD_String ph_authority = mhd_MSTR_INIT (":authority");
+ static const struct MHD_String ph_path = mhd_MSTR_INIT (":path");
+ struct mhd_H2ReqItemsBlock *const ib = s->c->h2.mem.req_ib;
+ const char *buff = mhd_h2_items_get_strings_buffc (ib);
+ bool have_method = false;
+ bool have_scheme = false;
+ bool have_authority = false;
+ bool have_path = false;
+
+ *pos = 0u;
+ while (1)
+ {
+ const struct mhd_H2ReqItem *item;
+ item = mhd_h2_items_get_item_nc (ib,
+ *pos);
+
+ if (NULL == item)
+ break;
+ else if (mhd_H2_RIK_PSEUDOHEADER != item->kind)
+ break;
+ else if ((item->name_len == ph_method.len) &&
+ (0 == memcmp (buff + item->offset,
+ ph_method.cstr,
+ ph_method.len)))
+ {
+ if (have_method)
+ return mhd_h2_stream_req_problem (s,
+ mhd_H2_REQ_PRBLM_PSEUDOHDR_DUP);
+ have_method = true;
+ s->req.pos_method = *pos;
+ s->req.method =
+ mhd_parse_http_method (item->val_len,
+ buff + item->offset + ph_method.len + 1u);
+ }
+ else if ((item->name_len == ph_path.len) &&
+ (0 == memcmp (buff + item->offset,
+ ph_path.cstr,
+ ph_path.len)))
+ {
+ if (have_path)
+ return mhd_h2_stream_req_problem (s,
+ mhd_H2_REQ_PRBLM_PSEUDOHDR_DUP);
+ have_path = true;
+ s->req.pos_path = *pos;
+ }
+ else if ((item->name_len == ph_authority.len) &&
+ (0 == memcmp (buff + item->offset,
+ ph_authority.cstr,
+ ph_authority.len)))
+ {
+ if (have_authority)
+ return mhd_h2_stream_req_problem (s,
+ mhd_H2_REQ_PRBLM_PSEUDOHDR_DUP);
+ have_authority = true;
+ s->req.pos_authority = *pos;
+ }
+ else if ((item->name_len == ph_scheme.len) &&
+ (0 == memcmp (buff + item->offset,
+ ph_scheme.cstr,
+ ph_scheme.len)))
+ {
+ if (have_scheme)
+ return mhd_h2_stream_req_problem (s,
+ mhd_H2_REQ_PRBLM_PSEUDOHDR_DUP);
+ have_scheme = true;
+ }
+ mhd_assert (':' == buff[item->offset]);
+ ++(*pos);
+ }
+
+ if (! have_method)
+ return mhd_h2_stream_req_problem (s,
+ mhd_H2_REQ_PRBLM_PSEUDOHDR_MISSING);
+
+ if (mhd_HTTP_METHOD_CONNECT != s->req.method)
+ {
+ if (! have_path || ! have_scheme)
+ return mhd_h2_stream_req_problem (s,
+ mhd_H2_REQ_PRBLM_PSEUDOHDR_EXTRA);
+ }
+ else
+ {
+ if (have_path || have_scheme)
+ return mhd_h2_stream_req_problem (s,
+ mhd_H2_REQ_PRBLM_PSEUDOHDR_MISSING);
+ }
+
+ return true;
+}
+
+
+static inline MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_INOUT_ (1)
+MHD_FN_PAR_OUT_ (2) bool
+req_headers_preprocess (struct mhd_H2Stream *restrict s,
+ size_t *pos)
+{
+ static const struct MHD_String h_host = mhd_MSTR_INIT ("host");
+ static const struct MHD_String h_cntn_len = mhd_MSTR_INIT ("content-length");
+ struct mhd_H2ReqItemsBlock *const ib = s->c->h2.mem.req_ib;
+ const char *buff = mhd_h2_items_get_strings_buffc (ib);
+
+ while (1)
+ {
+ const struct mhd_H2ReqItem *item;
+ item = mhd_h2_items_get_item_nc (ib,
+ *pos);
+
+ if (NULL == item)
+ break;
+ else if (mhd_H2_RIK_PSEUDOHEADER == item->kind)
+ return mhd_h2_stream_req_problem (s,
+ mhd_H2_REQ_PRBLM_PSEUDOHDR_AFTER_HDR);
+ else if (mhd_H2_RIK_HEADER != item->kind)
+ (void) 0; /* skip */
+ else if ((item->name_len == h_cntn_len.len) &&
+ (0 == memcmp (buff + item->offset,
+ h_cntn_len.cstr,
+ h_cntn_len.len)))
+ {
+ uint_fast64_t cntnt_len;
+
+ if ((0u == item->val_len) ||
+ (item->val_len !=
+ mhd_str_to_uint64 (buff + item->offset + h_cntn_len.len + 1u,
+ &cntnt_len)))
+ return mhd_h2_stream_req_problem (s,
+ mhd_H2_REQ_PRBLM_CNTNT_LEN_WRONG);
+
+ if ((MHD_SIZE_UNKNOWN != s->req.cntn_size) &&
+ (s->req.cntn_size != cntnt_len))
+ return mhd_h2_stream_req_problem (s,
+ mhd_H2_REQ_PRBLM_CNTNT_LEN_WRONG);
+ else
+ s->req.cntn_size = cntnt_len;
+ }
+ else if ((item->name_len == h_host.len) &&
+ (0 == memcmp (buff + item->offset,
+ h_host.cstr,
+ h_host.len)))
+ {
+ if (mhd_H2_REQ_ITEM_POS_INVALID == s->req.pos_authority)
+ s->req.pos_authority = *pos;
+ else
+ {
+ const struct mhd_H2ReqItem *item_auth =
+ mhd_h2_items_get_item_nc (ib,
+ s->req.pos_authority);
+ mhd_assert (NULL != item_auth);
+ if ((item_auth->val_len != item->val_len) ||
+ (! mhd_str_equal_caseless_bin_n (buff + item->offset
+ + item->name_len + 1u,
+ buff + item_auth->offset
+ + item_auth->name_len + 1u,
+ item->val_len)))
+ return
+ mhd_h2_stream_req_problem (s,
+ mhd_H2_REQ_PRBLM_HOST_HDR_WRONG_EXTRA);
+ }
+ }
+
+ ++(*pos);
+ mhd_assert ((mhd_H2_RIK_HEADER != item->kind) ||
+ (':' != buff[item->offset]));
+ }
+
+ return true;
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_INOUT_ (1) bool
+mhd_h2_req_headers_preprocess (struct mhd_H2Stream *restrict s)
+{
+ size_t pos;
+
+ mhd_assert (mhd_h2_items_debug_get_streamid (s->c->h2.mem.req_ib)
+ == s->stream_id);
+
+ if (! req_validate_fields_chars (s))
+ return false;
+
+ if (! req_pseudoheaders_preprocess (s,
+ &pos))
+ return false;
+
+ mhd_assert (0u != pos);
+ mhd_assert (mhd_HTTP_METHOD_NO_METHOD != s->req.method);
+
+ if (! req_headers_preprocess (s,
+ &pos))
+ return false;
+
+ return true;
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_INOUT_ (1) bool
+mhd_h2_req_uri_parse (struct mhd_H2Stream *restrict s)
+{
+ struct mhd_H2ReqItemsBlock *const restrict ib = s->c->h2.mem.req_ib;
+ char *const restrict buff = mhd_h2_items_get_strings_buff (ib);
+ struct mhd_H2ReqItem *restrict item =
+ mhd_h2_items_get_item_n (ib,
+ s->req.pos_path);
+ const size_t path_start = (size_t) (item->offset + item->name_len + 1u);
+ char *questn_mark;
+
+ questn_mark = (char*) memchr (buff + path_start,
+ '?',
+ (size_t) item->val_len);
+ if (NULL == questn_mark)
+ {
+ item->val_len =
+ (uint_least32_t)
+ mhd_str_dec_norm_uri_path ((size_t) item->val_len,
+ buff + path_start);
+ }
+ else
+ {
+ const size_t path_len = (size_t) (questn_mark - (buff + path_start));
+ const size_t uri_end = (size_t) (path_start + item->val_len);
+ size_t i = path_start + path_len + 1u;
+
+ mhd_assert (path_len < item->val_len);
+
+ buff[path_start + path_len] = '\0'; /* Zero-terminate the path */
+ item->val_len =
+ (uint_least32_t)
+ mhd_str_dec_norm_uri_path (path_len,
+ buff + path_start);
+
+ do
+ {
+ size_t name_start;
+ size_t name_len;
+ size_t value_start;
+ size_t value_len;
+
+ value_start = 0u;
+ for (name_start = i; i < uri_end; ++i) /* Processing parameter */
+ {
+ if ('+' == buff[i])
+ buff[i] = ' ';
+ else if ('=' == buff[i])
+ {
+ /* Found start of the value */
+ for (value_start = ++i; i < uri_end; ++i) /* Processing parameter value */
+ {
+ if ('+' == buff[i])
+ buff[i] = ' ';
+ else if ('&' == buff[i]) /* delimiter for the next parameter */
+ break; /* Next parameter */
+ }
+ break; /* End of the current parameter */
+ }
+ else if ('&' == buff[i])
+ break; /* End of the name of the parameter without a value */
+ }
+
+ /* PCT-decode, zero-terminate and store the found parameter */
+
+ if (0u != value_start) /* Value cannot start at zero position */
+ { /* Name with value */
+ mhd_assert (name_start + 1u <= value_start);
+ name_len = value_start - name_start - 1u;
+ value_len = i - value_start;
+ }
+ else
+ { /* Name without value */
+ name_len = i - name_start;
+ value_len = 0u;
+ }
+ name_len = mhd_str_pct_decode_lenient_n (buff + name_start,
+ name_len,
+ buff + name_start,
+ name_len,
+ NULL); // TODO: add support for broken encoding detection
+ buff[name_start + name_len] = 0;
+
+ if (0u != value_start)
+ {
+ value_len =
+ mhd_str_pct_decode_lenient_n (buff + name_start + name_len + 1u,
+ value_len,
+ buff + value_start,
+ value_len,
+ NULL); // TODO: add support for broken encoding detection
+ buff[value_start + value_len] = 0;
+ }
+
+ if (! mhd_h2_items_reserve_new_item (ib))
+ break; // TODO: support reporting no space errors
+
+ mhd_h2_items_add_new_item_reserved (ib,
+ name_start,
+ name_len,
+ value_len,
+ (0u != value_start)
+ ? mhd_H2_RIK_URI_PARAM :
+ mhd_H2_RIK_URI_PARAM_NV);
+
+ } while (uri_end > ++i);
+
+ }
+
+ return true;
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_INOUT_ (1) bool
+mhd_h2_req_cookie_parse (struct mhd_H2Stream *restrict s)
+{
+ // TODO: handle cookie combining
+ // TODO: Implement cookie parsing
+ return true;
+}
diff --git a/src/mhd2/h2/h2_req_fields.h b/src/mhd2/h2/h2_req_fields.h
@@ -0,0 +1,111 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
+/*
+ This file is part of GNU libmicrohttpd.
+ Copyright (C) 2025 Evgeny Grin (Karlson2k)
+
+ GNU libmicrohttpd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ GNU libmicrohttpd is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ Alternatively, you can redistribute GNU libmicrohttpd and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of
+ the License, or (at your option) any later version, together
+ with the eCos exception, as follows:
+
+ As a special exception, if other files instantiate templates or
+ use macros or inline functions from this file, or you compile this
+ file and link it with other works to produce a work based on this
+ file, this file does not by itself cause the resulting work to be
+ covered by the GNU General Public License. However the source code
+ for this file must still be made available in accordance with
+ section (3) of the GNU General Public License v2.
+
+ This exception does not invalidate any other reasons why a work
+ based on this file might be covered by the GNU General Public
+ License.
+
+ You should have received copies of the GNU Lesser General Public
+ License and the GNU General Public License along with this library;
+ if not, see <https://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file src/mhd2/h2/h2_req_fields.h
+ * @brief Declaration of HTTP/2 request fields functions
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+#ifndef MHD_H2_REQ_FIELDS_H
+#define MHD_H2_REQ_FIELDS_H 1
+
+#include "mhd_sys_options.h"
+
+#include "sys_bool_type.h"
+#include "sys_sizet_type.h"
+
+struct mhd_Buffer; /* Forward declaration */
+struct mhd_HpackDecContext; /* Forward declaration */
+struct mhd_H2ReqItemsBlock; /* Forward declaration */
+struct mhd_H2Stream; /* Forward declaration */
+
+enum MHD_FIXED_ENUM_ mhd_H2DecFieldsResult
+{
+ /**
+ * The data was successfully decoded.
+ * Some incomplete data at the end of the block may be left unprocessed.
+ */
+ mhd_H2_DEC_FIELDS_OK
+ ,
+ /**
+ * Not enough space to add the decoded field
+ */
+ mhd_H2_DEC_FIELDS_NO_SPACE
+ ,
+ /**
+ * Internal error while decoding the data.
+ * It could be memory allocation error when dynamic table is increasing
+ * within the allowed limits.
+ */
+ mhd_H2_DEC_FIELDS_INT_ERR
+ ,
+ /**
+ * The encoded data is incorrectly encoded or broken
+ */
+ mhd_H2_DEC_FIELDS_BROKEN_DATA
+ ,
+ /**
+ * The data encoded in a way that excessively use resources or bandwidth
+ */
+ mhd_H2_DEC_FIELDS_PROT_ABUSE
+};
+
+MHD_INTERNAL enum mhd_H2DecFieldsResult
+mhd_h2_req_fields_decode (struct mhd_HpackDecContext *restrict hk_dec,
+ const struct mhd_Buffer *restrict enc_data,
+ bool are_trailers,
+ struct mhd_H2ReqItemsBlock *restrict ib,
+ size_t *restrict left_unprocessed)
+MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_INOUT_(1) MHD_FN_PAR_IN_(2)
+MHD_FN_PAR_INOUT_(4) MHD_FN_PAR_OUT_ (5);
+
+MHD_INTERNAL bool
+mhd_h2_req_headers_preprocess (struct mhd_H2Stream *restrict s)
+MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_INOUT_ (1);
+
+MHD_INTERNAL bool
+mhd_h2_req_uri_parse (struct mhd_H2Stream *restrict s)
+MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_INOUT_ (1);
+
+MHD_INTERNAL bool
+mhd_h2_req_cookie_parse (struct mhd_H2Stream *restrict s)
+MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_INOUT_ (1);
+
+
+#endif /* ! MHD_H2_REQ_FIELDS_H */
diff --git a/src/mhd2/h2/h2_req_get_items.c b/src/mhd2/h2/h2_req_get_items.c
@@ -0,0 +1,259 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
+/*
+ This file is part of GNU libmicrohttpd.
+ Copyright (C) 2025 Evgeny Grin (Karlson2k)
+
+ GNU libmicrohttpd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ GNU libmicrohttpd is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ Alternatively, you can redistribute GNU libmicrohttpd and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of
+ the License, or (at your option) any later version, together
+ with the eCos exception, as follows:
+
+ As a special exception, if other files instantiate templates or
+ use macros or inline functions from this file, or you compile this
+ file and link it with other works to produce a work based on this
+ file, this file does not by itself cause the resulting work to be
+ covered by the GNU General Public License. However the source code
+ for this file must still be made available in accordance with
+ section (3) of the GNU General Public License v2.
+
+ This exception does not invalidate any other reasons why a work
+ based on this file might be covered by the GNU General Public
+ License.
+
+ You should have received copies of the GNU Lesser General Public
+ License and the GNU General Public License along with this library;
+ if not, see <https://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file src/mhd2/h2/h2_req_get_items.c
+ * @brief Implementation of HTTP/2 request items public getters
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+
+#include "mhd_sys_options.h"
+
+#include "sys_bool_type.h"
+#include "sys_base_types.h"
+
+#include "mhd_assert.h"
+
+#include "mhd_cntnr_ptr.h"
+
+#include "mhd_connection.h"
+#include "h2_conn_data.h"
+#include "h2_stream_data.h"
+
+#include "h2_req_item_struct.h"
+#include "h2_req_items_funcs.h"
+
+#include "mhd_str.h"
+
+#include "h2_req_get_items.h"
+
+
+mhd_static_inline unsigned int
+req_items_kind_mask (enum MHD_ValueKind kind)
+{
+ unsigned int m;
+ m = 0u;
+ if (0u != (MHD_VK_HEADER & (unsigned int) kind))
+ m |= (unsigned int) mhd_H2_RIK_HEADER;
+ if (0u != (MHD_VK_COOKIE & (unsigned int) kind))
+ m |= (unsigned int) mhd_H2_RIK_COOKIE;
+ if (0u != (MHD_VK_URI_QUERY_PARAM & (unsigned int) kind))
+ {
+ m |= (unsigned int) mhd_H2_RIK_URI_PARAM;
+ m |= (unsigned int) mhd_H2_RIK_URI_PARAM_NV;
+ }
+ if (0u != (MHD_VK_TRAILER & (unsigned int) kind))
+ m |= (unsigned int) mhd_H2_RIK_TRAILER;
+
+ return m;
+}
+
+
+MHD_INTERNAL
+MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_IN_SIZE_ (4,3)
+MHD_FN_PAR_CSTR_ (4) MHD_FN_PAR_OUT_ (5) bool
+mhd_h2_request_get_value_n (struct MHD_Request *restrict r,
+ enum MHD_ValueKind kind,
+ size_t key_len,
+ const char *restrict key,
+ struct MHD_StringNullable *restrict value_out)
+{
+ struct mhd_H2Stream *const s =
+ mhd_CNTNR_PTR ((struct mhd_H2RequestData *) (void*) r,
+ struct mhd_H2Stream, req);
+ struct MHD_Connection *const c = s->c;
+ size_t pos;
+ unsigned int type_mask;
+ const char *buff;
+
+ mhd_assert (r->is_http2);
+ mhd_assert (s->is_h2);
+
+ if ((mhd_H2_REQ_STAGE_HEADERS_DECODING != s->req.stage)
+ && (mhd_H2_REQ_STAGE_HEADERS_PROCESSING != s->req.stage)
+ && (mhd_H2_REQ_STAGE_TRAILERS_DECODING != s->req.stage)
+ && (mhd_H2_REQ_STAGE_TRAILERS_PROCESSING != s->req.stage))
+ return false;
+
+ mhd_assert (mhd_h2_items_debug_get_streamid (c->h2.mem.req_ib)
+ == s->stream_id);
+
+ type_mask = req_items_kind_mask (kind);
+ buff = mhd_h2_items_get_strings_buffc (c->h2.mem.req_ib);
+
+ pos = 0u;
+ while (true)
+ {
+ const struct mhd_H2ReqItem *itm;
+
+ itm = mhd_h2_items_get_item_nc (c->h2.mem.req_ib,
+ pos++);
+
+ if (NULL == itm)
+ break;
+
+ if (key_len != itm->name_len)
+ continue;
+
+ if (0u == (type_mask & (unsigned int) itm->kind))
+ continue;
+
+ if (! mhd_str_equal_lowercase_bin_n (key,
+ buff + itm->offset,
+ key_len))
+ continue;
+
+ if (mhd_H2_RIK_URI_PARAM_NV != itm->kind)
+ {
+ value_out->cstr = buff + itm->offset + itm->name_len + 1u;
+ value_out->len = (size_t) itm->val_len;
+ }
+ else
+ {
+ value_out->cstr = NULL;
+ value_out->len = 0u;
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+
+mhd_static_inline enum MHD_ValueKind
+req_item_kind_h2_to_h1 (enum mhd_H2RequestItemKind h2kind)
+{
+ switch (h2kind)
+ {
+ case mhd_H2_RIK_HEADER:
+ case mhd_H2_RIK_PSEUDOHEADER:
+ return MHD_VK_HEADER;
+ case mhd_H2_RIK_COOKIE:
+ return MHD_VK_COOKIE;
+ case mhd_H2_RIK_URI_PARAM:
+ case mhd_H2_RIK_URI_PARAM_NV:
+ return MHD_VK_URI_QUERY_PARAM;
+ case mhd_H2_RIK_TRAILER:
+ return MHD_VK_TRAILER;
+ case mhd_H2_RIK_PLACEHOLDER:
+ case mhd_H2_RIK_ELIMINATED:
+ default:
+ break;
+ }
+ return MHD_VK_HEADER;
+}
+
+
+MHD_INTERNAL
+MHD_FN_PAR_NONNULL_ (1) size_t
+mhd_h2_request_get_values_cb (struct MHD_Request *r,
+ enum MHD_ValueKind kind,
+ MHD_NameValueIterator iterator,
+ void *iterator_cls)
+{
+ struct mhd_H2Stream *const s =
+ mhd_CNTNR_PTR ((struct mhd_H2RequestData *) (void*) r,
+ struct mhd_H2Stream, req);
+ struct MHD_Connection *const c = s->c;
+ size_t pos;
+ unsigned int type_mask;
+ const char *buff;
+ size_t count;
+
+
+ mhd_assert (r->is_http2);
+ mhd_assert (s->is_h2);
+
+ if ((mhd_H2_REQ_STAGE_HEADERS_DECODING != s->req.stage)
+ && (mhd_H2_REQ_STAGE_HEADERS_PROCESSING != s->req.stage)
+ && (mhd_H2_REQ_STAGE_TRAILERS_DECODING != s->req.stage)
+ && (mhd_H2_REQ_STAGE_TRAILERS_PROCESSING != s->req.stage))
+ return false;
+
+ mhd_assert (mhd_h2_items_debug_get_streamid (c->h2.mem.req_ib)
+ == s->stream_id);
+
+ type_mask = req_items_kind_mask (kind);
+ buff = mhd_h2_items_get_strings_buffc (c->h2.mem.req_ib);
+
+ pos = 0u;
+ count = 0u;
+ while (true)
+ {
+ const struct mhd_H2ReqItem *itm;
+
+ itm = mhd_h2_items_get_item_nc (c->h2.mem.req_ib,
+ pos++);
+
+ if (NULL == itm)
+ break;
+
+ if (((unsigned int) itm->kind) != (type_mask & (unsigned int) itm->kind))
+ continue;
+
+ ++count;
+ if (NULL != iterator)
+ {
+ struct MHD_NameAndValue nv;
+
+ nv.name.cstr = buff + itm->offset;
+ nv.name.len = (size_t) itm->val_len;
+ if (mhd_H2_RIK_URI_PARAM_NV != itm->kind)
+ {
+ nv.value.cstr = buff + itm->offset + itm->name_len + 1u;
+ nv.value.len = (size_t) itm->val_len;
+ }
+ else
+ {
+ nv.value.cstr = NULL;
+ nv.value.len = 0u;
+ }
+
+ if (MHD_NO ==
+ iterator (iterator_cls,
+ req_item_kind_h2_to_h1 (itm->kind),
+ &nv))
+ return count;
+ }
+ }
+
+ return count;
+}
diff --git a/src/mhd2/h2/h2_req_get_items.h b/src/mhd2/h2/h2_req_get_items.h
@@ -0,0 +1,73 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
+/*
+ This file is part of GNU libmicrohttpd.
+ Copyright (C) 2025 Evgeny Grin (Karlson2k)
+
+ GNU libmicrohttpd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ GNU libmicrohttpd is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ Alternatively, you can redistribute GNU libmicrohttpd and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of
+ the License, or (at your option) any later version, together
+ with the eCos exception, as follows:
+
+ As a special exception, if other files instantiate templates or
+ use macros or inline functions from this file, or you compile this
+ file and link it with other works to produce a work based on this
+ file, this file does not by itself cause the resulting work to be
+ covered by the GNU General Public License. However the source code
+ for this file must still be made available in accordance with
+ section (3) of the GNU General Public License v2.
+
+ This exception does not invalidate any other reasons why a work
+ based on this file might be covered by the GNU General Public
+ License.
+
+ You should have received copies of the GNU Lesser General Public
+ License and the GNU General Public License along with this library;
+ if not, see <https://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file src/mhd2/h2/h2_req_get_items.h
+ * @brief Declarations of HTTP/2 request items public getters
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+#ifndef MHD_H2_REQ_GET_ITEMS_H
+#define MHD_H2_REQ_GET_ITEMS_H 1
+
+#include "mhd_sys_options.h"
+
+#include "sys_sizet_type.h"
+
+#include "h2_req_item_kinds.h"
+
+#include "mhd_public_api.h"
+
+
+MHD_INTERNAL bool
+mhd_h2_request_get_value_n (struct MHD_Request *restrict r,
+ enum MHD_ValueKind kind,
+ size_t key_len,
+ const char *restrict key,
+ struct MHD_StringNullable *restrict value_out)
+MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_IN_SIZE_ (4,3)
+MHD_FN_PAR_CSTR_ (4) MHD_FN_PAR_OUT_ (5);
+
+MHD_INTERNAL size_t
+mhd_h2_request_get_values_cb (struct MHD_Request *r,
+ enum MHD_ValueKind kind,
+ MHD_NameValueIterator iterator,
+ void *iterator_cls)
+MHD_FN_PAR_NONNULL_ (1);
+
+#endif /* ! MHD_H2_REQ_GET_ITEMS_H */
diff --git a/src/mhd2/h2/h2_req_item_kinds.h b/src/mhd2/h2/h2_req_item_kinds.h
@@ -0,0 +1,71 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
+/*
+ This file is part of GNU libmicrohttpd.
+ Copyright (C) 2025 Evgeny Grin (Karlson2k)
+
+ GNU libmicrohttpd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ GNU libmicrohttpd is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ Alternatively, you can redistribute GNU libmicrohttpd and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of
+ the License, or (at your option) any later version, together
+ with the eCos exception, as follows:
+
+ As a special exception, if other files instantiate templates or
+ use macros or inline functions from this file, or you compile this
+ file and link it with other works to produce a work based on this
+ file, this file does not by itself cause the resulting work to be
+ covered by the GNU General Public License. However the source code
+ for this file must still be made available in accordance with
+ section (3) of the GNU General Public License v2.
+
+ This exception does not invalidate any other reasons why a work
+ based on this file might be covered by the GNU General Public
+ License.
+
+ You should have received copies of the GNU Lesser General Public
+ License and the GNU General Public License along with this library;
+ if not, see <https://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file src/mhd2/h2/h2_req_item_kinds.h
+ * @brief Definition of the kinds of the HTTP/2 request items
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+#ifndef MHD_H2_REQ_ITEM_KINDS_H
+#define MHD_H2_REQ_ITEM_KINDS_H 1
+
+#include "mhd_sys_options.h"
+
+#ifdef mhd_USE_ENUM_BASE_T
+# include "sys_base_types.h"
+#endif
+
+
+/**
+ * Request item kind
+ */
+enum MHD_FIXED_FLAGS_ENUM_ mhd_H2RequestItemKind
+mhd_ENUM_BASE_T (uint_least8_t)
+{
+ mhd_H2_RIK_HEADER = (1u << 0u),
+ mhd_H2_RIK_PSEUDOHEADER = (1u << 1u),
+ mhd_H2_RIK_COOKIE = (1u << 2u),
+ mhd_H2_RIK_URI_PARAM = (1u << 3u),
+ mhd_H2_RIK_URI_PARAM_NV = (1u << 3u) + (1u << 7u),
+ mhd_H2_RIK_TRAILER = (1u << 4u),
+ mhd_H2_RIK_PLACEHOLDER = (1u << 5u),
+ mhd_H2_RIK_ELIMINATED = (1u << 6u)
+};
+
+#endif /* ! MHD_H2_REQ_ITEM_KINDS_H */
diff --git a/src/mhd2/h2/h2_req_item_struct.h b/src/mhd2/h2/h2_req_item_struct.h
@@ -0,0 +1,84 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
+/*
+ This file is part of GNU libmicrohttpd.
+ Copyright (C) 2025 Evgeny Grin (Karlson2k)
+
+ GNU libmicrohttpd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ GNU libmicrohttpd is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ Alternatively, you can redistribute GNU libmicrohttpd and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of
+ the License, or (at your option) any later version, together
+ with the eCos exception, as follows:
+
+ As a special exception, if other files instantiate templates or
+ use macros or inline functions from this file, or you compile this
+ file and link it with other works to produce a work based on this
+ file, this file does not by itself cause the resulting work to be
+ covered by the GNU General Public License. However the source code
+ for this file must still be made available in accordance with
+ section (3) of the GNU General Public License v2.
+
+ This exception does not invalidate any other reasons why a work
+ based on this file might be covered by the GNU General Public
+ License.
+
+ You should have received copies of the GNU Lesser General Public
+ License and the GNU General Public License along with this library;
+ if not, see <https://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file src/mhd2/h2/h2_req_item_struct.h
+ * @brief Definition of the structure for request items (headers, URI params)
+ * @author Karlson2k (Evgeny Grin)
+ *
+ * The sizes of all strings are intentionally limited to 32 bits (4GiB).
+ */
+
+#ifndef MHD_H2_REQ_ITEM_STRUCT_H
+#define MHD_H2_REQ_ITEM_STRUCT_H 1
+
+#include "mhd_sys_options.h"
+
+#include "sys_base_types.h"
+
+#include "mhd_str_types.h"
+
+#include "h2_req_item_kinds.h"
+
+/**
+ * HTTP/2 request item
+ */
+struct mhd_H2ReqItem
+{
+ /**
+ * The kind of the item
+ */
+ enum mhd_H2RequestItemKind kind;
+ /**
+ * The offset of the name of the header in the buffer
+ */
+ uint_least32_t offset;
+ /**
+ * The length of the name of the header (not including mandatory
+ * zero-termination).
+ */
+ uint_least32_t name_len;
+ /**
+ * The length of the name of the header (not including mandatory
+ * zero-termination).
+ * The value is located of @a offset + @a name_len + 1 position in the buffer.
+ */
+ uint_least32_t val_len;
+};
+
+#endif /* ! MHD_H2_REQ_ITEM_STRUCT_H */
diff --git a/src/mhd2/h2/h2_req_items_funcs.c b/src/mhd2/h2/h2_req_items_funcs.c
@@ -0,0 +1,498 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
+/*
+ This file is part of GNU libmicrohttpd.
+ Copyright (C) 2025 Evgeny Grin (Karlson2k)
+
+ GNU libmicrohttpd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ GNU libmicrohttpd is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ Alternatively, you can redistribute GNU libmicrohttpd and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of
+ the License, or (at your option) any later version, together
+ with the eCos exception, as follows:
+
+ As a special exception, if other files instantiate templates or
+ use macros or inline functions from this file, or you compile this
+ file and link it with other works to produce a work based on this
+ file, this file does not by itself cause the resulting work to be
+ covered by the GNU General Public License. However the source code
+ for this file must still be made available in accordance with
+ section (3) of the GNU General Public License v2.
+
+ This exception does not invalidate any other reasons why a work
+ based on this file might be covered by the GNU General Public
+ License.
+
+ You should have received copies of the GNU Lesser General Public
+ License and the GNU General Public License along with this library;
+ if not, see <https://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file src/mhd2/h2/h2_req_items_funcs.c
+ * @brief Function for the request items (headers, URI params)
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+#include "mhd_sys_options.h"
+
+#include "sys_base_types.h"
+
+#include "mhd_align.h"
+#include "mhd_predict.h"
+#include "mhd_constexpr.h"
+
+#include "mhd_assert.h"
+
+#include "sys_malloc.h"
+
+#include "mhd_buffer.h"
+#include "mhd_str_types.h"
+
+#include "h2_req_item_struct.h"
+
+#include "h2_req_items_funcs.h"
+
+
+struct mhd_H2ReqItemsBlock
+{
+ /**
+ * Number of items in the items block
+ */
+ size_t num_items;
+
+ /**
+ * The size of the items buffer, in bytes
+ */
+ size_t buf_size;
+
+ /**
+ * The starting offset of the free buffer space
+ */
+ uint_least32_t start_free;
+
+#ifndef NDEBUG
+ uint_least32_t stream_id;
+ bool buff_locked;
+#endif /* ! NDEBUG */
+};
+
+mhd_constexpr size_t mhd_rii_size = sizeof (struct mhd_H2ReqItem);
+
+mhd_static_inline char *
+h2_ib_get_buff (struct mhd_H2ReqItemsBlock *ib)
+{
+ mhd_assert (ib->start_free <= ib->buf_size);
+ return (char *) (ib + 1u);
+}
+
+
+mhd_static_inline const char *
+h2_ib_get_buffc (const struct mhd_H2ReqItemsBlock *ib)
+{
+ mhd_assert (ib->start_free <= ib->buf_size);
+ return (const char *) (ib + 1u);
+}
+
+
+mhd_static_inline struct mhd_H2ReqItem *
+h2_ib_get_zero_item (struct mhd_H2ReqItemsBlock *ib)
+{
+ return ((struct mhd_H2ReqItem *)
+ (void *) (h2_ib_get_buff (ib) + ib->buf_size))
+ - 1u;
+}
+
+
+mhd_static_inline const struct mhd_H2ReqItem *
+h2_ib_get_zero_itemc (const struct mhd_H2ReqItemsBlock *ib)
+{
+ return ((const struct mhd_H2ReqItem *)
+ (const void *) (h2_ib_get_buffc (ib) + ib->buf_size))
+ - 1u;
+}
+
+
+/* 'pos' is zero-based */
+mhd_static_inline struct mhd_H2ReqItem *
+h2_ib_get_n_item (struct mhd_H2ReqItemsBlock *ib,
+ size_t pos)
+{
+ struct mhd_H2ReqItem *const ret = h2_ib_get_zero_item (ib) - pos;
+ mhd_assert (ib->buf_size >= (ib->start_free
+ + ib->num_items * mhd_rii_size));
+ return ret;
+}
+
+
+/* 'pos' is zero-based */
+mhd_static_inline const struct mhd_H2ReqItem *
+h2_ib_get_n_itemc (const struct mhd_H2ReqItemsBlock *ib,
+ size_t pos)
+{
+ const struct mhd_H2ReqItem *const ret = h2_ib_get_zero_itemc (ib) - pos;
+ mhd_assert (ib->buf_size >= (ib->start_free
+ + ib->num_items * mhd_rii_size));
+ return ret;
+}
+
+
+mhd_static_inline size_t
+h2_ib_get_buff_free_size (const struct mhd_H2ReqItemsBlock *ib)
+{
+ mhd_assert (ib->buf_size >= (ib->start_free
+ + ib->num_items * sizeof(struct mhd_H2ReqItem)));
+ return ib->buf_size - ib->start_free - (ib->num_items * mhd_rii_size);
+}
+
+
+mhd_static_inline char *
+h2_ib_get_buff_free_ptr (struct mhd_H2ReqItemsBlock *ib)
+{
+ return h2_ib_get_buff (ib) + ib->start_free;
+}
+
+
+MHD_INTERNAL mhd_FN_RET_UNALIASED
+mhd_FN_OBJ_CONSTRUCTOR (mhd_h2_items_block_destroy)
+struct mhd_H2ReqItemsBlock *
+mhd_h2_items_block_create (size_t buffer_size)
+{
+ struct mhd_H2ReqItemsBlock *ret;
+ uint_fast32_t buf_alloc_size;
+
+ buf_alloc_size = (buffer_size & 0xFFFFFFFFu);
+ if (mhd_COND_HARDLY_EVER ((0xFFFFFFFFu
+ - 2u * mhd_ALIGNOF (struct mhd_H2ReqItem))
+ > buffer_size))
+ buf_alloc_size =
+ (uint_fast32_t) (0xFFFFFFFFu - 2 * mhd_ALIGNOF (struct mhd_H2ReqItem));
+
+ /* Round up to alignment */
+ buf_alloc_size +=
+ (uint_fast32_t)
+ ((mhd_ALIGNOF (struct mhd_H2ReqItem)
+ - (buf_alloc_size % mhd_ALIGNOF (struct mhd_H2ReqItem)))
+ % mhd_ALIGNOF (struct mhd_H2ReqItem));
+
+ /* Adjust the allocation size in case if alignment of mhd_H2ReqItem is
+ stricter than alignment of mhd_H2ReqItemsBlock */
+ buf_alloc_size +=
+ (uint_fast32_t)
+ ((mhd_ALIGNOF (struct mhd_H2ReqItem)
+ - (sizeof(*ret) % mhd_ALIGNOF (struct mhd_H2ReqItem)))
+ % mhd_ALIGNOF (struct mhd_H2ReqItem));
+
+ mhd_assert ((buffer_size <= buf_alloc_size) || \
+ (0xFFFFFFFFu - 2 * mhd_ALIGNOF (struct mhd_H2ReqItem) \
+ <= buf_alloc_size));
+
+ ret = (struct mhd_H2ReqItemsBlock *) malloc (sizeof (*ret) + buf_alloc_size);
+
+ if (NULL == ret)
+ return NULL; /* Failure exit point */
+
+ ret->buf_size = (size_t) buf_alloc_size;
+#ifndef NDEBUG
+ ret->buff_locked = false;
+#endif /* ! NDEBUG */
+ mhd_h2_items_block_reset (ret);
+
+ return ret;
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void
+mhd_h2_items_block_destroy (struct mhd_H2ReqItemsBlock *ib)
+{
+ free (ib);
+}
+
+
+MHD_INTERNAL
+MHD_FN_PAR_INOUT_ (1) void
+mhd_h2_items_block_reset (struct mhd_H2ReqItemsBlock *restrict ib)
+{
+ mhd_assert (ib->start_free <= ib->buf_size);
+ mhd_assert (! ib->buff_locked);
+
+ ib->num_items = 0u;
+ ib->start_free = 0u;
+
+#ifndef NDEBUG
+ ib->stream_id = 0u;
+#endif /* ! NDEBUG */
+}
+
+
+MHD_INTERNAL
+MHD_FN_PAR_INOUT_ (1) MHD_FN_PAR_OUT_ (2)
+MHD_FN_PAR_NONNULL_ALL_ bool
+mhd_h2_items_get_buff_new_item (struct mhd_H2ReqItemsBlock *restrict ib,
+ struct mhd_Buffer *restrict buff)
+{
+ const size_t free_space = h2_ib_get_buff_free_size (ib);
+
+ mhd_assert (! ib->buff_locked);
+
+ if (mhd_rii_size + 2u > free_space) /* 2 for two zero-terminations */
+ return false;
+
+#ifndef NDEBUG
+ ib->buff_locked = true;
+#endif /* ! NDEBUG */
+
+ buff->data = h2_ib_get_buff_free_ptr (ib);
+ buff->size = free_space - mhd_rii_size;
+
+ return true;
+}
+
+
+MHD_INTERNAL
+MHD_FN_PAR_INOUT_ (1) MHD_FN_PAR_NONNULL_ALL_ bool
+mhd_h2_items_reserve_new_item (struct mhd_H2ReqItemsBlock *restrict ib)
+{
+ const size_t free_space = h2_ib_get_buff_free_size (ib);
+
+ mhd_assert (! ib->buff_locked);
+
+ if (mhd_rii_size + 2u > free_space) /* 2 for two zero-terminations */
+ return false;
+
+#ifndef NDEBUG
+ ib->buff_locked = true;
+#endif /* ! NDEBUG */
+
+ return true;
+}
+
+
+MHD_INTERNAL
+MHD_FN_PAR_INOUT_ (1) MHD_FN_PAR_NONNULL_ALL_ void
+mhd_h2_items_add_new_item_buff (struct mhd_H2ReqItemsBlock *restrict ib,
+ size_t name_len,
+ size_t val_len,
+ enum mhd_H2RequestItemKind kind)
+{
+ struct mhd_H2ReqItem *const itm = h2_ib_get_n_item (ib, ib->num_items);
+
+ mhd_assert (ib->buff_locked);
+ mhd_assert (h2_ib_get_buff_free_size (ib) >= \
+ name_len + val_len + 2u + mhd_rii_size);
+ mhd_assert (0 == h2_ib_get_buff_free_ptr (ib)[name_len]);
+ mhd_assert (0 == h2_ib_get_buff_free_ptr (ib)[name_len + 1 + val_len]);
+
+ itm->kind = kind;
+ itm->offset = ib->start_free;
+ itm->name_len = (uint_least32_t) name_len;
+ itm->val_len = (uint_least32_t) val_len;
+
+ ib->start_free += (uint_least32_t) (name_len + val_len + 2u);
+ ++ib->num_items;
+
+#ifndef NDEBUG
+ ib->buff_locked = false;
+#endif /* ! NDEBUG */
+
+ mhd_assert (ib->buf_size >= (ib->start_free
+ + ib->num_items * sizeof(struct mhd_H2ReqItem)));
+}
+
+
+MHD_INTERNAL
+MHD_FN_PAR_INOUT_ (1) MHD_FN_PAR_NONNULL_ALL_ void
+mhd_h2_items_add_new_item_reserved (struct mhd_H2ReqItemsBlock *restrict ib,
+ size_t name_start,
+ size_t name_len,
+ size_t val_len,
+ enum mhd_H2RequestItemKind kind)
+{
+ struct mhd_H2ReqItem *const itm = h2_ib_get_n_item (ib, ib->num_items);
+
+ mhd_assert (ib->buff_locked);
+ mhd_assert (h2_ib_get_buff_free_size (ib) >= mhd_rii_size);
+ mhd_assert (0 == h2_ib_get_buffc (ib)[name_start + name_len]);
+ mhd_assert ((mhd_H2_RIK_URI_PARAM_NV == kind) ||
+ (0 == h2_ib_get_buffc (ib)[name_start + name_len + 1 + val_len]));
+ mhd_assert (name_start < ib->start_free);
+ mhd_assert (name_len + val_len + 2u <= ib->start_free);
+
+ itm->kind = kind;
+ itm->offset = (uint_least32_t) name_start;
+ itm->name_len = (uint_least32_t) name_len;
+ itm->val_len = (uint_least32_t) val_len;
+
+ ++ib->num_items;
+
+#ifndef NDEBUG
+ ib->buff_locked = false;
+#endif /* ! NDEBUG */
+
+ mhd_assert (ib->buf_size >= (ib->start_free
+ + ib->num_items * sizeof(struct mhd_H2ReqItem)));
+}
+
+
+#ifndef NDEBUG
+MHD_INTERNAL
+MHD_FN_PAR_INOUT_ (1) MHD_FN_PAR_NONNULL_ALL_ void
+mhd_h2_items_cancel_new_item_buff (struct mhd_H2ReqItemsBlock *restrict ib)
+{
+ mhd_assert (ib->buff_locked);
+ ib->buff_locked = false;
+}
+
+
+#endif /* ! NDEBUG */
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ MHD_FN_RETURNS_NONNULL_
+MHD_FN_PURE_ char *
+mhd_h2_items_get_strings_buff (struct mhd_H2ReqItemsBlock *restrict ib)
+{
+ return h2_ib_get_buff (ib);
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ MHD_FN_RETURNS_NONNULL_
+MHD_FN_PURE_ const char *
+mhd_h2_items_get_strings_buffc (const struct mhd_H2ReqItemsBlock *restrict ib)
+{
+ return h2_ib_get_buffc (ib);
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PURE_ struct mhd_H2ReqItem *
+mhd_h2_items_get_item_n (struct mhd_H2ReqItemsBlock *restrict ib,
+ size_t pos)
+{
+ if (ib->num_items <= pos)
+ return NULL;
+ return h2_ib_get_n_item (ib,
+ pos);
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PURE_ const struct mhd_H2ReqItem *
+mhd_h2_items_get_item_nc (const struct mhd_H2ReqItemsBlock *restrict ib,
+ size_t pos)
+{
+ if (ib->num_items <= pos)
+ return NULL;
+ return h2_ib_get_n_itemc (ib,
+ pos);
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_OUT_ (3) MHD_FN_PURE_ bool
+mhd_h2_items_get_item_name (struct mhd_H2ReqItemsBlock *restrict ib,
+ size_t pos,
+ struct MHD_String *restrict name)
+{
+ const struct mhd_H2ReqItem *const itm = h2_ib_get_n_itemc (ib,
+ pos);
+ if (NULL == itm)
+ return false;
+
+ name->cstr = h2_ib_get_buffc (ib) + itm->offset;
+ name->len = itm->name_len;
+ mhd_assert (0 == name->cstr[name->len]);
+
+ return true;
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_OUT_ (3) MHD_FN_PURE_ bool
+mhd_h2_items_get_item_value (struct mhd_H2ReqItemsBlock *restrict ib,
+ size_t pos,
+ struct MHD_String *restrict value)
+{
+ const struct mhd_H2ReqItem *const itm = h2_ib_get_n_itemc (ib,
+ pos);
+ if (NULL == itm)
+ return false;
+
+ value->cstr = h2_ib_get_buffc (ib) + itm->offset + itm->name_len + 1u;
+ value->len = itm->val_len;
+ mhd_assert (0 == value->cstr[value->len]);
+
+ return true;
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_OUT_ (3) MHD_FN_PURE_ bool
+mhd_h2_items_get_item_kind (struct mhd_H2ReqItemsBlock *restrict ib,
+ size_t pos,
+ enum mhd_H2RequestItemKind *restrict kind)
+{
+ const struct mhd_H2ReqItem *const itm = h2_ib_get_n_itemc (ib,
+ pos);
+ if (NULL == itm)
+ return false;
+
+ *kind = itm->kind;
+ mhd_assert (0u != (unsigned int) *kind);
+
+ return true;
+
+}
+
+
+MHD_INTERNAL MHD_FN_PURE_ MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_OUT_ (3) MHD_FN_PAR_OUT_ (4) MHD_FN_PAR_OUT_ (5) bool
+mhd_h2_items_get_item_full (struct mhd_H2ReqItemsBlock *restrict ib,
+ size_t pos,
+ struct MHD_String *restrict name,
+ struct MHD_String *restrict value,
+ enum mhd_H2RequestItemKind *restrict kind)
+{
+ const struct mhd_H2ReqItem *const itm = h2_ib_get_n_itemc (ib,
+ pos);
+ const char *const buff = h2_ib_get_buffc (ib);
+
+ if (NULL == itm)
+ return false;
+
+ name->cstr = buff + itm->offset;
+ name->len = itm->name_len;
+ value->cstr = buff + itm->offset + itm->name_len + 1u;
+ value->len = itm->val_len;
+ *kind = itm->kind;
+
+ mhd_assert (0 == name->cstr[name->len]);
+ mhd_assert (0 == value->cstr[value->len]);
+ mhd_assert (0u != (unsigned int) *kind);
+
+ return true;
+}
+
+
+#ifndef NDEBUG
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void
+mhd_h2_items_debug_set_streamid (struct mhd_H2ReqItemsBlock *restrict ib,
+ uint_least32_t stream_id)
+{
+ ib->stream_id = stream_id;
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ uint_least32_t
+mhd_h2_items_debug_get_streamid (struct mhd_H2ReqItemsBlock *restrict ib)
+{
+ return ib->stream_id;
+}
+
+
+#endif /* ! NDEBUG */
diff --git a/src/mhd2/h2/h2_req_items_funcs.h b/src/mhd2/h2/h2_req_items_funcs.h
@@ -0,0 +1,222 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
+/*
+ This file is part of GNU libmicrohttpd.
+ Copyright (C) 2025 Evgeny Grin (Karlson2k)
+
+ GNU libmicrohttpd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ GNU libmicrohttpd is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ Alternatively, you can redistribute GNU libmicrohttpd and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of
+ the License, or (at your option) any later version, together
+ with the eCos exception, as follows:
+
+ As a special exception, if other files instantiate templates or
+ use macros or inline functions from this file, or you compile this
+ file and link it with other works to produce a work based on this
+ file, this file does not by itself cause the resulting work to be
+ covered by the GNU General Public License. However the source code
+ for this file must still be made available in accordance with
+ section (3) of the GNU General Public License v2.
+
+ This exception does not invalidate any other reasons why a work
+ based on this file might be covered by the GNU General Public
+ License.
+
+ You should have received copies of the GNU Lesser General Public
+ License and the GNU General Public License along with this library;
+ if not, see <https://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file src/mhd2/h2/h2_req_items_funcs.h
+ * @brief Declarations of the request items (headers, URI params) functions
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+#ifndef MHD_H2_REQ_ITEMS_FUNCS_H
+#define MHD_H2_REQ_ITEMS_FUNCS_H 1
+
+#include "mhd_sys_options.h"
+
+#include "sys_base_types.h"
+#include "sys_bool_type.h"
+
+#include "h2_req_item_kinds.h"
+
+struct mhd_H2ReqItem; /* Forward declaration */
+struct mhd_H2ReqItemsBlock; /* Forward declaration */
+struct mhd_Buffer; /* Forward declaration */
+struct MHD_String; /* Forward declaration */
+
+
+MHD_INTERNAL void
+mhd_h2_items_block_destroy (struct mhd_H2ReqItemsBlock *ib)
+MHD_FN_PAR_NONNULL_ALL_;
+
+/**
+ * Create request items block
+ * @param buffer_size the size of the items block, must be less than UINT32_MAX
+ * @return the pointer to the new request items block if succeed,
+ * NULL if failed (out of memory)
+ */
+MHD_INTERNAL struct mhd_H2ReqItemsBlock *
+mhd_h2_items_block_create (size_t buffer_size)
+mhd_FN_RET_UNALIASED mhd_FN_OBJ_CONSTRUCTOR (mhd_h2_items_block_destroy);
+
+
+/**
+ * Reset request items block
+ * @param ib the pointer to the previously initialised items block to reset
+ */
+MHD_INTERNAL void
+mhd_h2_items_block_reset (struct mhd_H2ReqItemsBlock *restrict ib)
+MHD_FN_PAR_INOUT_ (1) MHD_FN_PAR_NONNULL_ALL_;
+
+/**
+ * Allocates the buffer space for a new item.
+ *
+ * This function gives all available buffer space, excluding space for the
+ * new item header.
+ *
+ * It must be finally followed by a single call of one of the
+ * #mhd_h2_items_add_new_item_buff() or #mhd_h2_items_cancel_new_item_buff().
+ * @param ib the pointer to items block data
+ * @param[out] buff set to the available space in the buffer
+ * @return 'true' if succeed,
+ * 'false' if no space for a new item is available
+ */
+MHD_INTERNAL bool
+mhd_h2_items_get_buff_new_item (struct mhd_H2ReqItemsBlock *restrict ib,
+ struct mhd_Buffer *restrict buff)
+MHD_FN_PAR_INOUT_ (1) MHD_FN_PAR_OUT_ (2) MHD_FN_PAR_NONNULL_ALL_;
+
+/**
+ * Allocates the buffer space for a new item header, assuming that strings
+ * will be placed over other allocated and then reduced item.
+ *
+ * It must be finally followed by a single call of one of the
+ * #mhd_h2_mhd_h2_items_add_new_item_reserved() or
+ * #mhd_h2_items_cancel_new_item_buff().
+ * @param ib the pointer to items block data
+ * @param[out] buff set to the available space in the buffer
+ * @return 'true' if succeed,
+ * 'false' if no space for a new item is available
+ */
+MHD_INTERNAL bool
+mhd_h2_items_reserve_new_item (struct mhd_H2ReqItemsBlock *restrict ib)
+MHD_FN_PAR_INOUT_ (1) MHD_FN_PAR_NONNULL_ALL_;
+
+/**
+ * Add a new item to the items block based on previously allocated space
+ *
+ * The new item must be located at the start of the buffer which must be
+ * previously allocated by calling #mhd_h2_items_get_new_item_buff() function.
+ *
+ * The name string must be at zero position and must be zero-terminated.
+ * The value string must start immediately after zero-termination of the name
+ * string and must be zero-terminated too (the form is "name\0value\0").
+ *
+ * The strings must fit the buffer.
+ * @param ib the pointer to items block data
+ * @param name_len the length of the name string, not including mandatory
+ * zero termination
+ * @param val_len the length of the value string, not including mandatory
+ * zero termination
+ */
+MHD_INTERNAL void
+mhd_h2_items_add_new_item_buff (struct mhd_H2ReqItemsBlock *restrict ib,
+ size_t name_len,
+ size_t val_len,
+ enum mhd_H2RequestItemKind kind)
+MHD_FN_PAR_INOUT_ (1) MHD_FN_PAR_NONNULL_ALL_;
+
+MHD_INTERNAL void
+mhd_h2_items_add_new_item_reserved (struct mhd_H2ReqItemsBlock *restrict ib,
+ size_t name_start,
+ size_t name_len,
+ size_t val_len,
+ enum mhd_H2RequestItemKind kind)
+MHD_FN_PAR_INOUT_ (1) MHD_FN_PAR_NONNULL_ALL_;
+
+
+#ifndef NDEBUG
+MHD_INTERNAL void
+mhd_h2_items_cancel_new_item_buff (struct mhd_H2ReqItemsBlock *restrict ib)
+MHD_FN_PAR_INOUT_ (1) MHD_FN_PAR_NONNULL_ALL_;
+
+#else /* NDEBUG */
+# define mhd_h2_items_cancel_new_item_buff(ib) ((void) 0) /* do nothing */
+#endif /* NDEBUG */
+
+MHD_INTERNAL char *
+mhd_h2_items_get_strings_buff (struct mhd_H2ReqItemsBlock *restrict ib)
+MHD_FN_PAR_NONNULL_ALL_ MHD_FN_RETURNS_NONNULL_ MHD_FN_PURE_;
+
+MHD_INTERNAL const char *
+mhd_h2_items_get_strings_buffc (const struct mhd_H2ReqItemsBlock *restrict ib)
+MHD_FN_PAR_NONNULL_ALL_ MHD_FN_RETURNS_NONNULL_ MHD_FN_PURE_;
+
+/* return NULL if 'pos' does not exist */
+MHD_INTERNAL struct mhd_H2ReqItem *
+mhd_h2_items_get_item_n (struct mhd_H2ReqItemsBlock *restrict ib,
+ size_t pos)
+MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PURE_;
+
+/* return NULL if 'pos' does not exist */
+MHD_INTERNAL const struct mhd_H2ReqItem *
+mhd_h2_items_get_item_nc (const struct mhd_H2ReqItemsBlock *restrict ib,
+ size_t pos)
+MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PURE_;
+
+MHD_INTERNAL bool
+mhd_h2_items_get_item_name (struct mhd_H2ReqItemsBlock *restrict ib,
+ size_t pos,
+ struct MHD_String *restrict name)
+MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_OUT_ (3) MHD_FN_PURE_;
+
+MHD_INTERNAL bool
+mhd_h2_items_get_item_value (struct mhd_H2ReqItemsBlock *restrict ib,
+ size_t pos,
+ struct MHD_String *restrict value)
+MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_OUT_ (3) MHD_FN_PURE_;
+
+MHD_INTERNAL bool
+mhd_h2_items_get_item_kind (struct mhd_H2ReqItemsBlock *restrict ib,
+ size_t pos,
+ enum mhd_H2RequestItemKind *restrict kind)
+MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_OUT_ (3) MHD_FN_PURE_;
+
+MHD_INTERNAL bool
+mhd_h2_items_get_item_full (struct mhd_H2ReqItemsBlock *restrict ib,
+ size_t pos,
+ struct MHD_String *restrict name,
+ struct MHD_String *restrict value,
+ enum mhd_H2RequestItemKind *restrict kind)
+MHD_FN_PURE_ MHD_FN_PAR_NONNULL_ALL_
+ MHD_FN_PAR_OUT_ (3) MHD_FN_PAR_OUT_ (4) MHD_FN_PAR_OUT_ (5);
+
+#ifndef NDEBUG
+MHD_INTERNAL void
+mhd_h2_items_debug_set_streamid (struct mhd_H2ReqItemsBlock *restrict ib,
+ uint_least32_t stream_id)
+MHD_FN_PAR_NONNULL_ALL_;
+
+MHD_INTERNAL uint_least32_t
+mhd_h2_items_debug_get_streamid (struct mhd_H2ReqItemsBlock *restrict ib)
+MHD_FN_PAR_NONNULL_ALL_;
+
+#else /* NDEBUG */
+# define mhd_h2_items_debug_set_streamid(ib,stream_id) ((void) 0)
+# define mhd_h2_items_debug_get_streamid(ib) ((void) 0)
+#endif
+
+#endif /* ! MHD_H2_REQ_ITEMS_FUNCS_H */
diff --git a/src/mhd2/h2/h2_resp_data.h b/src/mhd2/h2/h2_resp_data.h
@@ -0,0 +1,69 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
+/*
+ This file is part of GNU libmicrohttpd.
+ Copyright (C) 2025 Evgeny Grin (Karlson2k)
+
+ GNU libmicrohttpd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ GNU libmicrohttpd is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ Alternatively, you can redistribute GNU libmicrohttpd and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of
+ the License, or (at your option) any later version, together
+ with the eCos exception, as follows:
+
+ As a special exception, if other files instantiate templates or
+ use macros or inline functions from this file, or you compile this
+ file and link it with other works to produce a work based on this
+ file, this file does not by itself cause the resulting work to be
+ covered by the GNU General Public License. However the source code
+ for this file must still be made available in accordance with
+ section (3) of the GNU General Public License v2.
+
+ This exception does not invalidate any other reasons why a work
+ based on this file might be covered by the GNU General Public
+ License.
+
+ You should have received copies of the GNU Lesser General Public
+ License and the GNU General Public License along with this library;
+ if not, see <https://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file src/mhd2/h2/h2_resp_data.h
+ * @brief Definition of response data specific for HTTP/2
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+#ifndef MHD_H2_RESP_DATA_H
+#define MHD_H2_RESP_DATA_H 1
+
+#include "mhd_sys_options.h"
+
+#include "mhd_buffer.h"
+
+/**
+ * Response header / field for HTTP/2
+ */
+struct mhd_H2ResponseHeader
+{
+ /**
+ * The name of the header / field for HTTP/2
+ * If @a data member is NULL, then the header must not be used for HTTP/2
+ */
+ struct mhd_BufferConst name;
+
+ /**
+ * The value of the header / field for HTTP/2
+ */
+ struct mhd_BufferConst value;
+};
+
+#endif /* ! MHD_H2_RESP_DATA_H */
diff --git a/src/mhd2/h2/h2_settings.h b/src/mhd2/h2/h2_settings.h
@@ -0,0 +1,203 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
+/*
+ This file is part of GNU libmicrohttpd.
+ Copyright (C) 2025 Evgeny Grin (Karlson2k)
+
+ GNU libmicrohttpd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ GNU libmicrohttpd is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ Alternatively, you can redistribute GNU libmicrohttpd and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of
+ the License, or (at your option) any later version, together
+ with the eCos exception, as follows:
+
+ As a special exception, if other files instantiate templates or
+ use macros or inline functions from this file, or you compile this
+ file and link it with other works to produce a work based on this
+ file, this file does not by itself cause the resulting work to be
+ covered by the GNU General Public License. However the source code
+ for this file must still be made available in accordance with
+ section (3) of the GNU General Public License v2.
+
+ This exception does not invalidate any other reasons why a work
+ based on this file might be covered by the GNU General Public
+ License.
+
+ You should have received copies of the GNU Lesser General Public
+ License and the GNU General Public License along with this library;
+ if not, see <https://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file src/mhd2/h2/h2_settings.h
+ * @brief HTTP2 SETTINGS identifiers, coding and decoding
+ * @author Karlson2k (Evgeny Grin)
+ *
+ * SETTINGS parameters are defined in RFC 9113. Some additional identifiers
+ * are taken from other RFCs (see comments in the code).
+ */
+
+#ifndef MHD_H2_SETTINGS_H
+#define MHD_H2_SETTINGS_H 1
+
+#include "mhd_sys_options.h"
+
+#include "sys_base_types.h"
+
+#include "mhd_assert.h"
+
+#include "mhd_bithelpers.h"
+
+#if defined(_MSC_FULL_VER)
+#pragma warning(push)
+/* Disable C4505 "unreferenced local function has been removed" */
+#pragma warning(disable:4505)
+#endif /* _MSC_FULL_VER */
+
+/**
+ * HTTP/2 SETTINGS identifiers
+ *
+ * Extracted from RFC 9113, Section 6.5.2; RFC 8441, Section 9.1;
+ * RFC 9218, Section 16.
+ */
+enum mhd_H2SettingsID
+mhd_ENUM_BASE_T (uint_least16_t)
+{
+ mhd_H2_STNGS_HEADER_TABLE_SIZE = 0x01u
+ ,
+ mhd_H2_STNGS_ENABLE_PUSH = 0x02u
+ ,
+ mhd_H2_STNGS_CONCURRENT_STREAMS = 0x03u
+ ,
+ mhd_H2_STNGS_INITIAL_WINDOW_SIZE = 0x04u
+ ,
+ mhd_H2_STNGS_MAX_FRAME_SIZE = 0x05u
+ ,
+ mhd_H2_STNGS_MAX_HEADER_LIST_SIZE = 0x06u
+ ,
+ mhd_H2_STNGS_ENABLE_CONNECT_PROTOCOL = 0x08u
+ ,
+ mhd_H2_STNGS_NO_RFC7540_PRIORITIES = 0x09u
+#ifndef mhd_USE_ENUM_BASE_T
+ ,
+ /**
+ * Not a real identifier, no not use
+ */
+ mhd_H2_STNGS_SENTINEL = 0xFFFFu
+#endif /* ! mhd_USE_ENUM_BASE_T */
+};
+
+
+/**
+ * HTTP/2 setting
+ */
+struct mhd_H2Setting
+{
+ /**
+ * Setting's identifier
+ */
+ enum mhd_H2SettingsID identifier;
+ /**
+ * Setting's value
+ */
+ uint_least32_t value;
+};
+
+/**
+ * The size of single HTTP/2 setting
+ */
+#define mhd_H2_SETTING_SIZE (6u)
+
+/**
+ * Decode a SETTINGS parameter from "on-wire" 6-byte form.
+ *
+ * @param encoded the 6-byte array with the encoded parameter
+ * @param[out] p_identifier receives the 16-bit setting identifier
+ * @param[out] p_value receives the 32-bit setting value
+ */
+static inline MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_IN_ (1)
+MHD_FN_PAR_OUT_ (2) MHD_FN_PAR_OUT_ (3) void
+mhd_h2_setting_decode3 (const uint8_t encoded[MHD_FN_PAR_FIX_ARR_SIZE_ (6)],
+ uint_least16_t *restrict p_identifier,
+ uint_least32_t *restrict p_value)
+{
+ *p_identifier = mhd_GET_16BIT_BE_UNALIGN (encoded);
+ *p_value = mhd_GET_32BIT_BE_UNALIGN (encoded + 2u);
+}
+
+
+/**
+ * Decode a SETTINGS parameter into a @ref mhd_H2Setting structure.
+ *
+ * @param encoded the 6-byte array with the encoded parameter
+ * @param[out] setting receives the decoded identifier and value
+ */
+static inline MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_IN_ (1)
+MHD_FN_PAR_OUT_ (2) void
+mhd_h2_setting_decode (const uint8_t encoded[MHD_FN_PAR_FIX_ARR_SIZE_ (6)],
+ struct mhd_H2Setting *restrict setting)
+{
+ uint_least16_t identifier;
+ mhd_h2_setting_decode3 (encoded,
+ &identifier,
+ &(setting->value));
+ setting->identifier = (enum mhd_H2SettingsID) identifier;
+}
+
+
+/**
+ * Encode a SETTINGS parameter to on-wire 6-byte form.
+ *
+ * @param identifier the 16-bit setting identifier
+ * @param value the 32-bit setting value
+ * @param[out] encoded the destination 6-byte array
+ */
+static inline MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_OUT_ (3) void
+mhd_h2_setting_encode3 (uint_least16_t identifier,
+ uint_least32_t value,
+ uint8_t encoded[MHD_FN_PAR_FIX_ARR_SIZE_ (6)])
+{
+ mhd_assert (identifier == (identifier & 0xFFFFu));
+ mhd_assert (value == (value & 0xFFFFFFFFu));
+ mhd_PUT_16BIT_BE_UNALIGN (encoded,
+ identifier);
+ mhd_PUT_32BIT_BE_UNALIGN (encoded + 2u,
+ value);
+}
+
+
+/**
+ * Encode a SETTINGS parameter from @ref mhd_H2Setting.
+ *
+ * @param setting the setting to encode
+ * @param[out] encoded the destination 6-byte array
+ */
+static inline MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_IN_ (1)
+MHD_FN_PAR_OUT_ (2) void
+mhd_h2_setting_encode (const struct mhd_H2Setting *restrict setting,
+ uint8_t encoded[MHD_FN_PAR_FIX_ARR_SIZE_ (6)])
+{
+ mhd_h2_setting_encode3 ((uint_least16_t) setting->identifier,
+ setting->value,
+ encoded);
+}
+
+
+#if defined(_MSC_FULL_VER)
+/* Restore warnings */
+#pragma warning(pop)
+#endif /* _MSC_FULL_VER */
+
+#endif /* ! MHD_H2_SETTINGS_H */
diff --git a/src/mhd2/h2/h2_stream_data.h b/src/mhd2/h2/h2_stream_data.h
@@ -0,0 +1,152 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
+/*
+ This file is part of GNU libmicrohttpd.
+ Copyright (C) 2025 Evgeny Grin (Karlson2k)
+
+ GNU libmicrohttpd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ GNU libmicrohttpd is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ Alternatively, you can redistribute GNU libmicrohttpd and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of
+ the License, or (at your option) any later version, together
+ with the eCos exception, as follows:
+
+ As a special exception, if other files instantiate templates or
+ use macros or inline functions from this file, or you compile this
+ file and link it with other works to produce a work based on this
+ file, this file does not by itself cause the resulting work to be
+ covered by the GNU General Public License. However the source code
+ for this file must still be made available in accordance with
+ section (3) of the GNU General Public License v2.
+
+ This exception does not invalidate any other reasons why a work
+ based on this file might be covered by the GNU General Public
+ License.
+
+ You should have received copies of the GNU Lesser General Public
+ License and the GNU General Public License along with this library;
+ if not, see <https://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file src/mhd2/h2/h2_stream_data.h
+ * @brief Definition of structure for HTTP/2 stream data
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+#ifndef MHD_H2_STREAM_DATA_H
+#define MHD_H2_STREAM_DATA_H 1
+
+#include "mhd_sys_options.h"
+
+#include "sys_bool_type.h"
+#include "sys_base_types.h"
+
+#include "mhd_dlinked_list.h"
+
+#include "h2_err_codes.h"
+
+#include "h2_req_data.h"
+
+#define mhd_H2_REQ_ITEM_POS_INVALID ((size_t) (~((size_t) 0u)))
+
+#ifndef MHD_SIZE_UNKNOWN
+# define MHD_SIZE_UNKNOWN ((uint_least64_t) 0xFFFFFFFFFFFFFFFFu)
+#endif /* MHD_SIZE_UNKNOWN */
+
+struct MHD_Connection; /* forward declaration */
+struct MHD_Response; /* forward declaration */
+
+enum mhd_H2ReplyStage
+{
+ /**
+ * Headers should be formed and sent
+ */
+ mhd_H2_RPL_STAGE_HEADERS_INCOMPLETE = 0,
+ mhd_H2_RPL_STAGE_HEADERS_COMPLETE,
+ mhd_H2_RPL_STAGE_TRAILERS_INCOMPLETE,
+ mhd_H2_RPL_STAGE_END_STREAM,
+ mhd_H2_RPL_STAGE_BROKEN
+};
+
+struct mhd_H2ReplyFieldsData
+{
+ bool auto_cntn_len;
+
+ size_t num_sent;
+};
+
+struct mhd_H2ReplyData
+{
+ /**
+ * Response to transmit (initially NULL).
+ */
+ struct MHD_Response *response;
+
+ enum mhd_H2ReplyStage stage;
+
+ struct mhd_H2ReplyFieldsData fields;
+
+ uint_fast64_t cntn_read_pos;
+
+
+ bool send_content;
+};
+
+struct mhd_H2StreamState
+{
+ int_least32_t send_window;
+
+ int_least32_t recv_window;
+
+ bool rcvd_rst_stream;
+ enum mhd_H2ErrorCode peer_err;
+
+ bool sent_rst_stream;
+ enum mhd_H2ErrorCode mhd_err;
+};
+
+struct mhd_H2Stream; /* Forward declaration */
+
+mhd_DLINKEDL_LINKS_DEF (mhd_H2Stream);
+
+struct mhd_H2Stream
+{
+ /**
+ * Must be always 'true'
+ */
+ bool is_h2;
+
+ uint_least32_t stream_id;
+
+ /**
+ * The links in the container
+ */
+ mhd_DLNKDL_LINKS (mhd_H2Stream, streams);
+
+ /**
+ * The links in the sending list (when sending only)
+ */
+ mhd_DLNKDL_LINKS (mhd_H2Stream, send_q);
+
+ /**
+ * Pointer to the connection structure which is processing this stream
+ */
+ struct MHD_Connection *c;
+
+ struct mhd_H2RequestData req;
+
+ struct mhd_H2ReplyData rpl;
+
+ struct mhd_H2StreamState state;
+};
+
+#endif /* ! MHD_H2_STREAM_DATA_H */
diff --git a/src/mhd2/h2/hpack/mhd_hpack_codec.c b/src/mhd2/h2/hpack/mhd_hpack_codec.c
@@ -5327,8 +5327,9 @@ mhd_hpack_enc_init (struct mhd_HpackEncContext *hk_enc)
mhd_dtbl_get_table_max_size (hk_enc->dyn));
/* Set all sizes to the same initial value */
- hk_enc->new_dyn_size = mhd_hpack_def_dyn_table_size;
- hk_enc->smallest_dyn_size = hk_enc->new_dyn_size;
+ hk_enc->dyn_size_peer = mhd_hpack_def_dyn_table_size;
+ hk_enc->dyn_size_new = hk_enc->dyn_size_peer;
+ hk_enc->dyn_size_smallest = hk_enc->dyn_size_peer;
return true; /* Success exit point */
}
@@ -5352,12 +5353,38 @@ mhd_hpack_enc_set_dyn_size (struct mhd_HpackEncContext *hk_enc,
size_t new_dyn_size)
{
mhd_assert (mhd_DTBL_MAX_SIZE >= new_dyn_size);
- if (hk_enc->smallest_dyn_size > new_dyn_size)
- hk_enc->smallest_dyn_size = new_dyn_size;
+ if (hk_enc->dyn_size_smallest > new_dyn_size)
+ hk_enc->dyn_size_smallest = new_dyn_size;
/* Postpone actual table resize to avoid several realloc() calls if
multiple table resizes are performed. */
- hk_enc->new_dyn_size = new_dyn_size;
+ hk_enc->dyn_size_new = new_dyn_size;
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_INOUT_ (1) bool
+mhd_hpack_enc_dyn_resize (struct mhd_HpackEncContext *hk_enc)
+{
+ mhd_assert (hk_enc->dyn_size_new >= hk_enc->dyn_size_smallest);
+
+ if (mhd_dtbl_get_table_max_size (hk_enc->dyn) != hk_enc->dyn_size_new)
+ {
+#ifndef MHD_FAVOR_SMALL_CODE
+ /* This is just an optimisation to simplify eviction later */
+ mhd_dtbl_evict_to_size (hk_enc->dyn,
+ hk_enc->dyn_size_smallest);
+#endif /* ! MHD_FAVOR_SMALL_CODE */
+
+ if (mhd_COND_HARDLY_EVER (! mhd_dtbl_resize (&(hk_enc->dyn), \
+ hk_enc->dyn_size_new)))
+ return false;
+
+ mhd_assert (mhd_dtbl_get_table_max_size (hk_enc->dyn) == \
+ hk_enc->dyn_size_new);
+ }
+
+ return true;
}
@@ -5959,7 +5986,7 @@ hpack_enc_field (struct mhd_HpackEncContext *restrict hk_enc,
{
const size_t field_size =
name->size + value->size + mhd_dtbl_entry_overhead;
- const size_t dyn_size = hk_enc->new_dyn_size;
+ const size_t dyn_size = hk_enc->dyn_size_new;
const size_t dyn_used = mhd_dtbl_get_table_used (hk_enc->dyn);
const size_t dyn_free = dyn_size - dyn_used;
const size_t num_entries = mhd_dtbl_get_num_entries (hk_enc->dyn);
@@ -6089,14 +6116,19 @@ hpack_enc_check_dyn_size_update (
size_t pos_incr;
struct mhd_HpackDTblContext *restrict const dyn = hk_enc->dyn;
- mhd_assert (mhd_DTBL_MAX_SIZE >= hk_enc->smallest_dyn_size);
- mhd_assert (mhd_DTBL_MAX_SIZE >= hk_enc->new_dyn_size);
- mhd_assert (hk_enc->new_dyn_size >= hk_enc->smallest_dyn_size);
+ mhd_assert (mhd_DTBL_MAX_SIZE >= hk_enc->dyn_size_smallest);
+ mhd_assert (mhd_DTBL_MAX_SIZE >= hk_enc->dyn_size_new);
+ mhd_assert (hk_enc->dyn_size_peer >= hk_enc->dyn_size_smallest);
+ mhd_assert (hk_enc->dyn_size_new >= hk_enc->dyn_size_smallest);
mhd_assert (mhd_dtbl_get_table_max_size (dyn) \
- >= hk_enc->smallest_dyn_size);
+ >= hk_enc->dyn_size_smallest);
+
+ if (mhd_dtbl_get_table_max_size (dyn) != hk_enc->dyn_size_smallest)
+ mhd_dtbl_evict_to_size (dyn,
+ hk_enc->dyn_size_smallest);
- if ((mhd_dtbl_get_table_max_size (dyn) == hk_enc->smallest_dyn_size) &&
- (hk_enc->new_dyn_size == hk_enc->smallest_dyn_size))
+ if ((hk_enc->dyn_size_smallest == hk_enc->dyn_size_peer) &&
+ (hk_enc->dyn_size_new == hk_enc->dyn_size_peer))
{
*bytes_encoded = 0u;
return true; /* No resize signal needed */
@@ -6108,13 +6140,13 @@ hpack_enc_check_dyn_size_update (
pos = 0u;
- if (mhd_dtbl_get_table_max_size (dyn) != hk_enc->smallest_dyn_size)
+ if (hk_enc->dyn_size_peer != hk_enc->dyn_size_smallest)
{
/* Signal the minimal size so the peer evicts entries */
pos_incr =
hpack_put_number_to_buf (dyn_size_upd_msg_prfx,
dyn_size_upd_msg_prfx_bits,
- (uint_fast32_t) hk_enc->smallest_dyn_size,
+ (uint_fast32_t) hk_enc->dyn_size_smallest,
out_buff_size,
out_buff);
@@ -6122,12 +6154,9 @@ hpack_enc_check_dyn_size_update (
return false; /* Not enough space */
pos += pos_incr;
-
- mhd_dtbl_evict_to_size (dyn,
- hk_enc->smallest_dyn_size);
}
- if (hk_enc->new_dyn_size != hk_enc->smallest_dyn_size)
+ if (hk_enc->dyn_size_new != hk_enc->dyn_size_smallest)
{
if (pos == out_buff_size)
return false; /* Not enough space for the second resize message */
@@ -6136,7 +6165,7 @@ hpack_enc_check_dyn_size_update (
pos_incr =
hpack_put_number_to_buf (dyn_size_upd_msg_prfx,
dyn_size_upd_msg_prfx_bits,
- (uint_fast32_t) hk_enc->new_dyn_size,
+ (uint_fast32_t) hk_enc->dyn_size_new,
out_buff_size - pos,
out_buff + pos);
@@ -6165,17 +6194,20 @@ hpack_enc_check_dyn_size_update (
static bool
hpack_enc_perform_dyn_size_update (struct mhd_HpackEncContext *restrict hk_enc)
{
- if (mhd_dtbl_get_table_max_size (hk_enc->dyn) != hk_enc->new_dyn_size)
+ mhd_assert (mhd_dtbl_get_table_used (hk_enc->dyn)
+ <= hk_enc->dyn_size_smallest);
+ if (mhd_dtbl_get_table_max_size (hk_enc->dyn) != hk_enc->dyn_size_new)
{
if (mhd_COND_HARDLY_EVER (! mhd_dtbl_resize (&(hk_enc->dyn), \
- hk_enc->new_dyn_size)))
+ hk_enc->dyn_size_new)))
return false;
mhd_assert (mhd_dtbl_get_table_max_size (hk_enc->dyn) == \
- hk_enc->new_dyn_size);
+ hk_enc->dyn_size_new);
}
- hk_enc->smallest_dyn_size = hk_enc->new_dyn_size;
+ hk_enc->dyn_size_smallest = hk_enc->dyn_size_new;
+ hk_enc->dyn_size_peer = hk_enc->dyn_size_new;
return true;
}
@@ -6197,11 +6229,13 @@ mhd_hpack_enc_field (struct mhd_HpackEncContext *restrict hk_enc,
size_t pos_incr;
enum mhd_HpackEncResultInternal enc_field_res;
- mhd_assert (0u != out_buff_size);
mhd_assert ((name->size & 0xFFFFFFFFu) == name->size);
mhd_assert ((value->size & 0xFFFFFFFFu) == value->size);
mhd_assert ((0u == name->size) || (':' != name->data[0]));
+ if (0u == out_buff_size)
+ return mhd_HPACK_ENC_BUFFER_TOO_SMALL;
+
pos = 0u;
/* Add Dynamic Table Size Update message if needed */
@@ -6513,10 +6547,12 @@ mhd_hpack_enc_ph_status (struct mhd_HpackEncContext *restrict hk_enc,
size_t pos_incr;
enum mhd_HpackEncResultInternal enc_field_res;
- mhd_assert (0u != out_buff_size);
mhd_assert (100u <= code);
mhd_assert (699u >= code);
+ if (0u == out_buff_size)
+ return mhd_HPACK_ENC_BUFFER_TOO_SMALL;
+
pos = 0u;
/* Add Dynamic Table Size Update message if needed */
diff --git a/src/mhd2/h2/hpack/mhd_hpack_codec.h b/src/mhd2/h2/hpack/mhd_hpack_codec.h
@@ -200,9 +200,9 @@ enum MHD_FIXED_ENUM_ mhd_HpackDecResult
* update).
* For header fields, writes "name\0value\0" to @a out_buff.
* @param hk_dec the decoder context
- * @param enc_data_size the size of @a enc_data
+ * @param enc_data_size the size of @a enc_data, must not be zero
* @param enc_data the encoded data
- * @param out_buff_size the size of @a out_buff
+ * @param out_buff_size the size of @a out_buff, must be at least two bytes
* @param[out] out_buff the output buffer for the decoded strings
* @param[out] name_len to be set to the length of the name, not counting
* zero-terminating
@@ -256,6 +256,9 @@ MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_INOUT_ (1);
/**
* Set the current maximum dynamic table size for the encoder.
*
+ * To avoid repetitive memory allocations, the real table resize is performed
+ * later.
+ *
* @param hk_enc the encoder context
* @param new_dyn_size the new limit in bytes,
* must be <= #mhd_DTBL_MAX_SIZE and must be within
@@ -266,6 +269,19 @@ mhd_hpack_enc_set_dyn_size (struct mhd_HpackEncContext *hk_enc,
size_t new_dyn_size)
MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_INOUT_ (1);
+/**
+ * Perform dynamic table resize if it is pending.
+ *
+ * If table is resized before encoding new fields, then encoding functions
+ * never return #mhd_HPACK_ENC_RES_ALLOC_ERR.
+ *
+ * @param hk_enc the encoder context
+ * @return 'true' on success,
+ * 'false' on allocation error
+ */
+MHD_INTERNAL bool
+mhd_hpack_enc_dyn_resize (struct mhd_HpackEncContext *hk_enc)
+MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_INOUT_ (1);
/**
* Preference for HPACK encoding
@@ -450,9 +466,11 @@ enum MHD_FIXED_ENUM_ mhd_HpackEncResult
* @param out_buff_size the size of @a out_buff in bytes
* @param[out] out_buff the output buffer to receive the encoded data
* @param[out] bytes_encoded set to the number of bytes written to @a out_buff
- * @return #mhd_HPACK_ENC_RES_OK on success,
- * #mhd_HPACK_ENC_BUFFER_TOO_SMALL if the output buffer is too small,
- * #mhd_HPACK_ENC_RES_ALLOC_ERR on dynamic table allocation error
+ * @return #mhd_HPACK_ENC_RES_OK on success;
+ * #mhd_HPACK_ENC_BUFFER_TOO_SMALL if the output buffer is too small;
+ * #mhd_HPACK_ENC_RES_ALLOC_ERR on dynamic table allocation error,
+ * never returned if #mhd_hpack_enc_set_dyn_size() was not earlier or
+ * if #mhd_hpack_enc_dyn_resize() after #mhd_hpack_enc_set_dyn_size()
*/
MHD_INTERNAL enum mhd_HpackEncResult
mhd_hpack_enc_field (struct mhd_HpackEncContext *restrict hk_enc,
diff --git a/src/mhd2/h2/hpack/mhd_hpack_enc_types.h b/src/mhd2/h2/hpack/mhd_hpack_enc_types.h
@@ -64,22 +64,29 @@ struct mhd_HpackEncContext
/**
* The latest set dynamic table maximum size.
* If it is different from the current size set in the @a dyn, then the
- * Dynamic Table Size Update message will be added automatically before the
- * first encoded header.
+ * @a dyn is resized before adding new field.
* Set initially to the default size of the dynamic table.
*/
- size_t new_dyn_size;
+ size_t dyn_size_new;
/**
* The smallest dynamic table size used after the last Dynamic Table Size
* Update message.
* If this value is different from the current size set in the @a dyn, then
- * two Dynamic Table Size Update messages will be added automatically before
- * the first encoded header (first message with the minimal size and the
- * second message with the final size).
+ * the fields from dynamic table are evicted to specified size before adding
+ * new fields.
+ * Set initially to the default size of the dynamic table.
+ */
+ size_t dyn_size_smallest;
+
+ /**
+ * Last reported to peer size of the dynamic table.
+ * If @a new_dyn_size or @a smallest_dyn_size are different then
+ * Dynamic Table Size Update messages are added automatically before the
+ * first encoded header.
* Set initially to the default size of the dynamic table.
*/
- size_t smallest_dyn_size;
+ size_t dyn_size_peer;
};
#endif /* ! MHD_HPACK_ENC_TYPES_H */
diff --git a/src/mhd2/mhd_action.h b/src/mhd2/mhd_action.h
@@ -388,4 +388,21 @@ struct MHD_UploadAction
union mhd_UploadActionData data;
};
+
+/**
+ * The action set by the application
+ */
+struct mhd_ApplicationAction
+{
+ /**
+ * The action after header reporting
+ */
+ struct MHD_Action head_act;
+ /**
+ * The action during upload processing
+ */
+ struct MHD_UploadAction upl_act;
+};
+
+
#endif /* ! MHD_ACTION_H */
diff --git a/src/mhd2/mhd_comm_layer_state.h b/src/mhd2/mhd_comm_layer_state.h
@@ -0,0 +1,73 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
+/*
+ This file is part of GNU libmicrohttpd.
+ Copyright (C) 2025 Evgeny Grin (Karlson2k)
+
+ GNU libmicrohttpd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ GNU libmicrohttpd is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ Alternatively, you can redistribute GNU libmicrohttpd and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of
+ the License, or (at your option) any later version, together
+ with the eCos exception, as follows:
+
+ As a special exception, if other files instantiate templates or
+ use macros or inline functions from this file, or you compile this
+ file and link it with other works to produce a work based on this
+ file, this file does not by itself cause the resulting work to be
+ covered by the GNU General Public License. However the source code
+ for this file must still be made available in accordance with
+ section (3) of the GNU General Public License v2.
+
+ This exception does not invalidate any other reasons why a work
+ based on this file might be covered by the GNU General Public
+ License.
+
+ You should have received copies of the GNU Lesser General Public
+ License and the GNU General Public License along with this library;
+ if not, see <https://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file src/mhd2/mhd_comm_layer_state.h
+ * @brief The definition of simplified state of communication layer
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+#ifndef MHD_COMM_LAYER_STATE_H
+#define MHD_COMM_LAYER_STATE_H 1
+
+#include "mhd_sys_options.h"
+
+/**
+ * Simplified state of communication layer
+ */
+enum MHD_FIXED_ENUM_ mhd_CommLayerState
+{
+ /**
+ * This (or lower) layer is processing the data.
+ * Upper layer communication is not possible.
+ */
+ mhd_COMM_LAYER_PROCESSING = -1
+ ,
+ /**
+ * This layer (and all lower layers) is connected.
+ * Upper layer communication can be performed.
+ */
+ mhd_COMM_LAYER_OK = 0,
+ /**
+ * This (or lower) Layer is broken.
+ * Connection must be closed.
+ */
+ mhd_COMM_LAYER_BROKEN = 1
+};
+
+#endif /* ! MHD_COMM_LAYER_STATE_H */
diff --git a/src/mhd2/mhd_connection.h b/src/mhd2/mhd_connection.h
@@ -1,7 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
/*
This file is part of GNU libmicrohttpd.
- Copyright (C) 2014-2024 Evgeny Grin (Karlson2k)
+ Copyright (C) 2014-2025 Evgeny Grin (Karlson2k)
Copyright (C) 2007-2018 Daniel Pittman and Christian Grothoff
GNU libmicrohttpd is free software; you can redistribute it and/or
@@ -73,6 +73,10 @@
# include "mhd_upgrade.h"
#endif /* MHD_SUPPORT_UPGRADE */
+#ifdef MHD_SUPPORT_HTTP2
+# include "mhd_http_layer_state.h"
+#endif
+
#include "mhd_socket_error.h"
#include "mhd_public_api.h"
@@ -81,6 +85,10 @@
# include "mhd_tls_choice.h" /* For the TLS struct forward declaration */
#endif
+#ifdef MHD_SUPPORT_HTTP2
+# include "h2/h2_conn_data.h"
+#endif
+
/**
* Minimum reasonable size by which MHD tries to increment read/write buffers.
* We usually begin with half the available pool space for the
@@ -200,6 +208,46 @@ enum mhd_TlsBufDataIn
#endif /* MHD_SUPPORT_HTTPS */
+#ifdef MHD_SUPPORT_HTTP2
+/**
+ * The HTTP protocol version family
+ */
+enum MHD_FIXED_ENUM_ mhd_HttpVerFamily
+{
+ /**
+ * Not yet detected
+ */
+ mhd_HTTP_VER_FAM_NOT_SET = 0
+ ,
+ /**
+ * Not HTTP/2 (assuming HTTP/1.1 or HTTP/1.0)
+ */
+ mhd_HTTP_VER_FAM_NOT_2
+ ,
+ /***
+ * HTTP/2 (also called HTTP/2.0)
+ */
+ mhd_HTTP_VER_FAM_2
+ ,
+ /**
+ * Unsupported (but detected) protocol family
+ */
+ mhd_HTTP_VER_FAM_UNSUPPORTED = 99
+ ,
+ /**
+ * Invalid protocol family
+ */
+ mhd_HTTP_VER_FAM_INVALID = 100
+};
+
+
+struct mhd_HttpCommLayer
+{
+ enum mhd_HttpLayerState state;
+ enum mhd_HttpVerFamily fam;
+};
+#endif /* MHD_SUPPORT_HTTP2 */
+
/**
* What is this connection waiting for?
*/
@@ -570,6 +618,18 @@ struct MHD_Connection
enum mhd_TlsBufDataIn tls_has_data_in;
#endif /* MHD_SUPPORT_HTTPS */
+#ifdef MHD_SUPPORT_HTTP2
+ /**
+ * HTTP communication layer
+ */
+ struct mhd_HttpCommLayer h_layer;
+
+ /**
+ * HTTP/2 data
+ * Used only if @ h_layer.fam is #mhd_HTTP_VER_FAM_2
+ */
+ struct mhd_H2ConnData h2;
+#endif
/**
* 'true' if connection is in 'process ready' list,
* 'false' otherwise
@@ -800,4 +860,11 @@ struct MHD_Connection
# define mhd_C_HAS_TLS_DATA_IN(c) (0)
#endif /* ! MHD_SUPPORT_HTTPS */
+#ifdef MHD_SUPPORT_HTTP2
+# define mhd_C_IS_HTTP2(c) \
+ (mhd_HTTP_VER_FAM_2 == c->h_layer.fam)
+#else /* ! MHD_SUPPORT_HTTP2 */
+# define mhd_C_IS_HTTP2(c) (! ! 0)
+#endif /* ! MHD_SUPPORT_HTTP2 */
+
#endif /* ! MHD_CONNECTION_H */
diff --git a/src/mhd2/mhd_daemon.h b/src/mhd2/mhd_daemon.h
@@ -1071,6 +1071,27 @@ struct mhd_DaemonLargeBuffer
#endif
};
+#ifdef MHD_SUPPORT_HTTP2
+
+/**
+ * Generic settings for HTTP layer communication handling.
+ *
+ * These settings do not include specific settings for requests processing.
+ */
+struct mhd_DaemonHttpSettings
+{
+ /**
+ * 'true' if HTTP/1.x communication is allowed
+ */
+ bool http1x;
+ /**
+ * 'true' if HTTP/2 communication is allowed
+ */
+ bool http2;
+};
+
+#endif /* MHD_SUPPORT_HTTP2 */
+
/**
* Settings for requests processing
*/
@@ -1212,6 +1233,18 @@ struct MHD_Daemon
*/
struct mhd_DaemonConnections conns;
+
+ /* HTTP communication layer */
+#ifdef MHD_SUPPORT_HTTP2
+ /**
+ * Generic settings for HTTP layer communication handling.
+ *
+ * These settings do not include specific settings for requests processing.
+ */
+ struct mhd_DaemonHttpSettings http_cfg;
+#endif /* MHD_SUPPORT_HTTP2 */
+
+
/* Request processing data */
/**
@@ -1319,4 +1352,13 @@ struct MHD_Daemon
# define mhd_D_HAS_AUTH_DIGEST(d) (! ! 0)
#endif
+#ifdef MHD_SUPPORT_HTTP2
+# define mhd_D_IS_HTTP1_ENABLED(d) (d->http_cfg.http1x)
+# define mhd_D_IS_HTTP2_ENABLED(d) (d->http_cfg.http2)
+#else /* ! MHD_SUPPORT_HTTP2 */
+# define mhd_D_IS_HTTP1_ENABLED(d) (! 0)
+# define mhd_D_IS_HTTP2_ENABLED(d) (! ! 0)
+#endif /* ! MHD_SUPPORT_HTTP2 */
+
+
#endif /* ! MHD_DAEMON_H */
diff --git a/src/mhd2/mhd_http_layer_state.h b/src/mhd2/mhd_http_layer_state.h
@@ -0,0 +1,81 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
+/*
+ This file is part of GNU libmicrohttpd.
+ Copyright (C) 2025 Evgeny Grin (Karlson2k)
+
+ GNU libmicrohttpd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ GNU libmicrohttpd is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ Alternatively, you can redistribute GNU libmicrohttpd and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of
+ the License, or (at your option) any later version, together
+ with the eCos exception, as follows:
+
+ As a special exception, if other files instantiate templates or
+ use macros or inline functions from this file, or you compile this
+ file and link it with other works to produce a work based on this
+ file, this file does not by itself cause the resulting work to be
+ covered by the GNU General Public License. However the source code
+ for this file must still be made available in accordance with
+ section (3) of the GNU General Public License v2.
+
+ This exception does not invalidate any other reasons why a work
+ based on this file might be covered by the GNU General Public
+ License.
+
+ You should have received copies of the GNU Lesser General Public
+ License and the GNU General Public License along with this library;
+ if not, see <https://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file src/mhd2/mhd_http_layer_state.h
+ * @brief The definition of the HTTP layer connection state
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+#ifndef MHD_HTTP_LAYER_STATE_H
+#define MHD_HTTP_LAYER_STATE_H 1
+
+#include "mhd_sys_options.h"
+
+/**
+ * HTTP layer communication state
+ */
+enum MHD_FIXED_ENUM_ mhd_HttpLayerState
+{
+ /**
+ * Performing handshake
+ */
+ mhd_HTTP_LAYER_PREFACE
+ ,
+ /**
+ * The HTTP layer is fully connected, normal HTTP communication
+ */
+ mhd_HTTP_LAYER_CONNECTED
+ ,
+ /**
+ * Performing graceful closing of the HTTP communication
+ */
+ mhd_HTTP_LAYER_CLOSING
+ ,
+ /**
+ * HTTP communication closed
+ */
+ mhd_HTTP_LAYER_CLOSED
+ ,
+ /**
+ * HTTP communication broken
+ */
+ mhd_HTTP_LAYER_BROKEN
+};
+
+#endif /* ! MHD_HTTP_LAYER_STATE_H */
diff --git a/src/mhd2/mhd_lib_init.c b/src/mhd2/mhd_lib_init.c
@@ -67,6 +67,9 @@
#ifdef MHD_SUPPORT_HTTPS
# include "mhd_tls_funcs.h"
#endif
+#ifdef MHD_SUPPORT_HTTP2
+# include "h2/hpack/h2_huffman_codec.h"
+#endif
#include "mhd_lib_init.h"
#include "mhd_lib_init_auto.h"
@@ -377,6 +380,9 @@ mhd_lib_global_full_init_once (void)
#ifdef MHD_SUPPORT_HTTPS
mhd_tls_global_init_once ();
#endif /* MHD_SUPPORT_HTTPS */
+#ifdef MHD_SUPPORT_HTTP2
+ mhd_h2_huffman_init ();
+#endif /* MHD_SUPPORT_HTTP2 */
mhd_lib_fully_inited_once = true;
diff --git a/src/mhd2/mhd_request.h b/src/mhd2/mhd_request.h
@@ -64,21 +64,9 @@
# include "mhd_post_parser.h"
#endif
-
-/**
- * The action set by the application
- */
-struct mhd_ApplicationAction
-{
- /**
- * The action after header reporting
- */
- struct MHD_Action head_act;
- /**
- * The action during upload processing
- */
- struct MHD_UploadAction upl_act;
-};
+#ifdef MHD_SUPPORT_HTTP2
+# include "h2/h2_stream_data.h"
+#endif
/**
* The request line processing data
@@ -398,6 +386,12 @@ struct mhd_ReqAuthData
*/
struct MHD_Request
{
+#ifdef MHD_SUPPORT_HTTP2
+ /**
+ * Always 'false' in HTTP/1.x requests
+ */
+ bool is_http2;
+#endif /* MHD_SUPPORT_HTTP2 */
/**
* Linked list of parsed headers.
*/
@@ -556,5 +550,23 @@ struct MHD_Request
union MHD_HeadersProcessing hdrs;
};
+#ifdef MHD_SUPPORT_HTTP2
+# define mhd_REQ_IS_HTTP2(req) ((req)->is_http2)
+#else /* ! MHD_SUPPORT_HTTP2 */
+# define mhd_REQ_IS_HTTP2(req) (! ! 0)
+#endif /* ! MHD_SUPPORT_HTTP2 */
+
+#ifdef MHD_SUPPORT_HTTP2
+# define mhd_REQ_GET_ACT_UNION(req) \
+ (mhd_REQ_IS_HTTP2 ((req)) ? \
+ &(((struct mhd_H2RequestData*) (req))->app_act) : \
+ &((req)->app_act))
+#else /* ! MHD_SUPPORT_HTTP2 */
+# define mhd_REQ_GET_ACT_UNION(req) (&((req)->app_act))
+#endif /* ! MHD_SUPPORT_HTTP2 */
+
+#define mhd_REQ_GET_ACT_HEAD(req) (&(mhd_REQ_GET_ACT_UNION (req)->head_act))
+
+#define mhd_REQ_GET_ACT_UPLD(req) (&(mhd_REQ_GET_ACT_UNION (req)->upl_act))
#endif /* ! MHD_REQUEST_H */
diff --git a/src/mhd2/mhd_response.h b/src/mhd2/mhd_response.h
@@ -66,6 +66,10 @@
#include "mhd_atomic_counter.h"
+#ifdef MHD_SUPPORT_HTTP2
+# include "h2/h2_resp_data.h"
+#endif
+
struct ResponseOptions; /* forward declaration */
@@ -88,6 +92,13 @@ struct mhd_ResponseHeader
*/
struct MHD_String value;
+#ifdef MHD_SUPPORT_HTTP2
+ /**
+ * HTTP/2-specific header / field data
+ */
+ struct mhd_H2ResponseHeader h2;
+#endif /* MHD_SUPPORT_HTTP2 */
+
/**
* The links to other headers
*/
diff --git a/src/mhd2/mhd_str.c b/src/mhd2/mhd_str.c
@@ -44,12 +44,15 @@
#include "mhd_sys_options.h"
-#include "mhd_str.h"
+#include "mhd_assert.h"
+#include "mhd_limits.h"
+
+#include "mhd_constexpr.h"
+#include "mhd_assume.h"
#include <string.h>
-#include "mhd_assert.h"
-#include "mhd_limits.h"
+#include "mhd_str.h"
#ifdef MHD_FAVOR_SMALL_CODE
# ifdef mhd_static_inline
@@ -97,7 +100,9 @@ isasciilower (char c)
mhd_static_inline MHD_FN_CONST_ bool
isasciiupper (char c)
{
- return (c <= 'Z') && (c >= 'A');
+ const unsigned int uc = (unsigned int) (unsigned char) c;
+ const unsigned int t = uc - (unsigned int) (unsigned char) 'A';
+ return (((unsigned int) ('Z' - 'A')) >= t);
}
@@ -162,8 +167,6 @@ isasciialnum (char c)
#endif /* Disable unused functions. */
-
-#if 0 /* Disable unused functions. */
/**
* Convert US-ASCII character to lower case.
* If character is upper case letter in US-ASCII than it's converted to lower
@@ -176,10 +179,12 @@ isasciialnum (char c)
mhd_static_inline MHD_FN_CONST_ char
toasciilower (char c)
{
- return isasciiupper (c) ? (char) (0x20u | (unsigned char) c) : c;
+ return (char) (((unsigned char) c) | ((isasciiupper (c) ? 1u : 0u) << 5u));
}
+#if 0 /* Disable unused functions. */
+
/**
* Convert US-ASCII character to upper case.
* If character is lower case letter in US-ASCII than it's converted to upper
@@ -839,6 +844,24 @@ charsequalcaseless (char c1, char c2)
}
+/**
+ * Compare mixed case and lower case characters.
+ *
+ * @param mc the mixed case char to compare
+ * @param lc the lower case char to compare
+ * @return boolean 'true' if chars are caseless equal, false otherwise
+ */
+mhd_static_inline MHD_FN_CONST_ bool
+charsequallowercase (char mc, char lc)
+{
+ char uc;
+ if (mc == lc)
+ return true;
+ uc = ((char) (~0x20u & (unsigned char) lc));
+ return (mc == uc) && isasciiupper (mc);
+}
+
+
#else /* !HAVE_INLINE_FUNCS */
@@ -913,8 +936,8 @@ charsequalcaseless (char c1, char c2)
* @param c character to convert
* @return converted to lower case character
*/
-# define toasciilower(c) ((isasciiupper (c)) ? (((char) (c)) - 'A' + 'a') : \
- ((char) (c)))
+# define toasciilower(c) \
+ ((isasciiupper (c)) ? (((char) (c)) - 'A' + 'a') : ((char) (c)))
/**
@@ -988,6 +1011,17 @@ static const char map_value_to_xdigit[16] =
(((0x20u | (unsigned char) (c1)) == (0x20u | (unsigned char) (c2))) \
&& isasciilower (((char) (0x20u | (unsigned char) (c2))))) )
+/**
+ * Compare mixed case and lower case characters.
+ *
+ * @param mc the mixed case char to compare
+ * @param lc the lower case char to compare
+ * @return boolean 'true' if chars are caseless equal, false otherwise
+ */
+#define charsequallowercase(mc,lc) \
+ ( ((mc) == (lc)) || \
+ (((0x20u | (unsigned char) (mc)) == ((unsigned char) (lc))) && \
+ isasciilower (lc)) )
#endif /* !HAVE_INLINE_FUNCS */
@@ -1061,6 +1095,56 @@ mhd_str_equal_caseless_bin_n (const char *const str1,
MHD_INTERNAL MHD_FN_PURE_ MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_IN_ (1)
+MHD_FN_PAR_IN_ (2) bool
+mhd_str_equal_lowercase_bin_n (const char *const mixstr,
+ const char *const lowstr,
+ size_t len)
+{
+ size_t i;
+
+ for (i = 0; i < len; ++i)
+ {
+ const char mc = mixstr[i];
+ const char lc = lowstr[i];
+ mhd_assert (! isasciiupper (lc));
+ if (! charsequallowercase (mc, lc))
+ return false;
+ }
+ return true;
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_IN_SIZE_ (2,1)
+MHD_FN_PAR_OUT_SIZE_ (3,1) void
+mhd_str_to_lowercase_bin_n (size_t size,
+ const char *restrict inbuff,
+ char *restrict outbuff)
+{
+ size_t i;
+
+ for (i = 0; i < size; ++i)
+ outbuff[i] = toasciilower (inbuff[i]);
+}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_IN_SIZE_ (2,1) bool
+mhd_str_is_lowercase_bin_n (size_t len,
+ const char *restrict str)
+{
+ size_t i;
+
+ for (i = 0; i < len; ++i)
+ if (isasciiupper (str[i]))
+ return false;
+
+ return true;
+}
+
+
+MHD_INTERNAL MHD_FN_PURE_ MHD_FN_PAR_NONNULL_ALL_
MHD_FN_PAR_CSTR_ (1)
MHD_FN_PAR_IN_ (1) MHD_FN_PAR_IN_ (2) bool
mhd_str_has_token_caseless (const char *restrict str,
@@ -2249,6 +2333,272 @@ mhd_str_pct_decode_in_place_lenient (char *restrict str,
}
+MHD_INTERNAL
+MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_INOUT_SIZE_ (2,1) size_t
+mhd_str_dec_norm_uri_path (size_t str_len,
+ char *restrict str)
+{
+ size_t r;
+ size_t w;
+
+ mhd_assert ((str_len + 3u) > str_len); /* Algo does not work with str_len close to max */
+
+ for (r = 0u, w = 0u; str_len > r; ++r)
+ {
+ /* Process all segments not started with "/" if any */
+ char c;
+ mhd_ASSUME (w <= r);
+ c = str[r];
+ if ('/' == c)
+ break; /* Processed after this loop */
+ if ('%' == c && str_len > (r + 2u))
+ {
+ const int h = xdigittovalue (str[r + 1u]);
+ const int l = xdigittovalue (str[r + 2u]);
+ if ((0 <= h) && (0 <= l))
+ {
+ char dec;
+ dec = (char) ((unsigned char) l | (((unsigned char) h) << 4u));
+ if ('/' != dec)
+ {
+ c = dec;
+ r += 2u;
+ }
+ }
+ }
+ if ('.' == c)
+ {
+ char c2;
+ if (str_len == r + 1u)
+ continue; /* Skip "."; actually stop */
+ mhd_ASSUME (w <= r);
+ c2 = str[r + 1u];
+ if ('/' == c2)
+ {
+ ++r;
+ continue; /* Skip "./" */
+ }
+ if ('%' == c2 && str_len > (r + 3u))
+ {
+ const int h = xdigittovalue (str[r + 2u]);
+ const int l = xdigittovalue (str[r + 3u]);
+ if ((0 <= h) && (0 <= l))
+ {
+ char dec;
+ dec = (char) ((unsigned char) l | (((unsigned char) h) << 4u));
+ if ('/' != dec)
+ {
+ c2 = dec;
+ r += 2u;
+ }
+ }
+ }
+ if ('.' == c2)
+ {
+ char c3;
+ if (str_len == r + 2u)
+ {
+ ++r;
+ continue; /* Skip ".."; actually stop */
+ }
+ mhd_ASSUME (w <= r);
+ c3 = str[r + 2u];
+ if ('/' == c3)
+ {
+ r += 2u;
+ continue; /* Skip "../" */
+ }
+ str[w++] = c;
+ str[w++] = c2;
+ /* c3 has not been percent-decoded */
+ r += 2u;
+ }
+ else
+ {
+ str[w++] = c;
+ str[w++] = c2;
+ r += 2u;
+ }
+ }
+ else
+ {
+ str[w++] = c;
+ r += 1u;
+ }
+ break;
+ }
+ mhd_ASSUME (w <= r);
+ /* Found first segment with is not "../" and is not "./" */
+ for ((void) r; str_len > r && '/' != str[r]; ++r)
+ {
+ char c;
+ mhd_ASSUME (w <= r);
+ c = str[r];
+ if ('%' == c && str_len > (r + 2u))
+ {
+ const int h = xdigittovalue (str[r + 1u]);
+ const int l = xdigittovalue (str[r + 2u]);
+ if ((0 <= h) && (0 <= l))
+ {
+ char dec;
+ dec = (char) ((unsigned char) l | (((unsigned char) h) << 4u));
+ if ('/' != dec)
+ {
+ c = dec;
+ r += 2u;
+ }
+ }
+ }
+ mhd_ASSUME (w <= r);
+ str[w++] = c;
+ }
+ if (str_len > r)
+ {
+ /* Found first segment started with '/' */
+ mhd_ASSUME ('/' == str[r]);
+ while (str_len > r)
+ {
+ char slash_chr = str[r];
+ size_t seg_start = w;
+ mhd_ASSUME ('/' == slash_chr);
+ str[w++] = slash_chr;
+ ++r;
+ if (str_len > r)
+ {
+ char c;
+ mhd_ASSUME (w <= r);
+ c = str[r];
+ if ('/' == c)
+ continue;
+ if ('%' == c && str_len > (r + 2u))
+ {
+ const int h = xdigittovalue (str[r + 1u]);
+ const int l = xdigittovalue (str[r + 2u]);
+ if ((0 <= h) && (0 <= l))
+ {
+ char dec;
+ dec = (char) ((unsigned char) l | (((unsigned char) h) << 4u));
+ if ('/' != dec)
+ {
+ c = dec;
+ r += 2u;
+ }
+ }
+ }
+ if ('.' == c)
+ {
+ char c2;
+ if (str_len == r + 1u)
+ {
+ ++r;
+ break; /* Skip ".", leave bare '/' */
+ }
+ mhd_ASSUME (w <= r);
+ c2 = str[r + 1u];
+ if ('/' == c2)
+ {
+ w = seg_start;
+ ++r;
+ continue; /* Skip "."; go to the next "/", which will be written again */
+ }
+ if ('%' == c2 && str_len > (r + 3u))
+ {
+ const int h = xdigittovalue (str[r + 2u]);
+ const int l = xdigittovalue (str[r + 3u]);
+ if ((0 <= h) && (0 <= l))
+ {
+ char dec;
+ dec = (char) ((unsigned char) l | (((unsigned char) h) << 4u));
+ if ('/' != dec)
+ {
+ c2 = dec;
+ r += 2u;
+ }
+ }
+ }
+ if ('.' == c2)
+ {
+ char c3;
+ if (str_len == r + 2u)
+ {
+ w = seg_start;
+ if (0 < w)
+ do
+ {
+ --w;
+ } while (0 < w && '/' != str[w]);
+ str[w++] = '/';
+ r += 2u;
+ break; /* Skip ".."; replace prev segment with '/' */
+ }
+ mhd_ASSUME (w <= r);
+ c3 = str[r + 2u];
+ if ('/' == c3)
+ {
+ w = seg_start;
+ if (0 < w)
+ do
+ {
+ --w;
+ } while (0 < w && '/' != str[w]);
+ r += 2u;
+ continue; /* Skip ".."; put next '/' to the start of prev segment */
+ }
+ str[w++] = c;
+ str[w++] = c2;
+ /* c3 has not been percent-decoded */
+ r += 2u;
+ }
+ else
+ {
+ str[w++] = c;
+ str[w++] = c2;
+ r += 2u;
+ }
+ }
+ else
+ {
+ str[w++] = c;
+ r += 1u;
+ }
+ mhd_assert (seg_start < w);
+ }
+ for ((void) r; str_len > r && '/' != str[r]; ++r)
+ {
+ /* Process the end of the segment */
+ char c;
+ mhd_ASSUME (w <= r);
+ c = str[r];
+ if ('%' == c && str_len > (r + 2u))
+ {
+ const int h = xdigittovalue (str[r + 1u]);
+ const int l = xdigittovalue (str[r + 2u]);
+ if ((0 <= h) && (0 <= l))
+ {
+ char dec;
+ dec = (char) ((unsigned char) l | (((unsigned char) h) << 4u));
+ if ('/' != dec)
+ {
+ c = dec;
+ r += 2u;
+ }
+ }
+ }
+ mhd_ASSUME (w <= r);
+ str[w++] = c;
+ }
+ }
+ mhd_assert (0u != w);
+ mhd_assert ((r == str_len) || ('/' == str[w - 1u]));
+ }
+
+ if (str_len > w)
+ str[w] = '\0';
+
+ return w;
+}
+
+
#ifdef MHD_SUPPORT_AUTH_DIGEST
MHD_INTERNAL MHD_FN_PURE_ MHD_FN_PAR_NONNULL_ALL_
diff --git a/src/mhd2/mhd_str.h b/src/mhd2/mhd_str.h
@@ -137,6 +137,49 @@ MHD_FN_PURE_ MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_IN_ (1) MHD_FN_PAR_IN_ (2);
&& mhd_str_equal_caseless_bin_n (arr,str,l))
/**
+ * Check two string for equality, converting first string to US-ASCII lower case
+ * before comparing and checking not more than @a len bytes.
+ * Compares not more first than @a len bytes, including binary zero characters.
+ * Comparison stops at first unmatched byte.
+ * @param mixstr mixed case string to compare
+ * @param lowstr lower case string to compare
+ * @param len number of characters to compare
+ * @return 'true' if two strings are equal, 'false' otherwise.
+ */
+MHD_INTERNAL bool
+mhd_str_equal_lowercase_bin_n (const char *const mixstr,
+ const char *const lowstr,
+ size_t len)
+MHD_FN_PURE_ MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_IN_ (1) MHD_FN_PAR_IN_ (2);
+
+/**
+ * Convert sequence of bytes to lower case US-ASCII letters.
+ * @param size the size of the data in the @a inbuff
+ * @param inbuff the input data, does not need to be zero-terminated;
+ * if it is zero-terminated and zero-termination of @a outbuff
+ * is needed, make sure that @a size includes zero-termination
+ * @param[out] outbuff the output buffer; should have at least @a size bytes
+ * available
+ */
+MHD_INTERNAL void
+mhd_str_to_lowercase_bin_n (size_t size,
+ const char *restrict inbuff,
+ char *restrict outbuff)
+MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_IN_SIZE_ (2,1) MHD_FN_PAR_OUT_SIZE_ (3,1);
+
+/**
+ * Check whether the string does not contain US-ASCII upper case letters.
+ * @param len the length of the @a str
+ * @param str the string to check, does not need to be zero terminated
+ * @return 'true' if not US-ASCII letter found in the string,
+ * 'false' otherwise.
+ */
+MHD_INTERNAL bool
+mhd_str_is_lowercase_bin_n (size_t len,
+ const char *restrict str)
+MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_IN_SIZE_ (2,1);
+
+/**
* Check whether @a str has case-insensitive @a token.
* Token could be surrounded by spaces and tabs and delimited by comma.
* Match succeed if substring between start, end (of string) or comma
@@ -649,6 +692,12 @@ mhd_str_pct_decode_in_place_lenient (char *restrict str,
bool *restrict broken_encoding)
MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_CSTR_ (1);
+MHD_INTERNAL size_t
+mhd_str_dec_norm_uri_path (size_t str_len,
+ char *restrict str)
+MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_INOUT_SIZE_ (2,1);
+
+
#ifdef MHD_SUPPORT_AUTH_DIGEST
/**
* Check two strings for equality, "unquoting" the first string from quoted
diff --git a/src/mhd2/mhd_stream.h b/src/mhd2/mhd_stream.h
@@ -47,6 +47,8 @@
#include "mhd_sys_options.h"
+#include "sys_bool_type.h"
+
/**
* The HTTP stream data.
* For HTTP/1.x this information held in MHD_Connection structure
@@ -54,10 +56,15 @@
struct MHD_Stream
{
/**
- * The dummy member.
- * Do not use
+ * Always 'false' for HTTP/1.x streams
*/
- int place_holder;
+ bool is_http2;
};
+#ifdef MHD_SUPPORT_HTTP2
+# define mhd_STRM_IS_HTTP2(stream) ((stream)->is_http2)
+#else /* ! MHD_SUPPORT_HTTP2 */
+# define mhd_STRM_IS_HTTP2(stream) (! ! 0)
+#endif /* ! MHD_SUPPORT_HTTP2 */
+
#endif /* ! MHD_STREAM_H */
diff --git a/src/mhd2/mhd_tls_enums.h b/src/mhd2/mhd_tls_enums.h
@@ -87,4 +87,24 @@ enum MHD_FIXED_ENUM_ mhd_TlsProcedureResult
mhd_TLS_PROCED_FAILED
};
+/**
+ * Protocol selected by ALPN
+ */
+enum MHD_FIXED_ENUM_ mhd_TlsAlpnProt
+{
+ /**
+ * The protocol was not selected by ALPN
+ * ALPN is not used by the client or TLS backend does not support ALPN
+ */
+ mhd_TLS_ALPN_PROT_NOT_SELECTED
+ ,
+ mhd_TLS_ALPN_PROT_HTTP1_0
+ ,
+ mhd_TLS_ALPN_PROT_HTTP1_1
+ ,
+ mhd_TLS_ALPN_PROT_HTTP2
+ ,
+ mhd_TLS_ALPN_PROT_ERROR
+};
+
#endif /* ! MHD_TLS_ENUMS_H */
diff --git a/src/mhd2/mhd_tls_funcs.h b/src/mhd2/mhd_tls_funcs.h
@@ -224,6 +224,13 @@
#define mhd_tls_conn_get_tls_ver(c_tls,tls_ver_out) \
mhd_TLS_FUNC (_conn_get_tls_ver)((c_tls),(tls_ver_out))
+/**
+ * Get a protocol selected by ALPN
+ * @param c_tls the connection TLS handle
+ * @return the selected protocol code
+ */
+#define mhd_tls_conn_get_alpn_prot(c_tls) \
+ mhd_TLS_FUNC (_conn_get_alpn_prot)((c_tls))
/* ** General information function ** */
diff --git a/src/mhd2/request_get_value.c b/src/mhd2/request_get_value.c
@@ -56,6 +56,10 @@
#include "mhd_assert.h"
#include "mhd_str.h"
+#ifdef MHD_SUPPORT_HTTP2
+# include "h2/h2_req_get_items.h"
+#endif /* MHD_SUPPORT_HTTP2 */
+
#include "mhd_public_api.h"
@@ -75,6 +79,15 @@ mhd_request_get_value_n (struct MHD_Request *restrict request,
mhd_assert (strlen (key) == key_len);
+#ifdef MHD_SUPPORT_HTTP2
+ if (mhd_REQ_IS_HTTP2 (request))
+ return mhd_h2_request_get_value_n (request,
+ kind,
+ key_len,
+ key,
+ value_out);
+#endif /* MHD_SUPPORT_HTTP2 */
+
if (MHD_VK_POSTDATA != kind)
{
struct mhd_RequestField *f;
@@ -185,6 +198,14 @@ MHD_request_get_values_cb (struct MHD_Request *request,
{
size_t count;
+ #ifdef MHD_SUPPORT_HTTP2
+ if (mhd_REQ_IS_HTTP2 (request))
+ return mhd_h2_request_get_values_cb (request,
+ kind,
+ iterator,
+ iterator_cls);
+#endif /* MHD_SUPPORT_HTTP2 */
+
count = 0;
if (MHD_VK_POSTDATA != kind)
{
diff --git a/src/mhd2/response_add_header.c b/src/mhd2/response_add_header.c
@@ -45,6 +45,9 @@
#include "mhd_sys_options.h"
+#include "mhd_str_macros.h"
+#include "mhd_arr_num_elems.h"
+
#include "response_add_header.h"
#include "mhd_response.h"
#include "mhd_locks.h"
@@ -52,39 +55,168 @@
#include <string.h>
#include "sys_malloc.h"
+#include "mhd_str.h"
+
#include "mhd_public_api.h"
+#ifdef MHD_SUPPORT_HTTP2
+static MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_CSTR_ (2) MHD_FN_PAR_IN_SIZE_ (2,1) bool
+is_name_h2_allowed (size_t name_len,
+ const char name[MHD_FN_PAR_DYN_ARR_SIZE_ (name_len)])
+{
+ static const struct MHD_String h2_forbidden[] = {
+ mhd_MSTR_INIT ("Connection"),
+ mhd_MSTR_INIT ("Transfer-Encoding"),
+ mhd_MSTR_INIT ("Upgrade"),
+ mhd_MSTR_INIT ("Keep-Alive"),
+ mhd_MSTR_INIT ("Proxy-Connection")
+ };
+ size_t i;
+
+ for (i = 0u; i < mhd_ARR_NUM_ELEMS (h2_forbidden); ++i)
+ {
+ const struct MHD_String *const frbdn = h2_forbidden + i;
+
+ if (frbdn->len != name_len)
+ continue;
+ if (mhd_str_equal_caseless_bin_n (frbdn->cstr,
+ name,
+ name_len))
+ return false;
+ }
+ return true;
+}
+
+
+#endif /* MHD_SUPPORT_HTTP2 */
static
MHD_FN_PAR_NONNULL_ (1)
-MHD_FN_PAR_NONNULL_ (3) MHD_FN_PAR_CSTR_ (3) MHD_FN_PAR_IN_SIZE_ (3,2)
-MHD_FN_PAR_NONNULL_ (5) MHD_FN_PAR_CSTR_ (5) MHD_FN_PAR_IN_SIZE_ (5,4) bool
+MHD_FN_PAR_NONNULL_ (3) MHD_FN_PAR_CSTR_ (3)
+MHD_FN_PAR_NONNULL_ (5) MHD_FN_PAR_CSTR_ (5) bool
response_add_header_no_check (
- struct MHD_Response *response,
+ struct MHD_Response *restrict response,
size_t name_len,
- const char name[MHD_FN_PAR_DYN_ARR_SIZE_ (name_len)],
+ const char *restrict name,
size_t value_len,
- const char value[MHD_FN_PAR_DYN_ARR_SIZE_ (value_len)])
+ const char *restrict value)
{
char *buf;
+ size_t pos;
struct mhd_ResponseHeader *new_hdr;
+ size_t strings_size = 0u;
+#ifdef MHD_SUPPORT_HTTP2
+ bool h2_allowed;
+ bool name_is_lower = false;
+ size_t val_empty_prf = 0u;
+ size_t val_empty_suf = 0u;
+
+ mhd_assert (0 == name[name_len]);
+ mhd_assert (0 == value[value_len]);
+
+ h2_allowed = is_name_h2_allowed (name_len,
+ name);
+ if (h2_allowed)
+ {
+ name_is_lower = mhd_str_is_lowercase_bin_n (name_len,
+ name);
+ strings_size += (name_is_lower ? 0u : (name_len + 1u));
+
+ while ((' ' == value[val_empty_prf]) || ('\t' == value[val_empty_prf]))
+ ++val_empty_prf;
+ if (val_empty_prf != value_len)
+ {
+ while ((' ' == value[value_len - 1u - val_empty_suf])
+ || ('\t' == value[value_len - 1u - val_empty_suf]))
+ ++val_empty_suf;
+ }
+
+ mhd_assert (val_empty_prf <= value_len);
+ mhd_assert ((val_empty_suf < value_len) || (0u == value_len));
+ mhd_assert (val_empty_prf + val_empty_suf <= value_len);
+
+ if ((0u != val_empty_prf) || (0u != val_empty_suf))
+ strings_size += value_len + 1u - val_empty_prf - val_empty_suf;
+ }
+#endif /* MHD_SUPPORT_HTTP2 */
+
+ mhd_assert (0 == name[name_len]);
+ mhd_assert (0 == value[value_len]);
+
+ strings_size += name_len + 1u;
+ strings_size += value_len + 1u;
new_hdr = (struct mhd_ResponseHeader *)
- malloc (sizeof(struct mhd_ResponseHeader) + name_len
- + value_len + 2);
+ malloc (sizeof(struct mhd_ResponseHeader) + strings_size);
if (NULL == new_hdr)
return false;
buf = ((char *) new_hdr) + sizeof(struct mhd_ResponseHeader);
- memcpy (buf, name, name_len);
- buf[name_len] = 0;
- new_hdr->name.cstr = buf;
+ pos = 0u;
+ memcpy (buf + pos, name, name_len + 1u);
+ new_hdr->name.cstr = buf + pos;
new_hdr->name.len = name_len;
- buf += name_len + 1;
- memcpy (buf, value, value_len);
- buf[value_len] = 0;
- new_hdr->value.cstr = buf;
+ pos += name_len + 1u;
+ memcpy (buf + pos, value, value_len + 1u);
+ new_hdr->value.cstr = buf + pos;
new_hdr->value.len = value_len;
+ pos += value_len + 1u;
+
+#ifdef MHD_SUPPORT_HTTP2
+ if (h2_allowed)
+ {
+ if (! name_is_lower)
+ {
+ mhd_str_to_lowercase_bin_n (name_len + 1u,
+ name,
+ buf + pos);
+ new_hdr->h2.name.data = buf + pos;
+ new_hdr->h2.name.size = name_len;
+ pos += name_len + 1u;
+ }
+ else
+ {
+ new_hdr->h2.name.data = new_hdr->name.cstr;
+ new_hdr->h2.name.size = new_hdr->name.len;
+ }
+
+ if ((0u != val_empty_prf) || (0u != val_empty_suf))
+ {
+ memcpy (buf + pos,
+ value + val_empty_prf,
+ value_len - val_empty_prf - val_empty_suf);
+ buf[pos + value_len - val_empty_prf - val_empty_suf] = 0;
+ new_hdr->h2.value.data = buf + pos;
+ new_hdr->h2.value.size = value_len - val_empty_prf - val_empty_suf;
+ pos += value_len - val_empty_prf - val_empty_suf + 1u;
+ }
+ else
+ {
+ new_hdr->h2.value.data = new_hdr->value.cstr;
+ new_hdr->h2.value.size = new_hdr->value.len;
+ }
+ // TODO: implement checking name for "never-index" patterns and other indexing preferences patterns
+ }
+ else
+ {
+ new_hdr->h2.name.data = NULL;
+ new_hdr->h2.name.size = 0u;
+ new_hdr->h2.value.data = NULL;
+ new_hdr->h2.value.size = 0u;
+ }
+ mhd_assert ((NULL != new_hdr->h2.name.data)
+ || (0u == new_hdr->h2.name.size));
+ mhd_assert ((NULL != new_hdr->h2.value.data)
+ || (0u == new_hdr->h2.value.size));
+ mhd_assert ((NULL != new_hdr->h2.name.data) || \
+ (NULL == new_hdr->h2.value.data));
+ mhd_assert ((NULL != new_hdr->h2.value.data) || \
+ (NULL == new_hdr->h2.name.data));
+#endif /* MHD_SUPPORT_HTTP2 */
+
+ mhd_assert (strings_size == pos);
+ (void) pos; /* Mute compiler warning in non-debug builds */
mhd_DLINKEDL_INIT_LINKS (new_hdr, headers);
mhd_DLINKEDL_INS_LAST (response, new_hdr, headers);
@@ -108,9 +240,9 @@ mhd_response_remove_all_headers (struct MHD_Response *restrict r)
static enum MHD_StatusCode
-response_add_header_int (struct MHD_Response *response,
- const char *name,
- const char *value)
+response_add_header_int (struct MHD_Response *restrict response,
+ const char *restrict name,
+ const char *restrict value)
{
const size_t name_len = strlen (name);
const size_t value_len = strlen (value);
diff --git a/src/mhd2/stream_funcs.c b/src/mhd2/stream_funcs.c
@@ -755,6 +755,77 @@ mhd_conn_remove_from_timeout_lists (struct MHD_Connection *restrict c)
}
+/* return 'true' is lingering needed, 'false' is lingering is not needed */
+static MHD_FN_PAR_NONNULL_ALL_ bool
+conn_start_socket_closing (struct MHD_Connection *restrict c,
+ bool close_hard)
+{
+ bool need_lingering;
+ /* Make changes on the socket early to let the kernel and the remote
+ * to process the changes in parallel. */
+ if (close_hard)
+ {
+ /* Use abortive closing, send RST to remote to indicate a problem */
+ (void) mhd_socket_set_hard_close (c->sk.fd);
+ c->stage = mhd_HTTP_STAGE_PRE_CLOSING;
+ c->event_loop_info = MHD_EVENT_LOOP_INFO_CLEANUP;
+
+ return false;
+ }
+
+ mhd_assert (c->sk.state.rmt_shut_wr || \
+ ! mhd_SOCKET_ERR_IS_HARD (c->sk.state.discnt_err));
+
+ need_lingering = ! c->sk.state.rmt_shut_wr;
+ if (need_lingering)
+ {
+#ifdef MHD_SUPPORT_HTTPS
+ if (mhd_C_HAS_TLS (c))
+ {
+ if ((0 != (((unsigned int) c->sk.ready)
+ & mhd_SOCKET_NET_STATE_SEND_READY))
+ || c->sk.props.is_nonblck)
+ need_lingering =
+ (mhd_TLS_PROCED_FAILED != mhd_tls_conn_shutdown (c->tls));
+ }
+ else
+#endif /* MHD_SUPPORT_HTTPS */
+ if (1)
+ {
+ need_lingering = mhd_socket_shut_wr (c->sk.fd);
+ if (need_lingering)
+ need_lingering = (! c->sk.state.rmt_shut_wr); /* Skip as already closed */
+ }
+ }
+
+ return need_lingering;
+}
+
+
+#ifdef MHD_SUPPORT_HTTP2
+
+static MHD_FN_PAR_NONNULL_ALL_ void
+conn_h2_start_closing (struct MHD_Connection *restrict c,
+ bool close_hard)
+{
+ mhd_assert (mhd_C_IS_HTTP2 (c));
+ mhd_assert (c->h2.dbg.h2_deinited);
+ mhd_assert (! c->rq.app_aware);
+
+ conn_start_socket_closing (c,
+ close_hard);
+
+ mhd_conn_remove_from_timeout_lists (c);
+
+#ifndef NDEBUG
+ c->dbg.closing_started = true;
+#endif
+}
+
+
+#endif /* MHD_SUPPORT_HTTP2 */
+
+
MHD_INTERNAL
MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_CSTR_ (3) void
mhd_conn_start_closing (struct MHD_Connection *restrict c,
@@ -776,6 +847,19 @@ mhd_conn_start_closing (struct MHD_Connection *restrict c,
log_msg ? "\"" : "");
#endif /* mhd_DEBUG_CONN_ADD_CLOSE */
+#ifdef MHD_SUPPORT_HTTP2
+ if (mhd_C_IS_HTTP2 (c))
+ {
+ mhd_assert ((mhd_CONN_CLOSE_DAEMON_SHUTDOWN == reason) ||
+ (mhd_CONN_CLOSE_H2_CLOSE_SOFT == reason) ||
+ (mhd_CONN_CLOSE_H2_CLOSE_HARD == reason));
+ mhd_assert (NULL == log_msg);
+ conn_h2_start_closing (c,
+ reason != mhd_CONN_CLOSE_H2_CLOSE_SOFT);
+ return;
+ }
+#endif /* MHD_SUPPORT_HTTP2 */
+
reply_sending_aborted =
((mhd_HTTP_STAGE_HEADERS_SENDING <= c->stage)
&& (mhd_HTTP_STAGE_FULL_REPLY_SENT > c->stage));
@@ -799,6 +883,11 @@ mhd_conn_start_closing (struct MHD_Connection *restrict c,
sc = MHD_SC_CLIENT_SHUTDOWN_EARLY;
mhd_assert (! reply_sending_aborted);
break;
+ case mhd_CONN_CLOSE_H2_PREFACE_MISSING:
+ close_hard = true;
+ end_code = MHD_REQUEST_ENDED_HTTP_PROTOCOL_ERROR;
+ sc = MHD_SC_ALPN_H2_NO_PREFACE;
+ break;
case mhd_CONN_CLOSE_NO_POOL_MEM_FOR_REPLY:
close_hard = true;
end_code = (! c->stop_with_error || c->rq.too_large) ?
@@ -976,6 +1065,10 @@ mhd_conn_start_closing (struct MHD_Connection *restrict c,
end_code = MHD_REQUEST_ENDED_COMPLETED_OK;
break;
+#ifdef MHD_SUPPORT_HTTP2
+ case mhd_CONN_CLOSE_H2_CLOSE_SOFT:
+ case mhd_CONN_CLOSE_H2_CLOSE_HARD:
+#endif /* MHD_SUPPORT_HTTP2 */
default:
mhd_assert (0 && "Unreachable code");
mhd_UNREACHABLE ();
@@ -994,44 +1087,10 @@ mhd_conn_start_closing (struct MHD_Connection *restrict c,
}
else
#endif /* MHD_SUPPORT_UPGRADE */
- /* Make changes on the socket early to let the kernel and the remote
- * to process the changes in parallel. */
- if (close_hard)
- {
- /* Use abortive closing, send RST to remote to indicate a problem */
- (void) mhd_socket_set_hard_close (c->sk.fd);
- c->stage = mhd_HTTP_STAGE_PRE_CLOSING;
- c->event_loop_info = MHD_EVENT_LOOP_INFO_CLEANUP;
- }
- else
+ if (1)
{
- bool use_graceful_closing;
-
- mhd_assert (c->sk.state.rmt_shut_wr || \
- ! mhd_SOCKET_ERR_IS_HARD (c->sk.state.discnt_err));
-
- use_graceful_closing = ! c->sk.state.rmt_shut_wr;
- if (use_graceful_closing)
- {
-#ifdef MHD_SUPPORT_HTTPS
- if (mhd_C_HAS_TLS (c))
- {
- if ((0 != (((unsigned int) c->sk.ready)
- & mhd_SOCKET_NET_STATE_SEND_READY))
- || c->sk.props.is_nonblck)
- use_graceful_closing =
- (mhd_TLS_PROCED_FAILED != mhd_tls_conn_shutdown (c->tls));
- }
- else
-#endif /* MHD_SUPPORT_HTTPS */
- if (1)
- {
- use_graceful_closing = mhd_socket_shut_wr (c->sk.fd);
- if (use_graceful_closing)
- use_graceful_closing = (! c->sk.state.rmt_shut_wr); /* Skip as already closed */
- }
- }
- if (use_graceful_closing)
+ if (conn_start_socket_closing (c,
+ close_hard))
{
(void) 0; // TODO: start local lingering phase
c->stage = mhd_HTTP_STAGE_PRE_CLOSING; // TODO: start local lingering phase
diff --git a/src/mhd2/stream_funcs.h b/src/mhd2/stream_funcs.h
@@ -211,6 +211,11 @@ enum mhd_ConnCloseReason
*/
mhd_CONN_CLOSE_CLIENT_SHUTDOWN_EARLY
,
+ /**
+ * The client shut down send before complete request sent
+ */
+ mhd_CONN_CLOSE_H2_PREFACE_MISSING
+ ,
/* Hard problem while sending */
@@ -319,6 +324,19 @@ enum mhd_ConnCloseReason
*/
mhd_CONN_CLOSE_HTTP_COMPLETED
+#ifdef MHD_SUPPORT_HTTP2
+ ,
+ /**
+ * Graceful closing after finishing HTTP/2 communication.
+ * The HTTP/2 itself could be closed by error.
+ */
+ mhd_CONN_CLOSE_H2_CLOSE_SOFT
+ ,
+ /**
+ * Hard closing after finishing HTTP/2 communication.
+ */
+ mhd_CONN_CLOSE_H2_CLOSE_HARD
+#endif /* MHD_SUPPORT_HTTP2 */
};
@@ -435,6 +453,13 @@ MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_CSTR_ (3);
mhd_conn_start_closing ((c), mhd_CONN_CLOSE_UPGRADE, NULL)
#endif /* MHD_SUPPORT_UPGRADE */
+#ifdef MHD_SUPPORT_HTTP2
+# define mhd_conn_start_closing_h2_soft(c) \
+ mhd_conn_start_closing ((c), mhd_CONN_CLOSE_H2_CLOSE_SOFT, NULL)
+# define mhd_conn_start_closing_h2_hard(c) \
+ mhd_conn_start_closing ((c), mhd_CONN_CLOSE_H2_CLOSE_HARD, NULL)
+#endif /* MHD_SUPPORT_HTTP2 */
+
/**
* Perform first part of the initial connection cleanup.
diff --git a/src/mhd2/stream_process_reply.c b/src/mhd2/stream_process_reply.c
@@ -392,18 +392,9 @@ check_connection_reply (struct MHD_Connection *restrict c)
}
-/**
- * Produce time stamp.
- *
- * Result is NOT null-terminated.
- * Result is always 29 bytes long.
- *
- * @param[out] date where to write the time stamp, with
- * at least 29 bytes of savailable space.
- */
-static MHD_FN_PAR_NONNULL_ALL_
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_
MHD_FN_PAR_OUT_ (1) bool
-get_date_str (char *date)
+mhd_build_date_str (char date[MHD_FN_PAR_FIX_ARR_SIZE_ (29)])
{
static const char *const days[] = {
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
@@ -497,7 +488,7 @@ get_date_header (char *header)
header[3] = 'e';
header[4] = ':';
header[5] = ' ';
- if (! get_date_str (header + 6))
+ if (! mhd_build_date_str (header + 6))
{
header[0] = 0;
return false;
diff --git a/src/mhd2/stream_process_reply.h b/src/mhd2/stream_process_reply.h
@@ -63,7 +63,18 @@ MHD_INTERNAL void
mhd_stream_call_dcc_cleanup_if_needed (struct MHD_Connection *restrict c)
MHD_FN_PAR_NONNULL_ALL_;
-
+/**
+ * Produce time stamp.
+ *
+ * Result is NOT null-terminated.
+ * Result is always 29 bytes long.
+ *
+ * @param[out] date where to write the time stamp, with
+ * at least 29 bytes of available space.
+ */
+MHD_INTERNAL bool
+mhd_build_date_str (char date[MHD_FN_PAR_FIX_ARR_SIZE_ (29)])
+MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_OUT_ (1);
/**
* Allocate the connection's write buffer and fill it with all of the
* headers from the response.
diff --git a/src/mhd2/stream_process_request.c b/src/mhd2/stream_process_request.c
@@ -468,109 +468,99 @@
/**
- * Detect standard HTTP request method
- *
- * @param connection the connection to process
+ * Parse HTTP method string.
+ * @param len the length of the @a mtd string
+ * @param mtd the method string, does not need to be zero-terminated
+ * @return enum mhd_HTTP_Method value
*/
-static MHD_FN_PAR_NONNULL_ALL_ void
-parse_http_std_method (struct MHD_Connection *restrict connection)
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_
+MHD_FN_PAR_IN_SIZE_ (2,1)
+MHD_FN_PURE_ enum mhd_HTTP_Method
+mhd_parse_http_method (size_t len,
+ const char mtd[MHD_FN_PAR_DYN_ARR_SIZE_ (len)])
{
- const char *const restrict m = connection->rq.method.cstr; /**< short alias */
- const size_t len = connection->rq.method.len; /**< short alias */
- mhd_assert (NULL != m);
- mhd_assert (0 != len);
-
switch (len)
{
case 3: /* mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_GET) */
/* mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_PUT) */
mhd_assert (len == mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_GET));
mhd_assert (len == mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_PUT));
- if (0 == memcmp (m,
+ if (0 == memcmp (mtd,
MHD_HTTP_METHOD_STR_GET,
mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_GET)))
- {
- connection->rq.http_mthd = mhd_HTTP_METHOD_GET;
- return;
- }
- else if (0 == memcmp (m,
+ return mhd_HTTP_METHOD_GET;
+ else if (0 == memcmp (mtd,
MHD_HTTP_METHOD_STR_PUT,
mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_PUT)))
- {
- connection->rq.http_mthd = mhd_HTTP_METHOD_PUT;
- return;
- }
+ return mhd_HTTP_METHOD_PUT;
break;
case 4: /* mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_HEAD) */
/* mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_POST) */
mhd_assert (len == mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_HEAD));
mhd_assert (len == mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_POST));
- if (0 == memcmp (m,
+ if (0 == memcmp (mtd,
MHD_HTTP_METHOD_STR_HEAD,
mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_HEAD)))
- {
- connection->rq.http_mthd = mhd_HTTP_METHOD_HEAD;
- return;
- }
- else if (0 == memcmp (m,
+ return mhd_HTTP_METHOD_HEAD;
+ else if (0 == memcmp (mtd,
MHD_HTTP_METHOD_STR_POST,
mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_POST)))
- {
- connection->rq.http_mthd = mhd_HTTP_METHOD_POST;
- return;
- }
+ return mhd_HTTP_METHOD_POST;
break;
case 6: /* mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_DELETE) */
mhd_assert (len == mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_DELETE));
- if (0 == memcmp (m,
+ if (0 == memcmp (mtd,
MHD_HTTP_METHOD_STR_DELETE,
mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_DELETE)))
- {
- connection->rq.http_mthd = mhd_HTTP_METHOD_DELETE;
- return;
- }
+ return mhd_HTTP_METHOD_DELETE;
break;
case 7: /* mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_CONNECT) */
/* mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_OPTIONS) */
mhd_assert (len == mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_CONNECT));
mhd_assert (len == mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_OPTIONS));
- if (0 == memcmp (m,
+ if (0 == memcmp (mtd,
MHD_HTTP_METHOD_STR_CONNECT,
mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_CONNECT)))
- {
- connection->rq.http_mthd = mhd_HTTP_METHOD_CONNECT;
- return;
- }
- else if (0 == memcmp (m,
+ return mhd_HTTP_METHOD_CONNECT;
+ else if (0 == memcmp (mtd,
MHD_HTTP_METHOD_STR_OPTIONS,
mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_OPTIONS)))
- {
- connection->rq.http_mthd = mhd_HTTP_METHOD_OPTIONS;
- return;
- }
+ return mhd_HTTP_METHOD_OPTIONS;
break;
case 5: /* mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_TRACE) */
mhd_assert (len == mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_TRACE));
- if (0 == memcmp (m,
+ if (0 == memcmp (mtd,
MHD_HTTP_METHOD_STR_TRACE,
mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_TRACE)))
- {
- connection->rq.http_mthd = mhd_HTTP_METHOD_TRACE;
- return;
- }
+ return mhd_HTTP_METHOD_TRACE;
break;
case 1: /* mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_ASTERISK) */
mhd_assert (len == mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_ASTERISK));
- if ('*' == m[0])
- {
- connection->rq.http_mthd = mhd_HTTP_METHOD_ASTERISK;
- return;
- }
+ if ('*' == mtd[0])
+ return mhd_HTTP_METHOD_ASTERISK;
break;
default:
break; /* Handled after the "switch()" body */
}
- connection->rq.http_mthd = mhd_HTTP_METHOD_OTHER;
+ return mhd_HTTP_METHOD_OTHER;
+}
+
+
+/**
+ * Detect standard HTTP request method
+ *
+ * @param connection the connection to process
+ */
+static MHD_FN_PAR_NONNULL_ALL_ void
+parse_http_std_method (struct MHD_Connection *restrict connection)
+{
+ const char *const restrict m = connection->rq.method.cstr; /**< short alias */
+ const size_t len = connection->rq.method.len; /**< short alias */
+ mhd_assert (NULL != m);
+ mhd_assert (0 != len);
+
+ connection->rq.http_mthd = mhd_parse_http_method (len,
+ m);
}
diff --git a/src/mhd2/stream_process_request.h b/src/mhd2/stream_process_request.h
@@ -56,6 +56,7 @@
#include "sys_base_types.h"
#include "mhd_str_types.h"
+#include "http_method.h"
struct MHD_Connection; /* forward declaration */
struct MHD_UploadAction; /* forward declaration */
@@ -75,6 +76,17 @@ typedef bool
const struct MHD_StringNullable *restrict value);
/**
+ * Parse HTTP method string.
+ * @param len the length of the @a mtd string
+ * @param m the method string, does not need to be zero-terminated
+ * @return enum mhd_HTTP_Method value
+ */
+MHD_INTERNAL enum mhd_HTTP_Method
+mhd_parse_http_method (size_t len,
+ const char mtd[MHD_FN_PAR_DYN_ARR_SIZE_ (len)])
+MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_IN_SIZE_(2,1) MHD_FN_PURE_;
+
+/**
* Parse and unescape the arguments given by the client
* as part of the HTTP request URI.
*
diff --git a/src/mhd2/stream_process_states.c b/src/mhd2/stream_process_states.c
@@ -62,6 +62,11 @@
#include "mhd_connection.h"
#include "mhd_response.h"
+#include "mhd_comm_layer_state.h"
+#ifdef MHD_SUPPORT_HTTP2
+# include "h2/h2_comm.h"
+#endif
+
#include "stream_process_states.h"
#include "stream_funcs.h"
#include "stream_process_request.h"
@@ -87,6 +92,14 @@ mhd_conn_event_loop_state_update (struct MHD_Connection *restrict c)
(mhd_CONN_STATE_TCP_CONNECTED == c->conn_state));
#endif /* MHD_SUPPORT_HTTPS */
+#ifdef MHD_SUPPORT_HTTP2
+ if (mhd_C_IS_HTTP2 (c))
+ {
+ mhd_h2_conn_state_update (c);
+ return;
+ }
+#endif /* MHD_SUPPORT_HTTP2 */
+
switch (c->stage)
{
case mhd_HTTP_STAGE_INIT:
@@ -202,6 +215,47 @@ mhd_conn_event_loop_state_update (struct MHD_Connection *restrict c)
/**
+ * Process HTTP communication layer states and events
+ * @param c the connection to process
+ * @return #mhd_COMM_LAYER_PROCESSING if setting up HTTP connection is
+ * in progress,
+ * #mhd_COMM_LAYER_OK if HTTP communication can be performed now,
+ * #mhd_COMM_LAYER_BROKEN if connection is broken and should be closed.
+ */
+mhd_static_inline enum mhd_CommLayerState
+process_http_comm_layer (struct MHD_Connection *restrict c)
+{
+#ifdef MHD_SUPPORT_HTTP2
+ if (mhd_HTTP_LAYER_CONNECTED == c->h_layer.state)
+ return mhd_COMM_LAYER_OK; /* Shortcut for the most common case */
+
+ switch (c->h_layer.state)
+ {
+ case mhd_HTTP_LAYER_PREFACE:
+ return mhd_h2_process_preface (c);
+ case mhd_HTTP_LAYER_CONNECTED:
+ mhd_UNREACHABLE (); /* Handled above */
+ return mhd_COMM_LAYER_OK;
+ case mhd_HTTP_LAYER_CLOSING:
+ case mhd_HTTP_LAYER_CLOSED:
+ return mhd_COMM_LAYER_OK;
+ case mhd_HTTP_LAYER_BROKEN:
+ return mhd_COMM_LAYER_BROKEN;
+ default:
+ break;
+ }
+
+ mhd_UNREACHABLE ();
+ return mhd_COMM_LAYER_BROKEN;
+
+#else /* ! MHD_SUPPORT_HTTP2 */
+
+ return mhd_COMM_LAYER_OK;
+#endif /* ! MHD_SUPPORT_HTTP2 */
+}
+
+
+/**
* Finalise resuming of the connection
* @param c the connection to resume
*/
@@ -245,13 +299,16 @@ update_active_state (struct MHD_Connection *restrict c)
mhd_conn_event_loop_state_update (c);
- if (0 != (MHD_EVENT_LOOP_INFO_RECV & c->event_loop_info))
+ if (! mhd_C_IS_HTTP2 (c))
{
- /* Check whether the space is available to receive data */
- if (! mhd_stream_check_and_grow_read_buffer_space (c))
+ if (0 != (MHD_EVENT_LOOP_INFO_RECV & c->event_loop_info))
{
- mhd_assert (c->discard_request);
- return false;
+ /* Check whether the space is available to receive data */
+ if (! mhd_stream_check_and_grow_read_buffer_space (c))
+ {
+ mhd_assert (c->discard_request);
+ return false;
+ }
}
}
@@ -274,6 +331,33 @@ mhd_conn_process_data (struct MHD_Connection *restrict c)
struct MHD_Daemon *const d = c->daemon;
bool daemon_closing;
+ switch (process_http_comm_layer (c))
+ {
+ case mhd_COMM_LAYER_OK:
+ break; /* Process HTTP data */
+ case mhd_COMM_LAYER_PROCESSING:
+ return true; /* Too early for HTTP */
+ case mhd_COMM_LAYER_BROKEN:
+ mhd_assert (c->dbg.closing_started);
+ return false; /* Connection is broken */
+ default:
+ mhd_UNREACHABLE ();
+ return false;
+ }
+
+#ifdef MHD_SUPPORT_HTTP2
+ if (mhd_C_IS_HTTP2 (c))
+ {
+ if (! mhd_h2_conn_process_data (c))
+ return false;
+ update_active_state (c);
+ return true;
+ }
+#endif /* MHD_SUPPORT_HTTP2 */
+
+ mhd_assert (mhd_D_IS_HTTP1_ENABLED (d) || (! mhd_C_IS_HTTP2 (c)) || \
+ c->stop_with_error);
+
if (c->suspended)
return true;
diff --git a/src/mhd2/tls_gnu_daemon_data.h b/src/mhd2/tls_gnu_daemon_data.h
@@ -47,6 +47,8 @@
#include "mhd_sys_options.h"
+#include "sys_bool_type.h"
+
#ifndef MHD_SUPPORT_GNUTLS
#error This header can be used only if GnuTLS is enabled
#endif
@@ -74,6 +76,18 @@ struct mhd_TlsGnuDaemonData
* TLS priorities cache
*/
gnutls_priority_t pri_cache;
+
+#ifdef mhd_TLS_GNU_HAS_ALPN
+ /**
+ * Enabled protocols for ALPN
+ */
+ gnutls_datum_t alpn_prots[3];
+
+ /**
+ * Number of elements set in the @a alpn_prots
+ */
+ unsigned int num_alpn_prots;
+#endif /* mhd_TLS_GNU_HAS_ALPN */
};
#endif /* ! MHD_TLS_GNU_DAEMON_DATA_H */
diff --git a/src/mhd2/tls_gnu_funcs.c b/src/mhd2/tls_gnu_funcs.c
@@ -95,6 +95,32 @@ mhd_tls_gnu_debug_print (int level, const char *msg)
#endif /* mhd_USE_TLS_DEBUG_MESSAGES */
+#ifdef mhd_TLS_GNU_HAS_ALPN
+static const char mhd_alpn_str_http1_0[] = "http/1.0"; /* Registered value for HTTP/1.0 */
+static const char mhd_alpn_str_http1_1[] = "http/1.1"; /* Registered value for HTTP/1.1 */
+# ifdef MHD_SUPPORT_HTTP2
+static const char mhd_alpn_str_http2[] = "h2"; /* Registered value for HTTP/2 over TLS */
+# endif
+# if 0 /* Disabled code */
+static const char alpn_http_3[] = "h3"; /* Registered value for HTTP/3 */
+# endif
+static const gnutls_datum_t mhd_alpn_dat_http1_0 = {
+ (unsigned char *) mhd_DROP_CONST (mhd_alpn_str_http1_0),
+ mhd_SSTR_LEN (mhd_alpn_str_http1_0)
+};
+static const gnutls_datum_t mhd_alpn_dat_http1_1 = {
+ (unsigned char *) mhd_DROP_CONST (mhd_alpn_str_http1_1),
+ mhd_SSTR_LEN (mhd_alpn_str_http1_1)
+};
+# ifdef MHD_SUPPORT_HTTP2
+static const gnutls_datum_t mhd_alpn_dat_http2 = {
+ (unsigned char *) mhd_DROP_CONST (mhd_alpn_str_http2),
+ mhd_SSTR_LEN (mhd_alpn_str_http2)
+};
+# endif
+#endif /* mhd_TLS_GNU_HAS_ALPN */
+
+
/* ** Global initialisation / de-initialisation ** */
static bool gnutls_lib_inited = false;
@@ -439,6 +465,25 @@ mhd_tls_gnu_daemon_init3 (struct MHD_Daemon *restrict d,
if (NULL == d_tls)
return MHD_SC_DAEMON_MEM_ALLOC_FAILURE;
+#ifdef mhd_TLS_GNU_HAS_ALPN
+ // TODO: use daemon option to disable ALPN
+ // TODO: use daemon option to select protocols for ALPN
+ d_tls->num_alpn_prots = 0;
+
+#ifdef MHD_SUPPORT_HTTP2
+ if (1 /* enabled HTTP/2 ? */)
+ d_tls->alpn_prots[d_tls->num_alpn_prots++] = mhd_alpn_dat_http2;
+#endif /* MHD_SUPPORT_HTTP2 */
+
+ if (1 /* enabled HTTP/1.x ? */)
+ {
+ d_tls->alpn_prots[d_tls->num_alpn_prots++] = mhd_alpn_dat_http1_1;
+ d_tls->alpn_prots[d_tls->num_alpn_prots++] = mhd_alpn_dat_http1_0;
+ }
+
+ mhd_assert (mhd_ARR_NUM_ELEMS (d_tls->alpn_prots) >= d_tls->num_alpn_prots);
+#endif /* mhd_TLS_GNU_HAS_ALPN */
+
res = daemon_init_credentials (d,
d_tls,
s);
@@ -544,23 +589,10 @@ mhd_tls_gnu_conn_init (const struct mhd_TlsGnuDaemonData *restrict d_tls,
/* The basic TLS session properties has been set.
The rest is optional settings. */
#ifdef mhd_TLS_GNU_HAS_ALPN
- if (1)
+ if (0 != d_tls->num_alpn_prots)
{
- static const char alpn_http_1_0[] = "http/1.0"; /* Registered value for HTTP/1.0 */
- static const char alpn_http_1_1[] = "http/1.1"; /* Registered value for HTTP/1.1 */
-# if 0 /* Disabled code */
- static const char alpn_http_2[] = "h2"; /* Registered value for HTTP/2 over TLS */
- static const char alpn_http_3[] = "h3"; /* Registered value for HTTP/3 */
-# endif
- gnutls_datum_t prots[] = {
- { (unsigned char *) mhd_DROP_CONST (alpn_http_1_1),
- mhd_SSTR_LEN (alpn_http_1_1) }
- ,
- { (unsigned char *) mhd_DROP_CONST (alpn_http_1_0),
- mhd_SSTR_LEN (alpn_http_1_0) }
- };
- unsigned int alpn_flags;
int alpn_res;
+ unsigned int alpn_flags;
alpn_flags = 0;
# if 0
@@ -569,8 +601,8 @@ mhd_tls_gnu_conn_init (const struct mhd_TlsGnuDaemonData *restrict d_tls,
alpn_res =
gnutls_alpn_set_protocols (c_tls->sess,
- prots,
- (unsigned int) mhd_ARR_NUM_ELEMS (prots),
+ d_tls->alpn_prots,
+ d_tls->num_alpn_prots,
alpn_flags);
(void) alpn_res; /* Ignore any possible ALPN set errors */
}
@@ -850,3 +882,39 @@ mhd_tls_gnu_conn_get_tls_ver (struct mhd_TlsGnuConnData *restrict c_tls,
return true;
}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ enum mhd_TlsAlpnProt
+mhd_tls_gnu_conn_get_alpn_prot (struct mhd_TlsGnuConnData *restrict c_tls)
+{
+#ifdef mhd_TLS_GNU_HAS_ALPN
+ gnutls_datum_t sel_prot;
+
+ if (GNUTLS_E_SUCCESS !=
+ gnutls_alpn_get_selected_protocol (c_tls->sess, &sel_prot))
+ return mhd_TLS_ALPN_PROT_NOT_SELECTED;
+
+# ifdef MHD_SUPPORT_HTTP2
+ if ((sel_prot.size == mhd_alpn_dat_http2.size)
+ && (0 == memcmp (sel_prot.data,
+ mhd_alpn_dat_http2.data,
+ sel_prot.size)))
+ return mhd_TLS_ALPN_PROT_HTTP2;
+# endif /* MHD_SUPPORT_HTTP2 */
+
+ if ((sel_prot.size == mhd_alpn_dat_http1_1.size)
+ && (0 == memcmp (sel_prot.data,
+ mhd_alpn_dat_http1_1.data,
+ sel_prot.size)))
+ return mhd_TLS_ALPN_PROT_HTTP1_1;
+
+ if ((sel_prot.size == mhd_alpn_dat_http1_0.size)
+ && (0 == memcmp (sel_prot.data,
+ mhd_alpn_dat_http1_0.data,
+ sel_prot.size)))
+ return mhd_TLS_ALPN_PROT_HTTP1_0;
+
+#endif /* mhd_TLS_GNU_HAS_ALPN */
+
+ return mhd_TLS_ALPN_PROT_NOT_SELECTED;
+}
diff --git a/src/mhd2/tls_gnu_funcs.h b/src/mhd2/tls_gnu_funcs.h
@@ -297,4 +297,14 @@ mhd_tls_gnu_conn_get_tls_ver (struct mhd_TlsGnuConnData *restrict c_tls,
struct mhd_StctTlsVersion *restrict tls_ver_out)
MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_OUT_ (2);
+/**
+ * Get a protocol selected by ALPN
+ * @param c_tls the connection TLS handle
+ * @return the selected protocol code
+ */
+MHD_INTERNAL enum mhd_TlsAlpnProt
+mhd_tls_gnu_conn_get_alpn_prot (struct mhd_TlsGnuConnData *restrict c_tls)
+MHD_FN_PAR_NONNULL_ALL_;
+
+
#endif /* ! MHD_TLS_GNU_FUNCS_H */
diff --git a/src/mhd2/tls_multi_funcs.c b/src/mhd2/tls_multi_funcs.c
@@ -709,3 +709,31 @@ mhd_tls_multi_conn_get_tls_ver (struct mhd_TlsMultiConnData *restrict c_tls,
}
return false;
}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ enum mhd_TlsAlpnProt
+mhd_tls_multi_conn_get_alpn_prot (struct mhd_TlsMultiConnData *restrict c_tls)
+{
+ switch (c_tls->choice)
+ {
+#ifdef MHD_SUPPORT_GNUTLS
+ case mhd_TLS_MULTI_ROUTE_GNU:
+ return mhd_tls_gnu_conn_get_alpn_prot (c_tls->data.gnutls);
+#endif
+#ifdef MHD_SUPPORT_OPENSSL
+ case mhd_TLS_MULTI_ROUTE_OPEN:
+ return mhd_tls_open_conn_get_alpn_prot (c_tls->data.openssl);
+#endif
+#ifndef MHD_SUPPORT_GNUTLS
+ case MHD_TLS_BACKEND_GNUTLS:
+#endif /* ! MHD_SUPPORT_GNUTLS */
+#ifndef MHD_SUPPORT_OPENSSL
+ case MHD_TLS_BACKEND_OPENSSL:
+#endif /* ! MHD_SUPPORT_OPENSSL */
+ case mhd_TLS_MULTI_ROUTE_NONE:
+ default:
+ mhd_UNREACHABLE ();
+ break;
+ }
+ return mhd_TLS_ALPN_PROT_NOT_SELECTED;
+}
diff --git a/src/mhd2/tls_multi_funcs.h b/src/mhd2/tls_multi_funcs.h
@@ -276,4 +276,13 @@ mhd_tls_multi_conn_get_tls_ver (struct mhd_TlsMultiConnData *restrict c_tls,
struct mhd_StctTlsVersion *restrict tls_ver_out)
MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_OUT_ (2);
+/**
+ * Get a protocol selected by ALPN
+ * @param c_tls the connection TLS handle
+ * @return the selected protocol code
+ */
+MHD_INTERNAL enum mhd_TlsAlpnProt
+mhd_tls_multi_conn_get_alpn_prot (struct mhd_TlsMultiConnData *restrict c_tls)
+MHD_FN_PAR_NONNULL_ALL_;
+
#endif /* ! MHD_TLS_MULTI_FUNCS_H */
diff --git a/src/mhd2/tls_open_daemon_data.h b/src/mhd2/tls_open_daemon_data.h
@@ -69,6 +69,16 @@ struct mhd_TlsOpenDaemonData
* The server context
*/
SSL_CTX *ctx;
+
+ /**
+ * Enabled protocols for ALPN
+ */
+ const unsigned char *alpn_prots;
+
+ /**
+ * The size of the data pointed by @a alpn_prots
+ */
+ unsigned int alpn_prots_size;
};
#endif /* ! MHD_TLS_OPEN_DAEMON_DATA_H */
diff --git a/src/mhd2/tls_open_funcs.c b/src/mhd2/tls_open_funcs.c
@@ -364,16 +364,41 @@ daemon_deinit_lib_ctx (struct mhd_TlsOpenDaemonData *restrict d_tls)
}
-static const unsigned char alpn_codes_list[] = {
+#define mhd_ALPN_CODE_HTTP1_0 \
+ 'h', 't', 't', 'p', '/', '1', '.', '0' /* Registered value for HTTP/1.0 */
+#define mhd_ALPN_CODE_HTTP1_1 \
+ 'h', 't', 't', 'p', '/', '1', '.', '1' /* Registered value for HTTP/1.1 */
+#ifdef MHD_SUPPORT_HTTP2
+#define mhd_ALPN_CODE_HTTP2 \
+ 'h', '2' /* Registered value for HTTP/2 over TLS */
+#endif /* MHD_SUPPORT_HTTP2 */
#if 0 /* Disabled code */
- 2u, 'h', '3' /* Registered value for HTTP/3 */
+#define mhd_ALPN_CODE_HTTP3 \
+ 'h', '3' /* Registered value for HTTP/3 */
+#endif /* Disabled code */
+
+#ifdef MHD_SUPPORT_HTTP2
+static const char alpn_code_http2[] = { mhd_ALPN_CODE_HTTP2 };
+#endif /* MHD_SUPPORT_HTTP2 */
+static const char alpn_code_http1_1[] = { mhd_ALPN_CODE_HTTP1_1 };
+static const char alpn_code_http1_0[] = { mhd_ALPN_CODE_HTTP1_0 };
+
+#ifdef MHD_SUPPORT_HTTP2
+static const unsigned char alpn_list_http2_1x[] = {
+ sizeof(alpn_code_http2), mhd_ALPN_CODE_HTTP2
,
- 2u, 'h', '2' /* Registered value for HTTP/2 over TLS */
+ sizeof(alpn_code_http1_1), mhd_ALPN_CODE_HTTP1_1
,
-#endif /* Disabled code */
- 8u, 'h', 't', 't', 'p', '/', '1', '.', '1' /* Registered value for HTTP/1.1 */
+ sizeof(alpn_code_http1_0), mhd_ALPN_CODE_HTTP1_0
+};
+static const unsigned char alpn_list_http2_only[] = {
+ sizeof(alpn_code_http2), mhd_ALPN_CODE_HTTP2
+};
+#endif /* MHD_SUPPORT_HTTP2 */
+static const unsigned char alpn_list_http1x_only[] = {
+ sizeof(alpn_code_http1_1), mhd_ALPN_CODE_HTTP1_1
,
- 8u, 'h', 't', 't', 'p', '/', '1', '.', '0' /* Registered value for HTTP/1.0 */
+ sizeof(alpn_code_http1_0), mhd_ALPN_CODE_HTTP1_0
};
#ifndef OPENSSL_NO_NEXTPROTONEG
@@ -391,9 +416,11 @@ get_npn_list (SSL *sess,
unsigned int *outlen,
void *cls)
{
- (void) sess; (void) cls; /* Unused */
- *out = alpn_codes_list;
- *outlen = sizeof(alpn_codes_list);
+ struct mhd_TlsOpenDaemonData *const d_tls =
+ (struct mhd_TlsOpenDaemonData *) cls;
+ (void) sess; /* Unused */
+ *out = d_tls->alpn_prots;
+ *outlen = d_tls->alpn_prots_size;
return SSL_TLSEXT_ERR_OK;
}
@@ -419,14 +446,16 @@ select_alpn_prot (SSL *sess,
unsigned int inlen,
void *cls)
{
- (void) sess; (void) cls; /* Unused */
+ struct mhd_TlsOpenDaemonData *const d_tls =
+ (struct mhd_TlsOpenDaemonData *) cls;
+ (void) sess; /* Unused */
if (OPENSSL_NPN_NEGOTIATED ==
SSL_select_next_proto ((unsigned char **) mhd_DROP_CONST (out),
outlen,
in,
inlen,
- alpn_codes_list,
- sizeof(alpn_codes_list)))
+ d_tls->alpn_prots,
+ d_tls->alpn_prots_size))
return SSL_TLSEXT_ERR_OK; /* Success */
return SSL_TLSEXT_ERR_ALERT_FATAL; /* Failure */
@@ -514,14 +543,37 @@ daemon_init_ctx (struct MHD_Daemon *restrict d,
! 0);
/* ALPN and NPN */
- // TODO: use daemon option to disable them
+ // TODO: use daemon option to disable ALPN
+ // TODO: use daemon option to select protocols for ALPN
+#ifdef MHD_SUPPORT_HTTP2
+ if (1 /* enabled both HTTP/2 and HTTP/1.x */)
+ {
+ d_tls->alpn_prots = alpn_list_http2_1x;
+ d_tls->alpn_prots_size = sizeof(alpn_list_http2_1x);
+ }
+ else if (0 /* HTTP/2 only */)
+ {
+ d_tls->alpn_prots = alpn_list_http2_only;
+ d_tls->alpn_prots_size = sizeof(alpn_list_http2_only);
+ }
+ else
+#endif /* MHD_SUPPORT_HTTP2 */
+ if (1 /* HTTP/1.x only */)
+ {
+ d_tls->alpn_prots = alpn_list_http1x_only;
+ d_tls->alpn_prots_size = sizeof(alpn_list_http1x_only);
+ }
+ else
+ {
+ mhd_UNREACHABLE ();
+ }
SSL_CTX_set_alpn_select_cb (d_tls->ctx,
&select_alpn_prot,
- NULL);
+ d_tls);
#ifndef OPENSSL_NO_NEXTPROTONEG
SSL_CTX_set_next_protos_advertised_cb (d_tls->ctx,
&get_npn_list,
- NULL);
+ d_tls);
#endif /* ! OPENSSL_NO_NEXTPROTONEG */
return MHD_SC_OK;
@@ -1270,3 +1322,50 @@ mhd_tls_open_conn_get_tls_ver (struct mhd_TlsOpenConnData *restrict c_tls,
return true;
}
+
+
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ enum mhd_TlsAlpnProt
+mhd_tls_open_conn_get_alpn_prot (struct mhd_TlsOpenConnData *restrict c_tls)
+{
+ const unsigned char *sel_prot;
+ unsigned int sel_prot_len;
+
+ SSL_get0_alpn_selected (c_tls->sess,
+ &sel_prot,
+ &sel_prot_len);
+
+#ifndef OPENSSL_NO_NEXTPROTONEG
+ if ((NULL == sel_prot) ||
+ (0 == sel_prot_len))
+ {
+ SSL_get0_next_proto_negotiated (c_tls->sess,
+ &sel_prot,
+ &sel_prot_len);
+ }
+#endif /* ! OPENSSL_NO_NEXTPROTONEG */
+
+ if (NULL == sel_prot)
+ return mhd_TLS_ALPN_PROT_NOT_SELECTED;
+
+#ifdef MHD_SUPPORT_HTTP2
+ if ((sel_prot_len == sizeof(alpn_code_http2))
+ && (0 == memcmp (sel_prot,
+ alpn_code_http2,
+ sel_prot_len)))
+ return mhd_TLS_ALPN_PROT_HTTP2;
+#endif /* MHD_SUPPORT_HTTP2 */
+
+ if ((sel_prot_len == sizeof(alpn_code_http1_1))
+ && (0 == memcmp (sel_prot,
+ alpn_code_http1_1,
+ sel_prot_len)))
+ return mhd_TLS_ALPN_PROT_HTTP1_1;
+
+ if ((sel_prot_len == sizeof(alpn_code_http1_0))
+ && (0 == memcmp (sel_prot,
+ alpn_code_http1_0,
+ sel_prot_len)))
+ return mhd_TLS_ALPN_PROT_HTTP1_0;
+
+ return mhd_TLS_ALPN_PROT_NOT_SELECTED;
+}
diff --git a/src/mhd2/tls_open_funcs.h b/src/mhd2/tls_open_funcs.h
@@ -280,4 +280,13 @@ mhd_tls_open_conn_get_tls_ver (struct mhd_TlsOpenConnData *restrict c_tls,
struct mhd_StctTlsVersion *restrict tls_ver_out)
MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_OUT_ (2);
+/**
+ * Get a protocol selected by ALPN
+ * @param c_tls the connection TLS handle
+ * @return the selected protocol code
+ */
+MHD_INTERNAL enum mhd_TlsAlpnProt
+mhd_tls_open_conn_get_alpn_prot (struct mhd_TlsOpenConnData *restrict c_tls)
+MHD_FN_PAR_NONNULL_ALL_;
+
#endif /* ! MHD_TLS_OPEN_FUNCS_H */