Table of Contents
- src/n_http2_server.c
- Structures
- Functions
- Defines
- Detailed Description
- Functions Documentation
- function srv_window_update
- function srv_stream_open
- function srv_stream_find
- function srv_stream_close
- function srv_send
- function srv_put_setting
- function srv_pump_one
- function srv_next_ready
- function srv_next_frame
- function srv_need
- function srv_headers_payload
- function srv_handle_frame
- function srv_finish_headers
- function srv_fill
- function srv_apply_window_update
- function srv_apply_settings
- function srv_apply_headers
- function srv_apply_data
- function n_http2_server_run
- function n_http2_server_rst_stream
- function n_http2_server_respond
- function n_http2_server_request_count
- function n_http2_server_new
- function n_http2_server_handshake
- function n_http2_server_goaway
- function n_http2_server_free
- function n_http2_server_error
- function h2buf_shift
- function h2buf_free
- function h2buf_append
- Macros Documentation
- Source code
src/n_http2_server.c
HTTP/2 server connection and stream state machine (see n_http2_server.h). More...
Structures
| Name | |
|---|---|
| struct | N_H2_SERVER server connection state |
| struct | H2_STREAM one client-initiated stream |
| struct | H2BUF a growable byte buffer for the read stream and the per-stream accumulators |
Functions
| Name | |
|---|---|
| int | srv_window_update(N_H2_SERVER * s, uint32_t sid, uint32_t inc) send a WINDOW_UPDATE for sid with the given increment |
| H2_STREAM * | srv_stream_open(N_H2_SERVER * s, uint32_t id) open a stream, or NULL when the table is full |
| H2_STREAM * | srv_stream_find(N_H2_SERVER * s, uint32_t id) the stream with this id, or NULL |
| void | srv_stream_close(H2_STREAM * st) release a stream slot and everything it owns |
| int | srv_send(N_H2_SERVER * s, int type, int flags, uint32_t sid, const unsigned char * payload, size_t len) write one frame (header then payload). |
| void | srv_put_setting(unsigned char * out, uint16_t id, uint32_t value) write one 6-byte SETTINGS entry (identifier then value) into out |
| int | srv_pump_one(N_H2_SERVER * s) read and handle one frame. |
| H2_STREAM * | srv_next_ready(N_H2_SERVER * s) the lowest-numbered stream whose request is complete and undelivered |
| int | srv_next_frame(N_H2_SERVER * s, N_H2_FRAME * fr, size_t * consumed) read the next complete frame into fr (aliasing the read buffer). |
| int | srv_need(N_H2_SERVER * s, size_t n) make sure at least n bytes are buffered. |
| int | srv_headers_payload(const N_H2_FRAME * fr, const unsigned char ** p_out, size_t * len_out) strip the pad-length prefix and priority fields of a HEADERS payload. |
| int | srv_handle_frame(N_H2_SERVER * s, const N_H2_FRAME * fr) handle one received frame. |
| int | srv_finish_headers(N_H2_SERVER * s) HPACK-decode the accumulated header block onto its stream. |
| int | srv_fill(N_H2_SERVER * s) pull one chunk from the peer into the read buffer. |
| int | srv_apply_window_update(N_H2_SERVER * s, const N_H2_FRAME * fr) add a WINDOW_UPDATE increment to the matching send window |
| int | srv_apply_settings(N_H2_SERVER * s, const N_H2_FRAME * fr) apply a peer SETTINGS payload to our send-side state |
| int | srv_apply_headers(N_H2_SERVER * s, const N_H2_FRAME * fr) take one HEADERS or CONTINUATION frame |
| int | srv_apply_data(N_H2_SERVER * s, const N_H2_FRAME * fr) take one DATA frame onto its stream and replenish the receive windows |
| int | n_http2_server_run(N_H2_SERVER * srv, n_h2_srv_request_cb cb, void * user_data) serve the connection until it ends |
| int | n_http2_server_rst_stream(N_H2_SERVER * srv, uint32_t stream_id, uint32_t error_code) abort one stream with RST_STREAM |
| int | n_http2_server_respond(N_H2_SERVER * srv, uint32_t stream_id, const N_H2_HEADER * headers, size_t nheaders, const unsigned char * body, size_t body_len) write a complete response on a stream (HEADERS then DATA, END_STREAM) |
| size_t | n_http2_server_request_count(const N_H2_SERVER * s) the number of requests delivered to the callback so far |
| N_H2_SERVER * | n_http2_server_new(void * io, n_h2_srv_read_fn rd, n_h2_srv_write_fn wr) create a server connection over the given I/O callbacks |
| int | n_http2_server_handshake(N_H2_SERVER * srv) read and validate the client connection preface, then send ours |
| int | n_http2_server_goaway(N_H2_SERVER * srv, uint32_t error_code) send GOAWAY, naming the last stream we processed |
| void | n_http2_server_free(N_H2_SERVER ** srv) free a server connection and NULL the caller's pointer |
| const char * | n_http2_server_error(const N_H2_SERVER * s) the reason the connection failed, or NULL when none was recorded |
| void | h2buf_shift(H2BUF * b, size_t n) drop the first n bytes of b |
| void | h2buf_free(H2BUF * b) release a buffer's storage |
| int | h2buf_append(H2BUF * b, const unsigned char * p, size_t n) append n bytes to b, growing it as needed. |
Defines
| Name | |
|---|---|
| H2_WIN_MAX the largest value a flow-control window may hold (RFC 7540 6.9.1) |
|
| H2_SRV_RECV_WINDOW the connection and stream receive window we advertise |
|
| H2_DEFAULT_WINDOW the initial connection flow-control window, fixed by RFC 7540 6.9.2 |
|
| H2_DEFAULT_MAX_FRAME the initial SETTINGS_MAX_FRAME_SIZE, fixed by RFC 7540 6.5.2 |
Detailed Description
HTTP/2 server connection and stream state machine (see n_http2_server.h).
Author: Castagnier Mickael
Version: 1.0
Date: 2026
Functions Documentation
function srv_window_update
static int srv_window_update(
N_H2_SERVER * s,
uint32_t sid,
uint32_t inc
)
send a WINDOW_UPDATE for sid with the given increment
< WINDOW_UPDATE
function srv_stream_open
static H2_STREAM * srv_stream_open(
N_H2_SERVER * s,
uint32_t id
)
open a stream, or NULL when the table is full
function srv_stream_find
static H2_STREAM * srv_stream_find(
N_H2_SERVER * s,
uint32_t id
)
the stream with this id, or NULL
function srv_stream_close
static void srv_stream_close(
H2_STREAM * st
)
release a stream slot and everything it owns
function srv_send
static int srv_send(
N_H2_SERVER * s,
int type,
int flags,
uint32_t sid,
const unsigned char * payload,
size_t len
)
write one frame (header then payload).
Returns 0 on success, -1 on a write failure (which also latches io_failed).
function srv_put_setting
static void srv_put_setting(
unsigned char * out,
uint16_t id,
uint32_t value
)
write one 6-byte SETTINGS entry (identifier then value) into out
function srv_pump_one
static int srv_pump_one(
N_H2_SERVER * s
)
read and handle one frame.
Returns 1 on a handled frame, 0 at end of stream, -1 on an error.
function srv_next_ready
static H2_STREAM * srv_next_ready(
N_H2_SERVER * s
)
the lowest-numbered stream whose request is complete and undelivered
function srv_next_frame
static int srv_next_frame(
N_H2_SERVER * s,
N_H2_FRAME * fr,
size_t * consumed
)
read the next complete frame into fr (aliasing the read buffer).
Returns 1 on a frame, 0 at end of stream, -1 on an error.
function srv_need
static int srv_need(
N_H2_SERVER * s,
size_t n
)
make sure at least n bytes are buffered.
Returns 1 on success, 0 on a short read at end of stream, -1 on an I/O error.
function srv_headers_payload
static int srv_headers_payload(
const N_H2_FRAME * fr,
const unsigned char ** p_out,
size_t * len_out
)
strip the pad-length prefix and priority fields of a HEADERS payload.
Returns 0 on success, -1 when the framing is inconsistent.
< DATA/HEADERS/PUSH_PROMISE: a pad-length prefix is present
< HEADERS: priority fields are present
function srv_handle_frame
static int srv_handle_frame(
N_H2_SERVER * s,
const N_H2_FRAME * fr
)
handle one received frame.
Returns 0 on success, -1 on a connection error (s->err set).
< CONTINUATION
< HEADERS
< CONTINUATION
< DATA
< SETTINGS
< SETTINGS/PING: acknowledgement
< PING
< SETTINGS/PING: acknowledgement
< PING
< SETTINGS/PING: acknowledgement
< WINDOW_UPDATE
< RST_STREAM
< GOAWAY
< PUSH_PROMISE
< PRIORITY
function srv_finish_headers
static int srv_finish_headers(
N_H2_SERVER * s
)
HPACK-decode the accumulated header block onto its stream.
Returns 0 on success, -1 on a connection error.
function srv_fill
static int srv_fill(
N_H2_SERVER * s
)
pull one chunk from the peer into the read buffer.
Returns 1 on bytes read, 0 on a clean end of stream, -1 on an I/O error.
function srv_apply_window_update
static int srv_apply_window_update(
N_H2_SERVER * s,
const N_H2_FRAME * fr
)
add a WINDOW_UPDATE increment to the matching send window
function srv_apply_settings
static int srv_apply_settings(
N_H2_SERVER * s,
const N_H2_FRAME * fr
)
apply a peer SETTINGS payload to our send-side state
< SETTINGS
< SETTINGS/PING: acknowledgement
function srv_apply_headers
static int srv_apply_headers(
N_H2_SERVER * s,
const N_H2_FRAME * fr
)
take one HEADERS or CONTINUATION frame
< HEADERS
< stream not processed
< DATA/HEADERS: last frame of the stream
< HEADERS/PUSH_PROMISE/CONTINUATION: header block complete
function srv_apply_data
static int srv_apply_data(
N_H2_SERVER * s,
const N_H2_FRAME * fr
)
take one DATA frame onto its stream and replenish the receive windows
< DATA/HEADERS/PUSH_PROMISE: a pad-length prefix is present
< processing capacity exceeded
< DATA/HEADERS: last frame of the stream
function n_http2_server_run
int n_http2_server_run(
N_H2_SERVER * srv,
n_h2_srv_request_cb cb,
void * user_data
)
serve the connection until it ends
Parameters:
- srv the handshaked server connection, not NULL
- cb the request callback, not NULL
- user_data opaque pointer handed to the callback
Return: 0 when the connection ended cleanly (peer close, GOAWAY, or a callback stop), -1 on a protocol or I/O error
Reads frames, reassembles requests, and calls cb once per complete request. Returns when the peer closes or sends GOAWAY, when the callback returns -1, or on an error.
< graceful shutdown
< graceful shutdown
function n_http2_server_rst_stream
int n_http2_server_rst_stream(
N_H2_SERVER * srv,
uint32_t stream_id,
uint32_t error_code
)
abort one stream with RST_STREAM
Parameters:
- srv the server connection, not NULL
- stream_id the stream to reset
- error_code an N_H2_ERR_* code
Return: 0 on success, -1 on error
< RST_STREAM
function n_http2_server_respond
int n_http2_server_respond(
N_H2_SERVER * srv,
uint32_t stream_id,
const N_H2_HEADER * headers,
size_t nheaders,
const unsigned char * body,
size_t body_len
)
write a complete response on a stream (HEADERS then DATA, END_STREAM)
Parameters:
- srv the server connection, not NULL
- stream_id the stream to answer
- headers the response header fields, :status first, not NULL
- nheaders number of header fields
- body the response body, or NULL for none
- body_len the body length
Return: 0 on success, -1 on a bad argument, a closed stream, or an I/O error
headers must start with the :status pseudo-header and hold no connection-specific field (RFC 7540 8.1.2.2 forbids them). The body is split into DATA frames at the peer's SETTINGS_MAX_FRAME_SIZE and paced by the stream and connection send windows, pumping incoming frames whenever a window is exhausted.
< HEADERS/PUSH_PROMISE/CONTINUATION: header block complete
< DATA/HEADERS: last frame of the stream
< HEADERS
< CONTINUATION
< DATA
< DATA/HEADERS: last frame of the stream
function n_http2_server_request_count
size_t n_http2_server_request_count(
const N_H2_SERVER * s
)
the number of requests delivered to the callback so far
function n_http2_server_new
N_H2_SERVER * n_http2_server_new(
void * io,
n_h2_srv_read_fn rd,
n_h2_srv_write_fn wr
)
create a server connection over the given I/O callbacks
Parameters:
- io opaque handle handed to the callbacks
- rd read callback, not NULL
- wr write callback, not NULL
Return: the new server, or NULL on a bad argument or an allocation failure
function n_http2_server_handshake
int n_http2_server_handshake(
N_H2_SERVER * srv
)
read and validate the client connection preface, then send ours
Parameters:
- srv the server connection, not NULL
Return: 0 on success, -1 on a bad preface, a protocol error, or an I/O error
Reads the 24-byte preface, requires the client's first frame to be SETTINGS (RFC 7540 3.5), acknowledges it, and sends our own SETTINGS plus the connection-level WINDOW_UPDATE that opens our receive window.
< SETTINGS
< SETTINGS
< SETTINGS/PING: acknowledgement
function n_http2_server_goaway
int n_http2_server_goaway(
N_H2_SERVER * srv,
uint32_t error_code
)
send GOAWAY, naming the last stream we processed
Parameters:
- srv the server connection, not NULL
- error_code an N_H2_ERR_* code
Return: 0 on success, -1 on error
< GOAWAY
function n_http2_server_free
void n_http2_server_free(
N_H2_SERVER ** srv
)
free a server connection and NULL the caller's pointer
function n_http2_server_error
const char * n_http2_server_error(
const N_H2_SERVER * s
)
the reason the connection failed, or NULL when none was recorded
function h2buf_shift
static void h2buf_shift(
H2BUF * b,
size_t n
)
drop the first n bytes of b
function h2buf_free
static void h2buf_free(
H2BUF * b
)
release a buffer's storage
function h2buf_append
static int h2buf_append(
H2BUF * b,
const unsigned char * p,
size_t n
)
append n bytes to b, growing it as needed.
Returns 0 on success.
Macros Documentation
define H2_WIN_MAX
#define H2_WIN_MAX 0x7FFFFFFF
the largest value a flow-control window may hold (RFC 7540 6.9.1)
define H2_SRV_RECV_WINDOW
#define H2_SRV_RECV_WINDOW H2_WIN_MAX
the connection and stream receive window we advertise
define H2_DEFAULT_WINDOW
#define H2_DEFAULT_WINDOW 65535
the initial connection flow-control window, fixed by RFC 7540 6.9.2
define H2_DEFAULT_MAX_FRAME
#define H2_DEFAULT_MAX_FRAME 16384
the initial SETTINGS_MAX_FRAME_SIZE, fixed by RFC 7540 6.5.2
Source code
/*
* Nilorea Library
* Copyright (C) 2005-2026 Castagnier Mickael
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "nilorea/n_http2_server.h"
#include "nilorea/n_common.h"
#include "nilorea/n_log.h"
#include <stdlib.h>
#include <string.h>
#define H2_WIN_MAX 0x7FFFFFFF
#define H2_SRV_RECV_WINDOW H2_WIN_MAX
#define H2_DEFAULT_WINDOW 65535
#define H2_DEFAULT_MAX_FRAME 16384
typedef struct H2BUF {
unsigned char* data;
size_t len;
size_t cap;
} H2BUF;
typedef struct H2_STREAM {
uint32_t id;
int in_use;
int headers_done;
int end_stream;
int delivered;
int reset;
N_H2_HEADER* hdrs;
size_t nhdrs;
H2BUF body;
int64_t send_win;
} H2_STREAM;
struct N_H2_SERVER {
void* io;
n_h2_srv_read_fn rd;
n_h2_srv_write_fn wr;
H2BUF rb;
N_H2_HPACK* dec;
H2BUF hblock;
uint32_t hblock_sid;
int hblock_end_stream;
H2_STREAM streams[N_H2_SRV_MAX_STREAMS];
int64_t send_win_conn;
int64_t peer_initial_win;
size_t peer_max_frame;
uint32_t last_stream_id;
int goaway_recv;
int settings_acked;
int eof;
int io_failed;
const char* err;
size_t nrequests;
};
/* ------------------------------------------------------------------ */
/* buffer helpers */
/* ------------------------------------------------------------------ */
static int h2buf_append(H2BUF* b, const unsigned char* p, size_t n) {
if (n == 0)
return 0;
if (b->len + n > b->cap) {
size_t nc = b->cap ? b->cap : 512;
unsigned char* nd;
while (b->len + n > nc)
nc *= 2;
nd = realloc(b->data, nc);
if (!nd)
return -1;
b->data = nd;
b->cap = nc;
}
memcpy(b->data + b->len, p, n);
b->len += n;
return 0;
}
static void h2buf_shift(H2BUF* b, size_t n) {
if (n >= b->len) {
b->len = 0;
return;
}
memmove(b->data, b->data + n, b->len - n);
b->len -= n;
}
static void h2buf_free(H2BUF* b) {
free(b->data);
b->data = NULL;
b->len = 0;
b->cap = 0;
}
/* ------------------------------------------------------------------ */
/* frame I/O */
/* ------------------------------------------------------------------ */
static int srv_send(N_H2_SERVER* s, int type, int flags, uint32_t sid, const unsigned char* payload, size_t len) {
unsigned char hdr[N_H2_FRAME_HEADER_LEN];
if (n_http2_frame_build_header(hdr, sizeof(hdr), (uint32_t)len, type, flags, sid) == 0) {
s->err = "HTTP/2: frame too large to build";
return -1;
}
if (s->wr(s->io, hdr, sizeof(hdr)) != (ssize_t)sizeof(hdr)) {
s->io_failed = 1;
s->err = "HTTP/2: write failed";
return -1;
}
if (len > 0 && s->wr(s->io, payload, len) != (ssize_t)len) {
s->io_failed = 1;
s->err = "HTTP/2: write failed";
return -1;
}
return 0;
}
static int srv_window_update(N_H2_SERVER* s, uint32_t sid, uint32_t inc) {
unsigned char p[4];
if (inc == 0)
return 0; /* a zero increment is a protocol error, and means nothing */
p[0] = (unsigned char)((inc >> 24) & 0x7F);
p[1] = (unsigned char)((inc >> 16) & 0xFF);
p[2] = (unsigned char)((inc >> 8) & 0xFF);
p[3] = (unsigned char)(inc & 0xFF);
return srv_send(s, N_H2_WINDOW_UPDATE, 0, sid, p, sizeof(p));
}
static void srv_put_setting(unsigned char* out, uint16_t id, uint32_t value) {
out[0] = (unsigned char)((id >> 8) & 0xFF);
out[1] = (unsigned char)(id & 0xFF);
out[2] = (unsigned char)((value >> 24) & 0xFF);
out[3] = (unsigned char)((value >> 16) & 0xFF);
out[4] = (unsigned char)((value >> 8) & 0xFF);
out[5] = (unsigned char)(value & 0xFF);
}
static int srv_fill(N_H2_SERVER* s) {
unsigned char tmp[N_H2_SRV_READ_CHUNK];
ssize_t n = s->rd(s->io, tmp, sizeof(tmp));
if (n == 0) {
s->eof = 1;
return 0;
}
if (n < 0) {
s->io_failed = 1;
s->err = "HTTP/2: read failed";
return -1;
}
if (h2buf_append(&s->rb, tmp, (size_t)n) != 0) {
s->err = "HTTP/2: out of memory";
return -1;
}
return 1;
}
static int srv_need(N_H2_SERVER* s, size_t n) {
while (s->rb.len < n) {
int r = srv_fill(s);
if (r <= 0)
return r;
}
return 1;
}
static int srv_next_frame(N_H2_SERVER* s, N_H2_FRAME* fr, size_t* consumed) {
for (;;) {
int pr = (s->rb.len > 0) ? n_http2_frame_parse(s->rb.data, s->rb.len, fr, consumed) : 0;
int r;
if (pr == 1)
return 1;
if (pr < 0) {
s->err = "HTTP/2: frame exceeds the maximum frame size";
return -1;
}
r = srv_fill(s);
if (r <= 0)
return r;
}
}
/* ------------------------------------------------------------------ */
/* stream table */
/* ------------------------------------------------------------------ */
static H2_STREAM* srv_stream_find(N_H2_SERVER* s, uint32_t id) {
size_t i;
for (i = 0; i < N_H2_SRV_MAX_STREAMS; i++)
if (s->streams[i].in_use && s->streams[i].id == id)
return &s->streams[i];
return NULL;
}
static H2_STREAM* srv_stream_open(N_H2_SERVER* s, uint32_t id) {
size_t i;
for (i = 0; i < N_H2_SRV_MAX_STREAMS; i++) {
H2_STREAM* st = &s->streams[i];
if (!st->in_use) {
memset(st, 0, sizeof(*st));
st->in_use = 1;
st->id = id;
st->send_win = s->peer_initial_win;
return st;
}
}
return NULL;
}
static void srv_stream_close(H2_STREAM* st) {
if (!st->in_use)
return;
if (st->hdrs) {
n_http2_hpack_headers_free(st->hdrs, st->nhdrs);
free(st->hdrs);
}
h2buf_free(&st->body);
memset(st, 0, sizeof(*st));
}
static H2_STREAM* srv_next_ready(N_H2_SERVER* s) {
H2_STREAM* best = NULL;
size_t i;
for (i = 0; i < N_H2_SRV_MAX_STREAMS; i++) {
H2_STREAM* st = &s->streams[i];
if (st->in_use && st->headers_done && st->end_stream && !st->delivered && !st->reset)
if (!best || st->id < best->id)
best = st;
}
return best;
}
/* ------------------------------------------------------------------ */
/* header block handling */
/* ------------------------------------------------------------------ */
static int srv_finish_headers(N_H2_SERVER* s) {
N_H2_HEADER tmp[N_H2_SRV_MAX_HEADERS];
size_t count = 0;
H2_STREAM* st;
if (n_http2_hpack_decode(s->dec, s->hblock.data, s->hblock.len, tmp, N_H2_SRV_MAX_HEADERS, &count) != 0) {
s->err = "HTTP/2: HPACK decode failed";
return -1;
}
st = srv_stream_find(s, s->hblock_sid);
s->hblock.len = 0;
s->hblock_sid = 0;
if (!st || st->reset) {
n_http2_hpack_headers_free(tmp, count); /* the stream went away: drop them, the
HPACK state is still updated, which is
what keeps the connection decodable */
return 0;
}
if (st->headers_done) {
/* a second block on the same stream is a trailer section: it carries no
request line, so it is accounted for and dropped */
n_http2_hpack_headers_free(tmp, count);
} else {
st->hdrs = malloc(count ? count * sizeof(N_H2_HEADER) : sizeof(N_H2_HEADER));
if (!st->hdrs) {
n_http2_hpack_headers_free(tmp, count);
s->err = "HTTP/2: out of memory";
return -1;
}
if (count)
memcpy(st->hdrs, tmp, count * sizeof(N_H2_HEADER)); /* ownership moves */
st->nhdrs = count;
st->headers_done = 1;
}
if (s->hblock_end_stream)
st->end_stream = 1;
return 0;
}
static int srv_headers_payload(const N_H2_FRAME* fr, const unsigned char** p_out, size_t* len_out) {
const unsigned char* p = fr->payload;
size_t plen = fr->length;
size_t padlen = 0;
if (fr->flags & N_H2_FLAG_PADDED) {
if (plen < 1)
return -1;
padlen = p[0];
p++;
plen--;
}
if (fr->flags & N_H2_FLAG_PRIORITY) {
if (plen < 5)
return -1;
p += 5;
plen -= 5;
}
if (padlen > plen)
return -1;
plen -= padlen;
*p_out = p;
*len_out = plen;
return 0;
}
/* ------------------------------------------------------------------ */
/* frame dispatch */
/* ------------------------------------------------------------------ */
static int srv_apply_settings(N_H2_SERVER* s, const N_H2_FRAME* fr) {
N_H2_SETTING set[32];
size_t n = 0, i;
if (n_http2_settings_parse(fr->payload, fr->length, set, 32, &n) != 0) {
s->err = "HTTP/2: malformed SETTINGS";
return -1;
}
for (i = 0; i < n; i++) {
if (set[i].id == N_H2_SETTINGS_INITIAL_WINDOW_SIZE) {
int64_t nv = (int64_t)set[i].value;
int64_t delta;
size_t k;
if (nv > H2_WIN_MAX) {
s->err = "HTTP/2: SETTINGS_INITIAL_WINDOW_SIZE out of range";
return -1;
}
delta = nv - s->peer_initial_win;
s->peer_initial_win = nv;
/* the change applies to every open stream (RFC 7540 6.9.2); the
connection window is not affected */
for (k = 0; k < N_H2_SRV_MAX_STREAMS; k++)
if (s->streams[k].in_use)
s->streams[k].send_win += delta;
} else if (set[i].id == N_H2_SETTINGS_MAX_FRAME_SIZE) {
size_t v = set[i].value;
if (v < H2_DEFAULT_MAX_FRAME)
v = H2_DEFAULT_MAX_FRAME;
if (v > N_H2_MAX_FRAME_PAYLOAD)
v = N_H2_MAX_FRAME_PAYLOAD;
s->peer_max_frame = v;
}
}
return srv_send(s, N_H2_SETTINGS, N_H2_FLAG_ACK, 0, NULL, 0);
}
static int srv_apply_window_update(N_H2_SERVER* s, const N_H2_FRAME* fr) {
uint32_t inc;
if (fr->length != 4) {
s->err = "HTTP/2: malformed WINDOW_UPDATE";
return -1;
}
inc = ((uint32_t)(fr->payload[0] & 0x7F) << 24) | ((uint32_t)fr->payload[1] << 16) | ((uint32_t)fr->payload[2] << 8) | (uint32_t)fr->payload[3];
if (fr->stream_id == 0) {
s->send_win_conn += (int64_t)inc;
if (s->send_win_conn > H2_WIN_MAX)
s->send_win_conn = H2_WIN_MAX;
} else {
H2_STREAM* st = srv_stream_find(s, fr->stream_id);
if (st) {
st->send_win += (int64_t)inc;
if (st->send_win > H2_WIN_MAX)
st->send_win = H2_WIN_MAX;
}
}
return 0;
}
static int srv_apply_data(N_H2_SERVER* s, const N_H2_FRAME* fr) {
const unsigned char* p = fr->payload;
size_t plen = fr->length;
size_t padlen = 0;
H2_STREAM* st = srv_stream_find(s, fr->stream_id);
if (fr->flags & N_H2_FLAG_PADDED) {
if (plen < 1) {
s->err = "HTTP/2: bad padded DATA";
return -1;
}
padlen = p[0];
p++;
plen--;
}
if (padlen > plen) {
s->err = "HTTP/2: DATA padding too large";
return -1;
}
plen -= padlen;
/* the whole frame payload counts against the connection window whatever
happens to the stream, so it is always given back: withholding it on the
last DATA frame of each request slowly starves a long-lived connection */
if (fr->length > 0 && srv_window_update(s, 0, fr->length) != 0)
return -1;
if (!st || st->reset) {
return 0; /* unknown or reset stream: the bytes are accounted for and dropped */
}
if (st->body.len + plen > N_H2_SRV_MAX_BODY) {
st->reset = 1;
return n_http2_server_rst_stream(s, st->id, N_H2_ERR_ENHANCE_YOUR_CALM);
}
if (plen > 0 && h2buf_append(&st->body, p, plen) != 0) {
s->err = "HTTP/2: out of memory";
return -1;
}
if (fr->flags & N_H2_FLAG_END_STREAM)
st->end_stream = 1;
else if (fr->length > 0 && srv_window_update(s, fr->stream_id, fr->length) != 0)
return -1;
return 0;
}
static int srv_apply_headers(N_H2_SERVER* s, const N_H2_FRAME* fr) {
const unsigned char* p = fr->payload;
size_t plen = fr->length;
if (fr->type == N_H2_HEADERS) {
const H2_STREAM* st;
if (fr->stream_id == 0 || (fr->stream_id & 1) == 0) {
s->err = "HTTP/2: HEADERS on an invalid stream id";
return -1;
}
if (srv_headers_payload(fr, &p, &plen) != 0) {
s->err = "HTTP/2: bad HEADERS padding or priority";
return -1;
}
st = srv_stream_find(s, fr->stream_id);
if (!st) {
if (fr->stream_id <= s->last_stream_id) {
s->err = "HTTP/2: HEADERS reopening a closed stream";
return -1;
}
st = srv_stream_open(s, fr->stream_id);
s->last_stream_id = fr->stream_id;
if (!st) {
/* over the concurrency limit: refuse the stream but keep the
connection, and still decode the block so HPACK stays in sync */
if (n_http2_server_rst_stream(s, fr->stream_id, N_H2_ERR_REFUSED_STREAM) != 0)
return -1;
}
}
s->hblock_sid = fr->stream_id;
s->hblock_end_stream = (fr->flags & N_H2_FLAG_END_STREAM) ? 1 : 0;
s->hblock.len = 0;
} else if (s->hblock_sid != fr->stream_id) {
/* a CONTINUATION must follow its HEADERS on the same stream with nothing
in between: anything else leaves the peer's HPACK state undefined */
s->err = "HTTP/2: CONTINUATION on the wrong stream";
return -1;
}
if (plen > 0 && h2buf_append(&s->hblock, p, plen) != 0) {
s->err = "HTTP/2: out of memory";
return -1;
}
if (fr->flags & N_H2_FLAG_END_HEADERS)
return srv_finish_headers(s);
return 0;
}
static int srv_handle_frame(N_H2_SERVER* s, const N_H2_FRAME* fr) {
/* while a header block is open the only legal frame is its CONTINUATION */
if (s->hblock_sid != 0 && !(fr->type == N_H2_CONTINUATION && fr->stream_id == s->hblock_sid)) {
s->err = "HTTP/2: frame interleaved inside a header block";
return -1;
}
switch (fr->type) {
case N_H2_HEADERS:
case N_H2_CONTINUATION:
return srv_apply_headers(s, fr);
case N_H2_DATA:
return srv_apply_data(s, fr);
case N_H2_SETTINGS:
if (fr->flags & N_H2_FLAG_ACK) {
if (!s->settings_acked) {
s->settings_acked = 1;
n_log(LOG_DEBUG, "HTTP/2: the peer acknowledged our SETTINGS");
}
return 0;
}
return srv_apply_settings(s, fr);
case N_H2_PING:
if (fr->flags & N_H2_FLAG_ACK)
return 0;
if (fr->length != 8) {
s->err = "HTTP/2: malformed PING";
return -1;
}
return srv_send(s, N_H2_PING, N_H2_FLAG_ACK, 0, fr->payload, fr->length);
case N_H2_WINDOW_UPDATE:
return srv_apply_window_update(s, fr);
case N_H2_RST_STREAM: {
H2_STREAM* st = srv_stream_find(s, fr->stream_id);
if (st)
st->reset = 1; /* the slot is freed by the serving loop, so a
response in flight never dangles */
return 0;
}
case N_H2_GOAWAY:
s->goaway_recv = 1;
return 0;
case N_H2_PUSH_PROMISE:
s->err = "HTTP/2: PUSH_PROMISE from a client";
return -1;
case N_H2_PRIORITY:
default:
return 0; /* PRIORITY carries no state here, unknown types are ignored */
}
}
static int srv_pump_one(N_H2_SERVER* s) {
N_H2_FRAME fr;
size_t consumed = 0;
int r = srv_next_frame(s, &fr, &consumed);
if (r <= 0)
return r;
r = srv_handle_frame(s, &fr);
h2buf_shift(&s->rb, consumed); /* the frame aliases the buffer: shift after use */
return (r == 0) ? 1 : -1;
}
/* ------------------------------------------------------------------ */
/* public API */
/* ------------------------------------------------------------------ */
N_H2_SERVER* n_http2_server_new(void* io, n_h2_srv_read_fn rd, n_h2_srv_write_fn wr) {
N_H2_SERVER* s;
__n_assert(rd, return NULL);
__n_assert(wr, return NULL);
s = calloc(1, sizeof(*s));
if (!s) {
n_log(LOG_ERR, "could not allocate an HTTP/2 server context");
return NULL;
}
s->io = io;
s->rd = rd;
s->wr = wr;
s->dec = n_http2_hpack_new(0);
if (!s->dec) {
free(s);
return NULL;
}
s->send_win_conn = H2_DEFAULT_WINDOW;
s->peer_initial_win = H2_DEFAULT_WINDOW;
s->peer_max_frame = H2_DEFAULT_MAX_FRAME;
return s;
}
void n_http2_server_free(N_H2_SERVER** srv) {
N_H2_SERVER* s;
size_t i;
if (!srv || !*srv)
return;
s = *srv;
for (i = 0; i < N_H2_SRV_MAX_STREAMS; i++)
srv_stream_close(&s->streams[i]);
n_http2_hpack_free(&s->dec);
h2buf_free(&s->rb);
h2buf_free(&s->hblock);
free(s);
*srv = NULL;
}
int n_http2_server_handshake(N_H2_SERVER* s) {
unsigned char settings[18];
N_H2_FRAME fr;
size_t consumed = 0;
int r;
__n_assert(s, return -1);
r = srv_need(s, strlen(N_H2_PREFACE));
if (r <= 0) {
s->err = s->err ? s->err : "HTTP/2: connection closed before the preface";
return -1;
}
if (memcmp(s->rb.data, N_H2_PREFACE, strlen(N_H2_PREFACE)) != 0) {
s->err = "HTTP/2: bad client connection preface";
return -1;
}
h2buf_shift(&s->rb, strlen(N_H2_PREFACE));
srv_put_setting(settings + 0, N_H2_SETTINGS_ENABLE_PUSH, 0); /* no server push */
srv_put_setting(settings + 6, N_H2_SETTINGS_MAX_CONCURRENT_STREAMS, N_H2_SRV_MAX_STREAMS); /* concurrency cap */
srv_put_setting(settings + 12, N_H2_SETTINGS_INITIAL_WINDOW_SIZE, H2_SRV_RECV_WINDOW); /* stream receive window */
if (srv_send(s, N_H2_SETTINGS, 0, 0, settings, sizeof(settings)) != 0)
return -1;
/* the connection receive window starts at 65535 whatever SETTINGS says, so it
is opened explicitly to the maximum a window may hold */
if (srv_window_update(s, 0, H2_SRV_RECV_WINDOW - H2_DEFAULT_WINDOW) != 0)
return -1;
/* the client's first frame must be its SETTINGS (RFC 7540 3.5) */
r = srv_next_frame(s, &fr, &consumed);
if (r <= 0) {
s->err = s->err ? s->err : "HTTP/2: connection closed before the client SETTINGS";
return -1;
}
if (fr.type != N_H2_SETTINGS || (fr.flags & N_H2_FLAG_ACK) || fr.stream_id != 0) {
s->err = "HTTP/2: the first client frame was not a SETTINGS";
h2buf_shift(&s->rb, consumed);
return -1;
}
r = srv_handle_frame(s, &fr);
h2buf_shift(&s->rb, consumed);
return r;
}
int n_http2_server_run(N_H2_SERVER* s, n_h2_srv_request_cb cb, void* user_data) {
__n_assert(s, return -1);
__n_assert(cb, return -1);
for (;;) {
H2_STREAM* st;
int r;
while ((st = srv_next_ready(s)) != NULL) {
N_H2_REQUEST req;
size_t i;
int stop;
memset(&req, 0, sizeof(req));
req.stream_id = st->id;
req.headers = st->hdrs;
req.nheaders = st->nhdrs;
for (i = 0; i < st->nhdrs; i++) {
const char* n = st->hdrs[i].name;
if (!n || n[0] != ':')
continue;
if (strcmp(n, ":method") == 0)
req.method = st->hdrs[i].value;
else if (strcmp(n, ":scheme") == 0)
req.scheme = st->hdrs[i].value;
else if (strcmp(n, ":authority") == 0)
req.authority = st->hdrs[i].value;
else if (strcmp(n, ":path") == 0)
req.path = st->hdrs[i].value;
}
req.body = st->body.len ? st->body.data : NULL;
req.body_len = st->body.len;
st->delivered = 1;
s->nrequests++;
stop = cb(user_data, s, &req);
/* the slot is released only now: the callback held pointers into it */
st = srv_stream_find(s, req.stream_id);
if (st)
srv_stream_close(st);
if (s->io_failed)
return -1;
if (stop != 0) {
n_http2_server_goaway(s, N_H2_ERR_NO_ERROR);
return 0;
}
}
if (s->goaway_recv || s->eof)
break;
r = srv_pump_one(s);
if (r < 0)
return -1;
if (r == 0)
break;
/* reclaim the slots of streams the client gave up on, so a long-lived
connection does not fill its stream table with cancelled requests */
{
size_t i;
for (i = 0; i < N_H2_SRV_MAX_STREAMS; i++)
if (s->streams[i].in_use && s->streams[i].reset)
srv_stream_close(&s->streams[i]);
}
}
n_http2_server_goaway(s, N_H2_ERR_NO_ERROR);
return 0;
}
int n_http2_server_respond(N_H2_SERVER* s, uint32_t stream_id, const N_H2_HEADER* headers, size_t nheaders, const unsigned char* body, size_t body_len) {
size_t cap = 64, size, off = 0, i;
unsigned char* block;
int first = 1, rc = 0;
H2_STREAM* st;
__n_assert(s, return -1);
__n_assert(headers, return -1);
st = srv_stream_find(s, stream_id);
if (!st || st->reset) {
s->err = "HTTP/2: response for a stream that is gone";
return -1;
}
for (i = 0; i < nheaders; i++)
cap += (headers[i].name ? strlen(headers[i].name) : 0) + (headers[i].value ? strlen(headers[i].value) : 0) + 8;
block = malloc(cap);
if (!block) {
s->err = "HTTP/2: out of memory";
return -1;
}
size = n_http2_hpack_encode(headers, nheaders, block, cap);
if (size == 0) {
free(block);
s->err = "HTTP/2: HPACK encode failed";
return -1;
}
do {
size_t chunk = size - off;
int flags = 0;
if (chunk > s->peer_max_frame)
chunk = s->peer_max_frame;
if (off + chunk >= size)
flags |= N_H2_FLAG_END_HEADERS;
if (first && body_len == 0)
flags |= N_H2_FLAG_END_STREAM;
if (srv_send(s, first ? N_H2_HEADERS : N_H2_CONTINUATION, flags, stream_id, block + off, chunk) != 0) {
rc = -1;
break;
}
off += chunk;
first = 0;
} while (off < size);
free(block);
if (rc != 0 || body_len == 0)
return rc;
off = 0;
while (off < body_len) {
int64_t win;
size_t chunk;
st = srv_stream_find(s, stream_id);
if (!st || st->reset) {
s->err = "HTTP/2: the stream was reset while the response was being written";
return -1;
}
win = (s->send_win_conn < st->send_win) ? s->send_win_conn : st->send_win;
if (win <= 0) {
/* both windows are the peer's to reopen: read until it does, which is
the difference between a server that streams a large body and one
that hangs on it */
int r = srv_pump_one(s);
if (r <= 0) {
s->err = s->err ? s->err : "HTTP/2: the peer never reopened the send window";
return -1;
}
continue;
}
chunk = body_len - off;
if (chunk > s->peer_max_frame)
chunk = s->peer_max_frame;
if ((int64_t)chunk > win)
chunk = (size_t)win;
if (srv_send(s, N_H2_DATA, (off + chunk >= body_len) ? N_H2_FLAG_END_STREAM : 0, stream_id, body + off, chunk) != 0)
return -1;
s->send_win_conn -= (int64_t)chunk;
st->send_win -= (int64_t)chunk;
off += chunk;
}
return 0;
}
int n_http2_server_rst_stream(N_H2_SERVER* s, uint32_t stream_id, uint32_t error_code) {
unsigned char p[4];
__n_assert(s, return -1);
p[0] = (unsigned char)((error_code >> 24) & 0xFF);
p[1] = (unsigned char)((error_code >> 16) & 0xFF);
p[2] = (unsigned char)((error_code >> 8) & 0xFF);
p[3] = (unsigned char)(error_code & 0xFF);
return srv_send(s, N_H2_RST_STREAM, 0, stream_id, p, sizeof(p));
}
int n_http2_server_goaway(N_H2_SERVER* s, uint32_t error_code) {
unsigned char p[8];
__n_assert(s, return -1);
if (s->io_failed)
return -1; /* the connection is already gone: do not log a second failure */
p[0] = (unsigned char)((s->last_stream_id >> 24) & 0x7F);
p[1] = (unsigned char)((s->last_stream_id >> 16) & 0xFF);
p[2] = (unsigned char)((s->last_stream_id >> 8) & 0xFF);
p[3] = (unsigned char)(s->last_stream_id & 0xFF);
p[4] = (unsigned char)((error_code >> 24) & 0xFF);
p[5] = (unsigned char)((error_code >> 16) & 0xFF);
p[6] = (unsigned char)((error_code >> 8) & 0xFF);
p[7] = (unsigned char)(error_code & 0xFF);
return srv_send(s, N_H2_GOAWAY, 0, 0, p, sizeof(p));
}
const char* n_http2_server_error(const N_H2_SERVER* s) {
return s ? s->err : NULL;
}
size_t n_http2_server_request_count(const N_H2_SERVER* s) {
return s ? s->nrequests : 0;
}
Modules
- AABB3D
- ACCEPT_POOL
- ALLEGRO4
- ALLEGRO5
- ANIM_LIB
- ASTAR
- ATLAS_LIB
- AVRO
- CLOCK_SYNC
- COMMONS
- CONFIGFILE
- CYPHER_BASE64
- CYPHER_VIGENERE
- DEAD_RECKONING
- ENUMS
- EXCEPTIONS
- GAME_ENV
- GIT
- GUI
- HASH_TABLE
- ISOMETRIC_ENGINE
- LIST
- LOG
- LOGNODUP
- LZ4
- NETWORKING
- NETWORK_MSG
- N_COOKIES
- N_DIFF
- N_DIGEST
- N_DISK_CACHE
- N_DNS
- N_ECC
- N_ENTROPY
- N_FILES
- N_FLUIDS
- N_GUI_COLOR
- N_GUI_COLORWHEEL
- N_GUI_CURVE
- N_GUI_GRID
- N_GUI_POPUP
- N_HEX
- N_HTTP3
- N_HTTP_COOKIE
- N_HTTP_FORM
- N_HTTP_MIME
- N_HTTP_REQ
- N_HTTP_RESP
- N_HTTP_ROUTER
- N_HTTP_SERVER
- N_ICC
- N_JSON
- N_KAFKA
- N_LINALG
- N_MODULE
- N_OPENSSL_COMPAT
- N_PRETTY
- N_PTH
- N_QUERY
- N_RANDOM
- N_SHA256
- N_STR
- N_TIFF
- N_TIME
- N_USER
- N_X509
- N_ZIP
- PARTICLES
- PCRE
- PHYSICS
- SIGNALS
- STACK
- THREADS
- TRAJECTORY
- TREE
- ZLIB