size_t pos;
};
+/*
+ * Write HTTP/2 data to underlying transport layer.
+ */
static ssize_t send_callback(nghttp2_session *session, const uint8_t *data, size_t length,
int flags, void *user_data)
{
/* If the HEADERS don't have END_STREAM set, there are some DATA frames,
* which implies POST method. Skip header processing for POST. */
- if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) {
+ if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0)
return 0;
- }
/* If there is incomplete data in the buffer, we can't process the new stream. */
if (ctx->incomplete_stream) {
return 0;
}
-/* This method is called for data received via POST. */
-static int data_chunk_recv_callback(nghttp2_session *session, uint8_t flags, int32_t stream_id, const uint8_t *data, size_t len, void *user_data)
+/*
+ * Process DATA chunk sent by the client (by POST method).
+ *
+ * We use a single DNS message buffer for the entire connection. Therefore, we
+ * don't support interweaving DATA chunks from different streams. To successfully
+ * parse multiple subsequent streams, each one must be fully received before
+ * processing a new stream.
+ */
+static int data_chunk_recv_callback(nghttp2_session *session, uint8_t flags, int32_t stream_id,
+ const uint8_t *data, size_t len, void *user_data)
{
struct http_ctx *ctx = (struct http_ctx *)user_data;
+ ssize_t remaining;
+ ssize_t required;
if (ctx->incomplete_stream) {
if (queue_len(ctx->streams) <= 0) {
return NGHTTP2_ERR_CALLBACK_FAILURE;
} else if (queue_tail(ctx->streams) != stream_id) {
- /* If the received DATA chunk is from a new stream and the previous
- * one still has unfinished DATA, refuse the new stream. */
- kr_log_verbose("[http] refusing http DATA chunk, other stream has incomplete DATA\n");
- nghttp2_submit_rst_stream(
- session, NGHTTP2_FLAG_NONE, stream_id, NGHTTP2_REFUSED_STREAM);
+ kr_log_verbose("[http] previous stream incomplete, refusing\n");
+ refuse_stream(session, stream_id);
return 0;
}
}
- /* Check message and its length can still fit into the wire buffer. */
- ssize_t remaining = ctx->buf_size - ctx->submitted - ctx->buf_pos;
- ssize_t required = len + sizeof(uint16_t);
+ remaining = ctx->buf_size - ctx->submitted - ctx->buf_pos;
+ required = len + sizeof(uint16_t);
if (required > remaining) {
- kr_log_error(
- "[http] insufficient space in buffer: remaining %zd B, required %zd B\n",
- remaining, required);
+ kr_log_error("[http] insufficient space in buffer\n");
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
if (!ctx->incomplete_stream) {
ctx->incomplete_stream = true;
+ ctx->buf_pos = sizeof(uint16_t); /* Reserve 2B for dnsmsg len. */
queue_push(ctx->streams, stream_id);
-
- /* 2B at the start of buffer is reserved for message length. */
- ctx->buf_pos = sizeof(uint16_t);
}
+
memcpy(ctx->buf + ctx->buf_pos, data, len);
ctx->buf_pos += len;
-
return 0;
}
+/*
+ * Finalize existing buffer upon receiving the last frame in the stream.
+ *
+ * For GET, this would be HEADERS frame.
+ * For POST, it is a DATA frame.
+ *
+ * Unrelated frames (such as SETTINGS) are ignored (no data was buffered).
+ */
static int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame, void *user_data)
{
struct http_ctx *ctx = (struct http_ctx *)user_data;
+ ssize_t len;
if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) && ctx->buf_pos != 0) {
ctx->incomplete_stream = false;
- ssize_t len = ctx->buf_pos - sizeof(uint16_t);
+ len = ctx->buf_pos - sizeof(uint16_t);
if (len <= 0 || len > KNOT_WIRE_MAX_PKTSIZE) {
kr_log_verbose("[http] invalid dnsmsg size: %zd B\n", len);
return NGHTTP2_ERR_CALLBACK_FAILURE;
return 0;
}
+/*
+ * Setup and initialize connection with new HTTP/2 context.
+ */
struct http_ctx* http_new(http_send_callback cb, void *user_ctx)
{
assert(cb != NULL);
nghttp2_session_callbacks *callbacks;
+ struct http_ctx *ctx;
+ static const nghttp2_settings_entry iv[] = {
+ { NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, HTTP_MAX_CONCURRENT_STREAMS }
+ };
+
nghttp2_session_callbacks_new(&callbacks);
nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
nghttp2_session_callbacks_set_on_header_callback(callbacks, header_callback);
- nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks, data_chunk_recv_callback);
- nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, on_frame_recv_callback);
+ nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
+ callbacks, data_chunk_recv_callback);
+ nghttp2_session_callbacks_set_on_frame_recv_callback(
+ callbacks, on_frame_recv_callback);
- struct http_ctx *ctx = calloc(1UL, sizeof(struct http_ctx));
+ ctx = calloc(1UL, sizeof(struct http_ctx));
ctx->send_cb = cb;
ctx->user_ctx = user_ctx;
queue_init(ctx->streams);
nghttp2_session_server_new(&ctx->session, callbacks, ctx);
nghttp2_session_callbacks_del(callbacks);
- static const nghttp2_settings_entry iv[] = {
- { NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, HTTP_MAX_CONCURRENT_STREAMS }
- };
-
- nghttp2_submit_settings(ctx->session, NGHTTP2_FLAG_NONE, iv, sizeof(iv)/sizeof(*iv) );
+ nghttp2_submit_settings(ctx->session, NGHTTP2_FLAG_NONE,
+ iv, sizeof(iv)/sizeof(*iv));
return ctx;
}
+/*
+ * Process inbound HTTP/2 data and return number of bytes read into session wire buffer.
+ *
+ * This function may trigger outgoing HTTP/2 data, such as stream resets, window updates etc.
+ */
ssize_t http_process_input_data(struct session *s, const uint8_t *in_buf, ssize_t in_buf_len)
{
- struct http_ctx *http_p = session_http_get_server_ctx(s);
- if (!http_p->session) {
+ struct http_ctx *ctx = session_http_get_server_ctx(s);
+ ssize_t ret = 0;
+
+ if (!ctx->session) // TODO session vs h2; assert session equals
return kr_error(ENOSYS);
- }
- http_p->submitted = 0;
- http_p->buf = session_wirebuf_get_free_start(s);
- http_p->buf_pos = 0;
- http_p->buf_size = session_wirebuf_get_free_size(s);
+ ctx->submitted = 0;
+ ctx->buf = session_wirebuf_get_free_start(s);
+ ctx->buf_pos = 0;
+ ctx->buf_size = session_wirebuf_get_free_size(s);
- ssize_t ret = 0;
- if ((ret = nghttp2_session_mem_recv(http_p->session, in_buf, in_buf_len)) < 0) {
- kr_log_error("[http] nghttp2_session_mem_recv failed: %s (%zd)\n", nghttp2_strerror(ret), ret);
+ ret = nghttp2_session_mem_recv(ctx->session, in_buf, in_buf_len);
+ if (ret < 0) {
+ kr_log_error("[http] nghttp2_session_mem_recv failed: %s (%zd)\n",
+ nghttp2_strerror(ret), ret);
return kr_error(EIO);
}
- if ((ret = nghttp2_session_send(http_p->session)) < 0) {
- kr_log_error("[http] nghttp2_session_send failed: %s (%zd)\n", nghttp2_strerror(ret), ret);
+ ret = nghttp2_session_send(ctx->session);
+ if (ret < 0) {
+ kr_log_error("[http] nghttp2_session_send failed: %s (%zd)\n",
+ nghttp2_strerror(ret), ret);
return kr_error(EIO);
}
- return http_p->submitted;
+ return ctx->submitted;
}
-static ssize_t send_response_callback(nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t length, uint32_t *data_flags, nghttp2_data_source *source, void *user_data)
+/*
+ *
+ */
+static ssize_t send_response_callback(nghttp2_session *session, int32_t stream_id, uint8_t *buf,
+ size_t length, uint32_t *data_flags,
+ nghttp2_data_source *source, void *user_data)
{
struct http_data_buffer *buffer = (struct http_data_buffer *)source->ptr;
assert(buffer != NULL);
return kr_ok();
}
+/*
+ * Release HTTP/2 context.
+ */
void http_free(struct http_ctx *ctx)
{
- if (ctx == NULL || ctx->session == NULL) {
+ if (!ctx)
return;
- }
+
queue_deinit(ctx->streams);
nghttp2_session_del(ctx->session);
- ctx->session = NULL;
+ free(ctx);
}