@@ -15,69 +15,41 @@ static inline uint32_t read4(const uint8_t *p) { return p[0] << 24 | p[1] << 16
static inline uint32_t read3(const uint8_t *p) { return read4(p) >> 8; }


// pack several bytes into an array, return it and its length.
/* construct a stack-allocated array of bytes in place. expands to (pointer, length) */
#define PACK(...) (char *) (uint8_t []) { __VA_ARGS__ }, sizeof((uint8_t []) { __VA_ARGS__ })
#define I8(x) x
#define I16(x) x >> 8, x
#define I24(x) x >> 16, x >> 8, x
#define I32(x) x >> 24, x >> 16, x >> 8, x


/* fake http "request" sent by the client at the beginning of a connection */
static const struct cno_buffer_t CNO_PREFACE = { "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", 24 };

/* standard-defined pre-initial-SETTINGS values */
static const struct cno_settings_t CNO_SETTINGS_STANDARD = {{{ 4096, 1, -1, 65536, 16384, -1 }}};

/* actual values to send in the first SETTINGS frame */
static const struct cno_settings_t CNO_SETTINGS_INITIAL = {{{ 4096, 1, 1024, 65536, 65536, -1 }}};


/* check whether a stream is initiated by us, not our peer.
*
* even-numbered streams are initiated by the server.
*/
static int cno_stream_is_local(const struct cno_connection_t *conn, uint32_t id)
{
return id % 2 == conn->client;
}


/* deallocate a stream object *without firing a callback*.
/* each stream carries a single request-response pair, plus push promises.
*
* this is a fallback routine. since creating a stream always fires on_stream_start,
* it should only be used if the state doesn't matter anymore.
*/
static void cno_stream_destroy(struct cno_connection_t *conn, struct cno_stream_t *stream)
{
conn->stream_count[cno_stream_is_local(conn, stream->id)]--;

struct cno_stream_t **s = &conn->streams[stream->id % CNO_STREAM_BUCKETS];
while (*s != stream) s = &(*s)->next;
*s = stream->next;

free(stream);
}


/* deallocate a stream object.
* local:: 1 (== CNO_LOCAL) if this side is initiating the stream.
*
* fires: on_stream_end.
*/
static int cno_stream_destroy_clean(struct cno_connection_t *conn, struct cno_stream_t *stream)
{
uint32_t id = stream->id;
cno_stream_destroy(conn, stream);
return CNO_FIRE(conn, on_stream_end, id);
}


/* allocate resources for a new stream.
* throws::
*
* third argument should be 1 (== CNO_LOCAL) iff we're initiating this stream.
* INVALID_STREAM if stream id is unacceptable.
* WOULD_BLOCK if this side has initiated too many streams.
* TRANSPORT if the other side has initiated too many streams.
* NO_MEMORY streams are heap-allocated
*
* fires: on_stream_start.
* throws:
* INVALID_STREAM if third argument is invalid.
* INVALID_STREAM if stream id is lower than that of some existing stream.
* WOULD_BLOCK if we've already initiated too many streams.
* TRANSPORT if the peer has gone over our limit on concurrent streams.
* NO_MEMORY
*/
static struct cno_stream_t * cno_stream_new(struct cno_connection_t *conn, uint32_t id, int local)
{
@@ -92,7 +64,6 @@ static struct cno_stream_t * cno_stream_new(struct cno_connection_t *conn, uint3
: CNO_ERROR_NULL(TRANSPORT, "peer exceeded stream limit");

struct cno_stream_t *stream = malloc(sizeof(struct cno_stream_t));

if (!stream)
return CNO_ERROR_NULL(NO_MEMORY, "%zu bytes", sizeof(struct cno_stream_t));

@@ -117,10 +88,6 @@ static struct cno_stream_t * cno_stream_new(struct cno_connection_t *conn, uint3
}


/* retrieve a stream object by id, or NULL if not found.
*
* (that's a simple closed-hash prime-order map with integer keys.)
*/
static struct cno_stream_t * cno_stream_find(const struct cno_connection_t *conn, uint32_t id)
{
struct cno_stream_t *s = conn->streams[id % CNO_STREAM_BUCKETS];
@@ -129,31 +96,35 @@ static struct cno_stream_t * cno_stream_find(const struct cno_connection_t *conn
}


/* forbid sending any more data over this stream.
*
* inbound data will still be accepted. abort the stream (see cno_write_reset)
* if that data is undesired.
*
* fires: on_stream_end if the stream is closed in the other direction as well.
*/
static int cno_stream_close(struct cno_connection_t *conn, struct cno_stream_t *stream)
static void cno_stream_free(struct cno_connection_t *conn, struct cno_stream_t *stream)
{
if (!(stream->accept &= ~CNO_ACCEPT_OUTBOUND))
return cno_stream_destroy_clean(conn, stream);
conn->stream_count[cno_stream_is_local(conn, stream->id)]--;

return CNO_OK;
struct cno_stream_t **s = &conn->streams[stream->id % CNO_STREAM_BUCKETS];
while (*s != stream) s = &(*s)->next;
*s = stream->next;

free(stream);
}


/* send a single frame. no flow control accounting is done.
static int cno_stream_rst(struct cno_connection_t *conn, struct cno_stream_t *stream)
{
uint32_t id = stream->id;
cno_stream_free(conn, stream);
return CNO_FIRE(conn, on_stream_end, id);
}


/* send a single non-flow-controlled frame, splitting DATA/HEADERS if they are too big.
*
* throws::
*
* ASSERTION if a non-DATA frame exceeds the size limit (how)
* ASSERTION if a padded frame exceeds the size limit (FIXME)
*
* fires: on_write, on_frame_send.
* throws:
* ASSERTION if a non-DATA frame exceeds the size limit (how)
* ASSERTION if a padded frame exceeds the size limit (FIXME)
*/
static int cno_frame_write(const struct cno_connection_t *conn,
const struct cno_stream_t *stream,
const struct cno_frame_t *frame)
{
size_t length = frame->payload.size;
@@ -186,7 +157,7 @@ static int cno_frame_write(const struct cno_connection_t *conn,
part.payload.size = limit;

while (length > limit) {
if (cno_frame_write(conn, stream, &part))
if (cno_frame_write(conn, &part))
return CNO_ERROR_UP();

length -= limit;
@@ -199,67 +170,67 @@ static int cno_frame_write(const struct cno_connection_t *conn,

part.flags |= frame->flags & carry_on_last;
part.payload.size = length;
return cno_frame_write(conn, stream, &part);
return cno_frame_write(conn, &part);
}


static int cno_frame_write_settings(const struct cno_connection_t *conn,
const struct cno_settings_t *previous,
const struct cno_settings_t *current)
{
size_t i = 0;
uint8_t payload[CNO_SETTINGS_UNDEFINED - 1][6], (*ptr)[6] = payload;
const uint32_t *ax = previous->array;
const uint32_t *bx = current->array;

for (; ++i < CNO_SETTINGS_UNDEFINED; ++ax, ++bx) {
if (*ax != *bx) {
struct cno_buffer_t buf = { PACK(I16(i), I32(*bx)) };
memcpy(ptr++, buf.data, buf.size);
}
}

struct cno_frame_t frame = { CNO_FRAME_SETTINGS, 0, 0, { (char *) payload, (ptr - payload) * 6 } };
return cno_frame_write(conn, &frame);
}


/* send an RST_STREAM frame (and close the stream for both input and output.)
*
* fires: on_write, on_frame_send, maybe on_stream_end.
* throws: ASSERTION if attempted to reset stream 0
*/
static int cno_frame_write_rst_stream(struct cno_connection_t *conn,
struct cno_stream_t *stream,
uint32_t /* enum CNO_RST_STREAM_CODE */ code)
{
struct cno_frame_t error = { CNO_FRAME_RST_STREAM, 0, stream->id, { PACK(I32(code)) } };

if (cno_frame_write(conn, stream, &error))
if (cno_frame_write(conn, &error))
return CNO_ERROR_UP();

// a stream in closed state can still accept headers/data.
// headers will be decompressed; other than that, everything is ignored.
stream->closed = 1;
stream->accept &= ~CNO_ACCEPT_OUTBOUND;

if (!(stream->accept & CNO_ACCEPT_HEADERS))
// since headers were already handled, this stream can be safely destroyed.
return cno_stream_destroy_clean(conn, stream);
// i sure hope there are no trailers, though.
return cno_stream_rst(conn, stream);

// still have to decompress headers to maintain shared compression state.
// FIXME headers may never arrive if the peer receives RST_STREAM before sending them.
stream->accept &= ~CNO_ACCEPT_OUTBOUND;
stream->accept |= CNO_ACCEPT_NOP_HEADERS;
return CNO_OK;
}


/* send a GOAWAY frame.
*
* fires: on_write, on_frame_send.
*
* TODO: send some opaque data?
*/
static int cno_frame_write_goaway(struct cno_connection_t *conn, uint32_t /* enum CNO_RST_STREAM_CODE */ code)
static int cno_frame_write_goaway(struct cno_connection_t *conn,
uint32_t /* enum CNO_RST_STREAM_CODE */ code)
{
uint32_t last = conn->last_stream[CNO_REMOTE];
struct cno_frame_t error = { CNO_FRAME_GOAWAY, 0, 0, { PACK(I32(last), I32(code)) } };
return cno_frame_write(conn, NULL, &error);
return cno_frame_write(conn, &error);
}


/* close a connection due to an error.
*
* fires: on_write, on_frame_send.
* throws: TRANSPORT, always.
*/
#define cno_frame_write_error(conn, type, ...) \
(cno_frame_write_goaway(conn, type) ? CNO_ERROR_UP() \
: CNO_ERROR(TRANSPORT, __VA_ARGS__))
/* shut down a connection and *then* throw a TRANSPORT error. */
#define cno_frame_write_error(conn, type, ...) \
(cno_frame_write_goaway(conn, type) ? CNO_ERROR_UP() : CNO_ERROR(TRANSPORT, __VA_ARGS__))


/* handle a frame that carries an END_STREAM flag.
*
* fires:
* on_message_end
* on_stream_end if the stream is now closed in both directions.
*/
static int cno_frame_handle_end_stream(struct cno_connection_t *conn,
struct cno_stream_t *stream)
{
@@ -272,15 +243,10 @@ static int cno_frame_handle_end_stream(struct cno_connection_t *conn,
if (half_open)
return CNO_OK;

return cno_stream_destroy_clean(conn, stream);
return cno_stream_rst(conn, stream);
}


/* ensure that an HTTP 2 message satisfies the rules regarding headers and their arragment;
* if it does, pass it on to the application layer; otherwise, reset the stream.
*
* emits: on_message_start, on_message_trail, on_push_promise, maybe on_message_end/on_stream_end
*/
static int cno_frame_handle_message(struct cno_connection_t *conn,
struct cno_stream_t *stream,
struct cno_frame_t *frame,
@@ -356,6 +322,10 @@ static int cno_frame_handle_message(struct cno_connection_t *conn,
}

if (stream->accept & CNO_ACCEPT_TRAILERS) {
if (!(frame->flags & CNO_FLAG_END_STREAM))
// there is no data after trailers.
goto invalid_message;

stream->accept &= ~CNO_ACCEPT_INBOUND;

if (CNO_FIRE(conn, on_message_trail, stream->id, msg))
@@ -374,8 +344,9 @@ static int cno_frame_handle_message(struct cno_connection_t *conn,
stream->accept &= ~CNO_ACCEPT_HEADERS;
stream->accept |= CNO_ACCEPT_TRAILERS | CNO_ACCEPT_DATA;

if (stream->closed)
return cno_stream_destroy_clean(conn, stream);
if (stream->accept & CNO_ACCEPT_NOP_HEADERS)
// hpack compression is now in sync, there's no use for this stream anymore.
return cno_stream_rst(conn, stream);

if (CNO_FIRE(conn, on_message_start, stream->id, msg))
return CNO_ERROR_UP();
@@ -390,14 +361,6 @@ static int cno_frame_handle_message(struct cno_connection_t *conn,
}


/* handle a frame that carries an END_HEADERS flag.
*
* fires:
* on_message_push if the frame is PUSH_PROMISE.
* on_message_start if the frame is HEADERS and the stream was not reset.
*
* throws: TRANSPORT if header block is corrupt.
*/
static int cno_frame_handle_end_headers(struct cno_connection_t *conn,
struct cno_stream_t *stream,
struct cno_frame_t *frame)
@@ -424,15 +387,11 @@ static int cno_frame_handle_end_headers(struct cno_connection_t *conn,
}


/* trim padding, if any, off a frame.
* throws: PROTOCOL if the padding spec is malformed.
*/
static int cno_frame_handle_padding(struct cno_connection_t *conn, struct cno_frame_t *frame)
{
if (frame->flags & CNO_FLAG_PADDED) {
if (frame->payload.size == 0)
return cno_frame_write_error(conn, CNO_RST_FRAME_SIZE_ERROR, "no padding");
return cno_frame_write_error(conn, CNO_RST_FRAME_SIZE_ERROR, "no padding found");

size_t padding = read1((const uint8_t *) frame->payload.data) + 1;

@@ -447,13 +406,6 @@ static int cno_frame_handle_padding(struct cno_connection_t *conn, struct cno_fr
}


/* handle a HEADERS frame.
*
* fires:
* on_message_start if the frame has END_HEADERS set.
* on_message_end if the frame has END_STREAM set.
* on_stream_end if the frame has END_STREAM and the stream was unidirectional.
*/
static int cno_frame_handle_headers(struct cno_connection_t *conn,
struct cno_stream_t *stream,
struct cno_frame_t *frame)
@@ -463,10 +415,10 @@ static int cno_frame_handle_headers(struct cno_connection_t *conn,

if (stream == NULL) {
if (conn->client)
// servers cannot initiate streams.
return cno_frame_write_error(conn, CNO_RST_PROTOCOL_ERROR, "unexpected HEADERS");

stream = cno_stream_new(conn, frame->stream, CNO_REMOTE);

if (stream == NULL)
return CNO_ERROR_UP();

@@ -486,7 +438,7 @@ static int cno_frame_handle_headers(struct cno_connection_t *conn,
if (frame->payload.size < 5)
return cno_frame_write_error(conn, CNO_RST_FRAME_SIZE_ERROR, "no priority spec");

// can do nothing with this data without access to the transport layer :((
// TODO implement stream prioritization
frame->payload.data += 5;
frame->payload.size -= 5;
}
@@ -506,10 +458,6 @@ static int cno_frame_handle_headers(struct cno_connection_t *conn,
}


/* handle a PUSH_PROMISE frame.
*
* fires: on_message_push if the frame has END_HEADERS set.
*/
static int cno_frame_handle_push_promise(struct cno_connection_t *conn,
struct cno_stream_t *stream,
struct cno_frame_t *frame)
@@ -524,45 +472,36 @@ static int cno_frame_handle_push_promise(struct cno_connection_t *conn,
return cno_frame_write_error(conn, CNO_RST_FRAME_SIZE_ERROR, "PUSH_PROMISE too short");

uint32_t promised = read4((const uint8_t *) frame->payload.data);
frame->payload.data += 4;
frame->payload.size -= 4;

struct cno_stream_t *pushed = cno_stream_new(conn, promised, CNO_REMOTE);

if (pushed == NULL)
struct cno_stream_t *child = cno_stream_new(conn, promised, CNO_REMOTE);
if (child == NULL)
return CNO_ERROR_UP();

pushed->accept = CNO_ACCEPT_HEADERS;
child->accept = CNO_ACCEPT_HEADERS;
conn->continued_flags = 0; // PUSH_PROMISE cannot have END_STREAM
conn->continued_stream = stream->id;
conn->continued_promise = promised;

if (cno_buffer_dyn_concat(&conn->continued, frame->payload))
// another compression error. we're screwed.
if (cno_buffer_dyn_concat(&conn->continued, (struct cno_buffer_t) { frame->payload.data + 4,
frame->payload.size - 4 }))
// a compression error. unrecoverable.
return CNO_ERROR_UP();

if (frame->flags & CNO_FLAG_END_HEADERS)
return cno_frame_handle_end_headers(conn, stream, frame);

return CNO_OK;
}


/* handle a CONTINUATION.
*
* fires: same as *_headers or *_push_promise, depending on what is continued.
*/
static int cno_frame_handle_continuation(struct cno_connection_t *conn,
struct cno_stream_t *stream,
struct cno_frame_t *frame)
{
if (!stream || !conn->continued_stream)
return cno_frame_write_error(conn, CNO_RST_PROTOCOL_ERROR, "unexpected CONTINUATION");

// we don't actually count CONTINUATIONs, but this is a good estimate. especially
// if the other side decides to send the message as a bunch of small frames.
// we don't actually count CONTINUATIONs, but this is an ok estimate.
size_t max_buf_size = (CNO_MAX_CONTINUATIONS + 1) * conn->settings[CNO_LOCAL].max_frame_size;

if (frame->payload.size + conn->continued.size > max_buf_size)
// finally a chance to use that error code.
return cno_frame_write_error(conn, CNO_RST_ENHANCE_YOUR_CALM, "too many HEADERS");
@@ -571,34 +510,25 @@ static int cno_frame_handle_continuation(struct cno_connection_t *conn,
return CNO_ERROR_UP();

frame->flags |= conn->continued_flags;

if (frame->flags & CNO_FLAG_END_HEADERS)
return cno_frame_handle_end_headers(conn, stream, frame);

return CNO_OK;
}


/* ignore non-HEADERS frames on reset streams, as the spec requires. unfortunately,
* these are indistinguishable from streams that were never opened, but hey, what
* can i do, keep a set of uint32_t-s? memory doesn't grow on trees, you know. */
static int cno_frame_invalid_stream(struct cno_connection_t *conn, struct cno_frame_t *frame)
static int cno_frame_handle_invalid_stream(struct cno_connection_t *conn,
struct cno_frame_t *frame)
{
if (0 < frame->stream && frame->stream <= conn->last_stream[cno_stream_is_local(conn, frame->stream)])
return CNO_OK;

// definitely idle
return cno_frame_write_error(conn, CNO_RST_PROTOCOL_ERROR, "invalid stream");
if (!frame->stream || frame->stream > conn->last_stream[cno_stream_is_local(conn, frame->stream)])
// definitely idle
return cno_frame_write_error(conn, CNO_RST_PROTOCOL_ERROR, "invalid stream");
return CNO_OK;
}


/* handle a DATA frame. this includes sending a WINDOW_UPDATE to compensate for its size.
*
* fires:
* on_message_data
* on_message_end if carries END_STREAM
* on_stream_end if carries END_STREAM and the stream is (was) unidirectional
*/
static int cno_frame_handle_data(struct cno_connection_t *conn,
struct cno_stream_t *stream,
struct cno_frame_t *frame)
@@ -609,17 +539,15 @@ static int cno_frame_handle_data(struct cno_connection_t *conn,
return CNO_ERROR_UP();

if (length) {
// XXX do frames sent to closed streams count against connection-wide flow control?
// TODO allow manual flow control
struct cno_frame_t update = { CNO_FRAME_WINDOW_UPDATE, 0, 0, { PACK(I32(length)) } };

if (cno_frame_write(conn, NULL, &update))
if (cno_frame_write(conn, &update))
return CNO_ERROR_UP();

// TODO update window_recv, should it ever become used.
}

if (!stream)
return cno_frame_invalid_stream(conn, frame);
return cno_frame_handle_invalid_stream(conn, frame);

if (!(stream->accept & CNO_ACCEPT_DATA))
return cno_frame_write_rst_stream(conn, stream, CNO_RST_STREAM_CLOSED);
@@ -634,14 +562,10 @@ static int cno_frame_handle_data(struct cno_connection_t *conn,
return CNO_OK;

struct cno_frame_t update = { CNO_FRAME_WINDOW_UPDATE, 0, stream->id, { PACK(I32(length)) } };
return cno_frame_write(conn, stream, &update);
return cno_frame_write(conn, &update);
}


/* handle a PING frame, maybe by sending a pong.
*
* fires: on_pong technically, never, as there is no way to send a ping for now.
*/
static int cno_frame_handle_ping(struct cno_connection_t *conn,
struct cno_stream_t *stream __attribute__((unused)),
struct cno_frame_t *frame)
@@ -656,16 +580,10 @@ static int cno_frame_handle_ping(struct cno_connection_t *conn,
return CNO_FIRE(conn, on_pong, frame->payload.data);

struct cno_frame_t response = { CNO_FRAME_PING, CNO_FLAG_ACK, 0, frame->payload };
return cno_frame_write(conn, NULL, &response);
return cno_frame_write(conn, &response);
}


/* handle a GOAWAY frame, at least when the error code is nonzero.
* when it is, i have no idea what to do.
*
* fires: nothing.
* throws: TRANSPORT if this frame is due to an error on our part.
*/
static int cno_frame_handle_goaway(struct cno_connection_t *conn,
struct cno_stream_t *stream __attribute__((unused)),
struct cno_frame_t *frame)
@@ -677,40 +595,27 @@ static int cno_frame_handle_goaway(struct cno_connection_t *conn,
return cno_frame_write_error(conn, CNO_RST_FRAME_SIZE_ERROR, "bad GOAWAY");

const uint32_t error = read4((const uint8_t *) frame->payload.data + 4);

if (error != CNO_RST_NO_ERROR)
return CNO_ERROR(TRANSPORT, "disconnected with error %u", error);

return CNO_OK;
}


/* handle an RST_STREAM frame by destroying an appropriate object.
*
* fires: on_stream_end.
*/
static int cno_frame_handle_rst_stream(struct cno_connection_t *conn,
struct cno_stream_t *stream,
struct cno_frame_t *frame)
{
if (!stream)
return cno_frame_invalid_stream(conn, frame);
return cno_frame_handle_invalid_stream(conn, frame);

if (frame->payload.size != 4)
return cno_frame_write_error(conn, CNO_RST_FRAME_SIZE_ERROR, "bad RST_STREAM");

// TODO parse the error code and do something with it.
return cno_stream_destroy_clean(conn, stream);
return cno_stream_rst(conn, stream);
}


/* do not handle a PRIORITY frame. prioritizing streams requires knowledge about
* the transport layer (essentially, a normal write buffer should be transformed into
* a priority queue of frames to send), which we do not have. we simply assume that
* everything sent through on_write is transmitted instantly, so there's no contention.
*
* fires: also nothing.
*/
static int cno_frame_handle_priority(struct cno_connection_t *conn,
struct cno_stream_t *stream __attribute__((unused)),
struct cno_frame_t *frame)
@@ -721,25 +626,23 @@ static int cno_frame_handle_priority(struct cno_connection_t *conn,
if (frame->payload.size != 5)
return cno_frame_write_error(conn, CNO_RST_FRAME_SIZE_ERROR, "bad PRIORITY");

// TODO implement prioritization
return CNO_OK;
}


/* handle a SETTINGS frame and apply everything it says to our connection.
*
* fires: on_flow_increase if the frame carries a new flow control window size.
*/
static int cno_frame_handle_settings(struct cno_connection_t *conn,
struct cno_stream_t *stream __attribute__((unused)),
struct cno_frame_t *frame)
{
if (frame->stream)
return cno_frame_write_error(conn, CNO_RST_PROTOCOL_ERROR, "SETTINGS on a stream");

if (frame->flags & CNO_FLAG_ACK)
return frame->payload.size == 0
? CNO_OK
: cno_frame_write_error(conn, CNO_RST_FRAME_SIZE_ERROR, "bad SETTINGS ack");
if (frame->flags & CNO_FLAG_ACK) {
if (frame->payload.size)
return cno_frame_write_error(conn, CNO_RST_FRAME_SIZE_ERROR, "bad SETTINGS ack");
return CNO_OK;
}

if (frame->payload.size % 6)
return cno_frame_write_error(conn, CNO_RST_FRAME_SIZE_ERROR, "bad SETTINGS");
@@ -771,14 +674,10 @@ static int cno_frame_handle_settings(struct cno_connection_t *conn,
// TODO update stream flow control windows.

struct cno_frame_t ack = { CNO_FRAME_SETTINGS, CNO_FLAG_ACK, 0, CNO_BUFFER_EMPTY };
return cno_frame_write(conn, NULL, &ack);
return cno_frame_write(conn, &ack);
}


/* handle a WINDOW_UPDATE frame. if there is data to send, the app should retry `write_data`.
*
* fires: on_flow_increase.
*/
static int cno_frame_handle_window_update(struct cno_connection_t *conn,
struct cno_stream_t *stream,
struct cno_frame_t *frame)
@@ -799,7 +698,7 @@ static int cno_frame_handle_window_update(struct cno_connection_t *conn,
// TODO maybe emit an event for each stream with nonzero window instead?
} else {
if (stream == NULL)
return cno_frame_invalid_stream(conn, frame);
return cno_frame_handle_invalid_stream(conn, frame);

if (stream->window_send > 0x7fffffff - (int32_t) increment)
return cno_frame_write_rst_stream(conn, stream, CNO_RST_FLOW_CONTROL_ERROR);
@@ -846,49 +745,7 @@ static int cno_frame_handle(struct cno_connection_t *conn, struct cno_frame_t *f
}


/* send a SETTINGS frame containing the delta from one configuration to the other.
*
* fires:
* on_write
* on_frame_send
*/
static int cno_settings_diff(const struct cno_connection_t *conn,
const struct cno_settings_t *a,
const struct cno_settings_t *b)
{
size_t i = 0;
uint8_t payload[CNO_SETTINGS_UNDEFINED - 1][6], (*ptr)[6] = payload;
const uint32_t *ax = a->array;
const uint32_t *bx = b->array;

for (; ++i < CNO_SETTINGS_UNDEFINED; ++ax, ++bx) {
if (*ax != *bx) {
struct cno_buffer_t buf = { PACK(I16(i), I32(*bx)) };
memcpy(ptr++, buf.data, buf.size);
}
}

struct cno_frame_t frame = { CNO_FRAME_SETTINGS, 0, 0, { (char *) payload, (ptr - payload) * 6 } };
return cno_frame_write(conn, NULL, &frame);
}


/* copy the current local settings into a provided struct. modify that struct
* then call cno_settings_apply to change the configuration. */
void cno_settings_copy(struct cno_connection_t *conn, struct cno_settings_t *target)
{
memcpy(target, &conn->settings[CNO_LOCAL], sizeof(*target));
}


/* check the new config and apply it to the local end of the connection.
* (there's also the remote version of the configuration which we can't affect. each
* side has to conform to the other's config, e.g. a server should only send push
* promises if the client allows it even if the server itself has set enable_push to 0.)
*
* throws: ASSERTION if one of the parameters was set incorrectly.
*/
int cno_settings_apply(struct cno_connection_t *conn, const struct cno_settings_t *settings)
int cno_connection_set_config(struct cno_connection_t *conn, const struct cno_settings_t *settings)
{
if (settings->enable_push != 0 && settings->enable_push != 1)
return CNO_ERROR(ASSERTION, "enable_push neither 0 nor 1");
@@ -898,7 +755,7 @@ int cno_settings_apply(struct cno_connection_t *conn, const struct cno_settings_

if (conn->state != CNO_CONNECTION_INIT && cno_connection_is_http2(conn))
// If not yet in HTTP2 mode, `cno_connection_upgrade` will send the SETTINGS frame.
if (cno_settings_diff(conn, &conn->settings[CNO_LOCAL], settings))
if (cno_frame_write_settings(conn, &conn->settings[CNO_LOCAL], settings))
return CNO_ERROR_UP();

memcpy(&conn->settings[CNO_LOCAL], settings, sizeof(*settings));
@@ -934,12 +791,10 @@ void cno_connection_reset(struct cno_connection_t *conn)

for (int i = 0; i < CNO_STREAM_BUCKETS; i++)
while (conn->streams[i])
cno_stream_destroy(conn, conn->streams[i]);
cno_stream_free(conn, conn->streams[i]);
}


/* return 1 iff the requests/responses will be sent as http 2. note that there
* may still be an http 1 request in the buffer. */
int cno_connection_is_http2(struct cno_connection_t *conn)
{
return conn->state != CNO_CONNECTION_HTTP1_INIT &&
@@ -949,20 +804,15 @@ int cno_connection_is_http2(struct cno_connection_t *conn)
}


/* switch the outbound communication to http 2 mode. */
static int cno_connection_upgrade(struct cno_connection_t *conn)
{
if (conn->client && CNO_FIRE(conn, on_write, CNO_PREFACE.data, CNO_PREFACE.size))
return CNO_ERROR_UP();

return cno_settings_diff(conn, &CNO_SETTINGS_STANDARD, &conn->settings[CNO_LOCAL]);
return cno_frame_write_settings(conn, &CNO_SETTINGS_STANDARD, &conn->settings[CNO_LOCAL]);
}


/* consume as much of the buffered data as possible. yep, this is a dfa!
*
* fires: EVERYTHING.
*/
static int cno_connection_proceed(struct cno_connection_t *conn)
{
while (1) switch (conn->state) {
@@ -998,7 +848,7 @@ static int cno_connection_proceed(struct cno_connection_t *conn)
conn->last_stream[CNO_REMOTE] = 0;
conn->last_stream[CNO_LOCAL] = 0;

if (cno_stream_destroy_clean(conn, cno_stream_find(conn, 1)))
if (cno_stream_rst(conn, cno_stream_find(conn, 1)))
return CNO_ERROR_UP();
break;
}
@@ -1260,7 +1110,7 @@ int cno_connection_lost(struct cno_connection_t *conn)

for (s = &conn->streams[0]; s != &conn->streams[CNO_STREAM_BUCKETS]; s++)
while (*s)
if (cno_stream_destroy_clean(conn, *s))
if (cno_stream_rst(conn, *s))
return CNO_ERROR_UP();

cno_buffer_dyn_clear(&conn->buffer);
@@ -1277,12 +1127,7 @@ uint32_t cno_stream_next_id(struct cno_connection_t *conn)
return 1;

uint32_t last = conn->last_stream[CNO_LOCAL];

if (last || !conn->client)
return last + 2;

// client-initiated streams are odd-numbered, but right now, `last` is 0.
return 1;
return conn->client && !last ? 1 : last + 2;
}


@@ -1292,10 +1137,8 @@ int cno_write_reset(struct cno_connection_t *conn, uint32_t stream, enum CNO_RST
return CNO_ERROR(DISCONNECT, "HTTP/1.x connection rejected");

struct cno_stream_t *obj = cno_stream_find(conn, stream);

if (!obj)
return CNO_OK; // assume it has already been reset

return cno_frame_write_rst_stream(conn, obj, code);
}

@@ -1342,7 +1185,7 @@ int cno_write_push(struct cno_connection_t *conn, uint32_t stream, const struct

frame.payload = payload.as_static;

if (cno_frame_write(conn, streamobj, &frame))
if (cno_frame_write(conn, &frame))
goto payload_generation_error;

cno_buffer_dyn_clear(&payload);
@@ -1483,16 +1326,20 @@ int cno_write_message(struct cno_connection_t *conn, uint32_t stream, const stru

frame.payload = payload.as_static;

if (cno_frame_write(conn, streamobj, &frame))
if (cno_frame_write(conn, &frame))
goto payload_generation_error;

cno_buffer_dyn_clear(&payload);

if (final)
return cno_stream_close(conn, streamobj);
if (!final) {
streamobj->accept &= ~CNO_ACCEPT_WRITE_HEADERS;
streamobj->accept |= CNO_ACCEPT_WRITE_DATA;
return CNO_OK;
}

if (!(streamobj->accept &= ~CNO_ACCEPT_OUTBOUND))
return cno_stream_rst(conn, streamobj);

streamobj->accept &= ~CNO_ACCEPT_WRITE_HEADERS;
streamobj->accept |= CNO_ACCEPT_WRITE_DATA;
return CNO_OK;

payload_generation_error:
@@ -1561,14 +1408,16 @@ int cno_write_data(struct cno_connection_t *conn, uint32_t stream, const char *d

struct cno_frame_t frame = { CNO_FRAME_DATA, final ? CNO_FLAG_END_STREAM : 0, stream, { data, length } };

if (cno_frame_write(conn, streamobj, &frame))
if (cno_frame_write(conn, &frame))
return CNO_ERROR_UP();

conn->window_send -= length;
streamobj->window_send -= length;

if (final && cno_stream_close(conn, streamobj))
return CNO_ERROR_UP();
if (final)
if (!(streamobj->accept &= ~CNO_ACCEPT_OUTBOUND))
if (cno_stream_rst(conn, streamobj))
return CNO_ERROR_UP();

return length;
}
@@ -64,7 +64,8 @@ enum CNO_STREAM_ACCEPT
CNO_ACCEPT_DATA = 0x02,
CNO_ACCEPT_PUSH = 0x04,
CNO_ACCEPT_TRAILERS = 0x08,
CNO_ACCEPT_INBOUND = 0x0F,
CNO_ACCEPT_NOP_HEADERS = 0x10, // decompress but discard header blocks on reset streams
CNO_ACCEPT_INBOUND = 0x1F,
CNO_ACCEPT_WRITE_PUSH = 0x20,
CNO_ACCEPT_WRITE_HEADERS = 0x40,
CNO_ACCEPT_WRITE_DATA = 0x80,
@@ -154,7 +155,6 @@ struct cno_stream_t
uint32_t id;
int32_t window_recv;
int32_t window_send;
uint8_t closed;
uint8_t /* enum CNO_STREAM_ACCEPT */ accept;
};

@@ -275,11 +275,10 @@ int cno_connection_stop (struct cno_connection_t *);
* `cno_write_push` does nothing if this returns false. On the other hand,
* you can't switch protocols (e.g. to websockets) if this returns true. */
int cno_connection_is_http2 (struct cno_connection_t *);
/* cno_settings_copy loads a struct with current values, cno_settings_apply
* either sends updated values to the peer or schedules them to be sent
* when the connection enters HTTP 2 mode. */
void cno_settings_copy (struct cno_connection_t *, struct cno_settings_t *);
int cno_settings_apply (struct cno_connection_t *, const struct cno_settings_t *);
/* Send a new configuration/schedule it to be sent when upgrading to HTTP 2.
* The current configuration can be read through `conn->settings[CNO_LOCAL]`.
* DO NOT modify `conn->settings` directly -- it is used to compute the delta. */
int cno_connection_set_config (struct cno_connection_t *, const struct cno_settings_t *);

/* (As a client) sending requests:
*