Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 96 additions & 1 deletion lib/includes/nghttp3/nghttp3.h
Original file line number Diff line number Diff line change
Expand Up @@ -2240,10 +2240,34 @@ typedef int (*nghttp3_recv_settings2)(nghttp3_conn *conn,
const nghttp3_proto_settings *settings,
void *conn_user_data);

/**
* @functypedef
*
* :type:`nghttp3_recv_datagram` is a callback function which is
* invoked when an HTTP/3 Datagram (see :rfc:`9297`) associated with a
* request stream identified by |stream_id| is received. |data|
* points to the HTTP Datagram payload (that is, the bytes that follow
* the Quarter Stream ID field), and its length is |datalen|. The
* payload may be zero length. This callback is invoked from
* `nghttp3_conn_read_datagram`.
*
* The implementation of this callback must return 0 if it succeeds.
* Returning :macro:`NGHTTP3_ERR_CALLBACK_FAILURE` will return to the
* caller immediately. Any values other than 0 is treated as
* :macro:`NGHTTP3_ERR_CALLBACK_FAILURE`.
*
* .. version-added:: 1.17.0
*/
typedef int (*nghttp3_recv_datagram)(nghttp3_conn *conn, int64_t stream_id,
const uint8_t *data, size_t datalen,
void *conn_user_data,
void *stream_user_data);

#define NGHTTP3_CALLBACKS_V1 1
#define NGHTTP3_CALLBACKS_V2 2
#define NGHTTP3_CALLBACKS_V3 3
#define NGHTTP3_CALLBACKS_VERSION NGHTTP3_CALLBACKS_V3
#define NGHTTP3_CALLBACKS_V4 4
#define NGHTTP3_CALLBACKS_VERSION NGHTTP3_CALLBACKS_V4

/**
* @struct
Expand Down Expand Up @@ -2375,6 +2399,15 @@ typedef struct nghttp3_callbacks {
* .. version-added:: 1.14.0
*/
nghttp3_recv_settings2 recv_settings2;
/* The following fields have been added since
NGHTTP3_CALLBACKS_V3. */
/**
* :member:`recv_datagram` is a callback function which is invoked
* when an HTTP/3 Datagram (see :rfc:`9297`) is received.
*
* .. version-added:: 1.17.0
*/
nghttp3_recv_datagram recv_datagram;
} nghttp3_callbacks;

/**
Expand Down Expand Up @@ -2562,6 +2595,68 @@ NGHTTP3_EXTERN nghttp3_ssize nghttp3_conn_read_stream2(nghttp3_conn *conn,
size_t srclen, int fin,
nghttp3_tstamp ts);

/**
* @function
*
* `nghttp3_conn_read_datagram` processes a single HTTP/3 Datagram (see
* :rfc:`9297`). |data| of length |datalen| is the payload of a QUIC
* DATAGRAM frame as received from the underlying QUIC stack; that is,
* the Quarter Stream ID field followed by the HTTP Datagram payload.
*
* The function decodes the Quarter Stream ID, maps it to the
* associated request stream, and, if the datagram is valid, invokes
* :type:`nghttp3_recv_datagram` with the request stream ID and the
* remaining payload.
*
* Because HTTP/3 Datagrams are unreliable, a datagram that cannot be
* delivered is silently dropped rather than treated as a connection
* error. This includes the cases where |datalen| is 0, the Quarter
* Stream ID field is truncated or maps to an invalid stream ID, the
* referenced request stream is not open, HTTP/3 Datagrams have not
* been enabled by both endpoints, or no :type:`nghttp3_recv_datagram`
* callback has been installed.
*
* This function returns 0 if it succeeds, or one of the following
* negative error codes:
*
* :macro:`NGHTTP3_ERR_CALLBACK_FAILURE`
* User callback failed.
*
* .. version-added:: 1.17.0
*/
NGHTTP3_EXTERN int nghttp3_conn_read_datagram(nghttp3_conn *conn,
const uint8_t *data,
size_t datalen);

