7 n__http2__server_8c
gullradriel edited this page 2026-10-29 20:54:40 +01:00

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;
}