/**
* @function
*
* `nghttp3_conn_write_datagram_prefix` writes the Quarter Stream ID
* field that prefixes an HTTP/3 Datagram (see :rfc:`9297`) for the
* request stream identified by |stream_id| into the buffer |dest| of
* length |destlen|. This is a framing helper: an application sends an
* HTTP/3 Datagram by passing the written prefix together with its own
* payload to the QUIC stack as the payload of a single QUIC DATAGRAM
* frame (e.g. as two iovecs to avoid copying the payload).
*
* |dest| should be able to hold at least 8 bytes, which is the maximum
* length of the Quarter Stream ID field.
*
* This function returns the number of bytes written to |dest|, or one
* of the following negative error codes:
*
* :macro:`NGHTTP3_ERR_INVALID_ARGUMENT`
* |stream_id| does not identify a client-initiated bidirectional
* (request) stream, or |destlen| is too small.
* :macro:`NGHTTP3_ERR_INVALID_STATE`
* HTTP/3 Datagrams have not been enabled by both endpoints; sending
* an HTTP/3 Datagram is not allowed.
*
* .. version-added:: 1.17.0
*/
NGHTTP3_EXTERN nghttp3_ssize nghttp3_conn_write_datagram_prefix(
nghttp3_conn *conn, int64_t stream_id, uint8_t *dest, size_t destlen);

/**
* @function
*
Expand Down
3 changes: 3 additions & 0 deletions lib/nghttp3_callbacks.c
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ size_t nghttp3_callbackslen_version(int callbacks_version) {
switch (callbacks_version) {
case NGHTTP3_CALLBACKS_VERSION:
return sizeof(callbacks);
case NGHTTP3_CALLBACKS_V3:
return offsetof(nghttp3_callbacks, recv_settings2) +
sizeof(callbacks.recv_settings2);
case NGHTTP3_CALLBACKS_V2:
return offsetof(nghttp3_callbacks, rand) + sizeof(callbacks.rand);
case NGHTTP3_CALLBACKS_V1:
Expand Down
83 changes: 83 additions & 0 deletions lib/nghttp3_conn.c
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,89 @@ nghttp3_ssize nghttp3_conn_read_stream2(nghttp3_conn *conn, int64_t stream_id,
ts);
}

/* conn_h3_datagram_negotiated returns nonzero if HTTP/3 Datagrams have
been enabled by both endpoints. RFC 9297, Section 2.1.1 requires the
H3_DATAGRAM setting to have been both sent and received with a value
of 1 before HTTP/3 Datagrams may be sent or received. */
static int conn_h3_datagram_negotiated(const nghttp3_conn *conn) {
return conn->local.settings.h3_datagram && conn->remote.settings.h3_datagram;
}

int nghttp3_conn_read_datagram(nghttp3_conn *conn, const uint8_t *data,
size_t datalen) {
nghttp3_stream *stream;
uint64_t qstream_id;
int64_t stream_id;
size_t prefixlen;
int rv;

/* HTTP/3 Datagrams are unreliable, so anything that cannot be
delivered is dropped rather than treated as a connection error. */
if (!conn_h3_datagram_negotiated(conn) || !conn->callbacks.recv_datagram ||
datalen == 0) {
return 0;
}

prefixlen = nghttp3_get_uvarintlen(data);
if (prefixlen > datalen) {
return 0;
}

nghttp3_get_uvarint(&qstream_id, data);

/* Quarter Stream ID is the request stream ID divided by four; reject
values that would map to a stream ID greater than the maximum. */
if (qstream_id > NGHTTP3_MAX_VARINT >> 2) {
return 0;
}

stream_id = (int64_t)(qstream_id << 2);

stream = nghttp3_conn_find_stream(conn, stream_id);
if (stream == NULL) {
return 0;
}

rv = conn->callbacks.recv_datagram(conn, stream_id, data + prefixlen,
datalen - prefixlen, conn->user_data,
stream->user_data);
if (rv != 0) {
return NGHTTP3_ERR_CALLBACK_FAILURE;
}

return 0;
}

nghttp3_ssize nghttp3_conn_write_datagram_prefix(nghttp3_conn *conn,
int64_t stream_id,
uint8_t *dest,
size_t destlen) {
uint64_t qstream_id;
size_t prefixlen;

assert(stream_id >= 0);
assert(stream_id <= (int64_t)NGHTTP3_MAX_VARINT);

if (!nghttp3_client_stream_bidi(stream_id)) {
return NGHTTP3_ERR_INVALID_ARGUMENT;
}

if (!conn_h3_datagram_negotiated(conn)) {
return NGHTTP3_ERR_INVALID_STATE;
}

qstream_id = (uint64_t)stream_id >> 2;

prefixlen = nghttp3_put_uvarintlen(qstream_id);
if (prefixlen > destlen) {
return NGHTTP3_ERR_INVALID_ARGUMENT;
}

nghttp3_put_uvarint(dest, qstream_id);

return (nghttp3_ssize)prefixlen;
}

static nghttp3_ssize conn_read_type(nghttp3_conn *conn, nghttp3_stream *stream,
const uint8_t *src, size_t srclen,
int fin) {
Expand Down
Loading