zephyr/subsys/net/lib/http/http_server_http2.c
Robert Lubos e9bedccc2e net: http_server: Add support for generic HTTP2 500 response
In case of errors during HTTP2 request processing (or after the HTTP1
upgrade response was sent), send 500 Internal Server Error response
before shutting down the connection.

Signed-off-by: Robert Lubos <robert.lubos@nordicsemi.no>
2025-01-16 14:52:10 +01:00

1859 lines
48 KiB
C

/*
* Copyright (c) 2023, Emna Rekik
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <zephyr/fs/fs.h>
#include <zephyr/fs/fs_interface.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/net/http/service.h>
LOG_MODULE_DECLARE(net_http_server, CONFIG_NET_HTTP_SERVER_LOG_LEVEL);
#include "headers/server_internal.h"
static const char content_404[] = {
#ifdef INCLUDE_HTML_CONTENT
#include "not_found_page.html.gz.inc"
#endif
};
static bool is_header_flag_set(uint8_t flags, uint8_t mask)
{
return (flags & mask) != 0;
}
static void clear_header_flag(uint8_t *flags, uint8_t mask)
{
*flags &= ~mask;
}
static void print_http_frames(struct http_client_ctx *client)
{
#if defined(PRINT_COLOR)
const char *bold = "\033[1m";
const char *reset = "\033[0m";
const char *green = "\033[32m";
const char *blue = "\033[34m";
#else
const char *bold = "";
const char *reset = "";
const char *green = "";
const char *blue = "";
#endif
struct http2_frame *frame = &client->current_frame;
LOG_DBG("%s=====================================%s", green, reset);
LOG_DBG("%sReceived %s Frame :%s", bold, get_frame_type_name(frame->type), reset);
LOG_DBG(" %sLength:%s %u", blue, reset, frame->length);
LOG_DBG(" %sType:%s %u (%s)", blue, reset, frame->type, get_frame_type_name(frame->type));
LOG_DBG(" %sFlags:%s %u", blue, reset, frame->flags);
LOG_DBG(" %sStream Identifier:%s %u", blue, reset, frame->stream_identifier);
LOG_DBG("%s=====================================%s", green, reset);
}
static struct http2_stream_ctx *find_http_stream_context(
struct http_client_ctx *client, uint32_t stream_id)
{
ARRAY_FOR_EACH(client->streams, i) {
if (client->streams[i].stream_id == stream_id) {
return &client->streams[i];
}
}
return NULL;
}
static struct http2_stream_ctx *allocate_http_stream_context(
struct http_client_ctx *client, uint32_t stream_id)
{
ARRAY_FOR_EACH(client->streams, i) {
if (client->streams[i].stream_state == HTTP2_STREAM_IDLE) {
client->streams[i].stream_id = stream_id;
client->streams[i].stream_state = HTTP2_STREAM_OPEN;
client->streams[i].window_size =
HTTP_SERVER_INITIAL_WINDOW_SIZE;
client->streams[i].headers_sent = false;
client->streams[i].end_stream_sent = false;
return &client->streams[i];
}
}
return NULL;
}
static void release_http_stream_context(struct http_client_ctx *client,
uint32_t stream_id)
{
ARRAY_FOR_EACH(client->streams, i) {
if (client->streams[i].stream_id == stream_id) {
client->streams[i].stream_id = 0;
client->streams[i].stream_state = HTTP2_STREAM_IDLE;
client->streams[i].current_detail = NULL;
break;
}
}
}
static int add_header_field(struct http_client_ctx *client, uint8_t **buf,
size_t *buflen, const char *name, const char *value)
{
int ret;
client->header_field.name = name;
client->header_field.name_len = strlen(name);
client->header_field.value = value;
client->header_field.value_len = strlen(value);
ret = http_hpack_encode_header(*buf, *buflen, &client->header_field);
if (ret < 0) {
LOG_DBG("Failed to encode header, err %d", ret);
return ret;
}
*buf += ret;
*buflen -= ret;
return 0;
}
static void encode_frame_header(uint8_t *buf, uint32_t payload_len,
enum http2_frame_type frame_type,
uint8_t flags, uint32_t stream_id)
{
sys_put_be24(payload_len, &buf[HTTP2_FRAME_LENGTH_OFFSET]);
buf[HTTP2_FRAME_TYPE_OFFSET] = frame_type;
buf[HTTP2_FRAME_FLAGS_OFFSET] = flags;
sys_put_be32(stream_id, &buf[HTTP2_FRAME_STREAM_ID_OFFSET]);
}
static int send_headers_frame(struct http_client_ctx *client, enum http_status status,
uint32_t stream_id, struct http_resource_detail *detail_common,
uint8_t flags, const struct http_header *extra_headers,
size_t extra_headers_count)
{
uint8_t headers_frame[CONFIG_HTTP_SERVER_HTTP2_MAX_HEADER_FRAME_LEN];
uint8_t status_str[4];
uint8_t *buf = headers_frame + HTTP2_FRAME_HEADER_SIZE;
size_t buflen = sizeof(headers_frame) - HTTP2_FRAME_HEADER_SIZE;
bool content_encoding_sent = false;
bool content_type_sent = false;
size_t payload_len;
int ret;
ret = snprintf(status_str, sizeof(status_str), "%d", status);
if (ret > sizeof(status_str) - 1) {
return -EINVAL;
}
ret = add_header_field(client, &buf, &buflen, ":status", status_str);
if (ret < 0) {
return ret;
}
for (size_t i = 0; i < extra_headers_count; i++) {
const struct http_header *hdr = &extra_headers[i];
if (strcasecmp(hdr->name, "content-encoding") == 0) {
content_encoding_sent = true;
}
if (strcasecmp(hdr->name, "content-type") == 0) {
content_type_sent = true;
}
ret = add_header_field(client, &buf, &buflen, hdr->name, hdr->value);
if (ret < 0) {
return ret;
}
}
if (!content_encoding_sent && detail_common && detail_common->content_encoding != NULL) {
ret = add_header_field(client, &buf, &buflen, "content-encoding",
detail_common->content_encoding);
if (ret < 0) {
return ret;
}
}
if (!content_type_sent && detail_common && detail_common->content_type != NULL) {
ret = add_header_field(client, &buf, &buflen, "content-type",
detail_common->content_type);
if (ret < 0) {
return ret;
}
}
payload_len = sizeof(headers_frame) - buflen - HTTP2_FRAME_HEADER_SIZE;
flags |= HTTP2_FLAG_END_HEADERS;
encode_frame_header(headers_frame, payload_len, HTTP2_HEADERS_FRAME,
flags, stream_id);
ret = http_server_sendall(client, headers_frame,
payload_len + HTTP2_FRAME_HEADER_SIZE);
if (ret < 0) {
LOG_DBG("Cannot write to socket (%d)", ret);
return ret;
}
client->current_stream->headers_sent = true;
return 0;
}
static int send_data_frame(struct http_client_ctx *client, const char *payload,
size_t length, uint32_t stream_id, uint8_t flags)
{
uint8_t frame_header[HTTP2_FRAME_HEADER_SIZE];
int ret;
encode_frame_header(frame_header, length, HTTP2_DATA_FRAME,
is_header_flag_set(flags, HTTP2_FLAG_END_STREAM) ?
HTTP2_FLAG_END_STREAM : 0,
stream_id);
ret = http_server_sendall(client, frame_header, sizeof(frame_header));
if (ret < 0) {
LOG_DBG("Cannot write to socket (%d)", ret);
} else {
if (payload != NULL && length > 0) {
ret = http_server_sendall(client, payload, length);
if (ret < 0) {
LOG_DBG("Cannot write to socket (%d)", ret);
}
}
}
return ret;
}
int send_settings_frame(struct http_client_ctx *client, bool ack)
{
uint8_t settings_frame[HTTP2_FRAME_HEADER_SIZE +
2 * sizeof(struct http2_settings_field)];
struct http2_settings_field *setting;
size_t len;
int ret;
if (ack) {
encode_frame_header(settings_frame, 0,
HTTP2_SETTINGS_FRAME,
HTTP2_FLAG_SETTINGS_ACK, 0);
len = HTTP2_FRAME_HEADER_SIZE;
} else {
encode_frame_header(settings_frame,
2 * sizeof(struct http2_settings_field),
HTTP2_SETTINGS_FRAME, 0, 0);
setting = (struct http2_settings_field *)
(settings_frame + HTTP2_FRAME_HEADER_SIZE);
UNALIGNED_PUT(htons(HTTP2_SETTINGS_HEADER_TABLE_SIZE),
&setting->id);
UNALIGNED_PUT(0, &setting->value);
setting++;
UNALIGNED_PUT(htons(HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS),
&setting->id);
UNALIGNED_PUT(htonl(CONFIG_HTTP_SERVER_MAX_STREAMS),
&setting->value);
len = HTTP2_FRAME_HEADER_SIZE +
2 * sizeof(struct http2_settings_field);
}
ret = http_server_sendall(client, settings_frame, len);
if (ret < 0) {
LOG_DBG("Cannot write to socket (%d)", ret);
return ret;
}
return 0;
}
int send_window_update_frame(struct http_client_ctx *client,
struct http2_stream_ctx *stream)
{
uint8_t window_update_frame[HTTP2_FRAME_HEADER_SIZE +
sizeof(uint32_t)];
uint32_t window_update;
uint32_t stream_id;
int ret;
if (stream != NULL) {
window_update = HTTP_SERVER_INITIAL_WINDOW_SIZE - stream->window_size;
stream->window_size = HTTP_SERVER_INITIAL_WINDOW_SIZE;
stream_id = stream->stream_id;
} else {
window_update = HTTP_SERVER_INITIAL_WINDOW_SIZE - client->window_size;
client->window_size = HTTP_SERVER_INITIAL_WINDOW_SIZE;
stream_id = 0;
}
encode_frame_header(window_update_frame, sizeof(uint32_t),
HTTP2_WINDOW_UPDATE_FRAME,
0, stream_id);
sys_put_be32(window_update,
window_update_frame + HTTP2_FRAME_HEADER_SIZE);
ret = http_server_sendall(client, window_update_frame,
sizeof(window_update_frame));
if (ret < 0) {
LOG_DBG("Cannot write to socket (%d)", ret);
return ret;
}
return 0;
}
static int send_http2_404(struct http_client_ctx *client,
struct http2_frame *frame)
{
int ret;
ret = send_headers_frame(client, HTTP_404_NOT_FOUND, frame->stream_identifier, NULL, 0,
NULL, 0);
if (ret < 0) {
LOG_DBG("Cannot write to socket (%d)", ret);
return ret;
}
ret = send_data_frame(client, content_404, sizeof(content_404),
frame->stream_identifier,
HTTP2_FLAG_END_STREAM);
if (ret < 0) {
LOG_DBG("Cannot write to socket (%d)", ret);
}
return ret;
}
static int send_http2_405(struct http_client_ctx *client,
struct http2_frame *frame)
{
int ret;
ret = send_headers_frame(client, HTTP_405_METHOD_NOT_ALLOWED,
frame->stream_identifier, NULL,
HTTP2_FLAG_END_STREAM, NULL, 0);
if (ret < 0) {
LOG_DBG("Cannot write to socket (%d)", ret);
return ret;
}
return ret;
}
static int send_http2_409(struct http_client_ctx *client,
struct http2_frame *frame)
{
int ret;
ret = send_headers_frame(client, HTTP_409_CONFLICT, frame->stream_identifier, NULL,
HTTP2_FLAG_END_STREAM, NULL, 0);
if (ret < 0) {
LOG_DBG("Cannot write to socket (%d)", ret);
}
return ret;
}
static void send_http2_500(struct http_client_ctx *client,
struct http2_frame *frame, int error_code)
{
#define HTTP_500_RESPONSE_TEMPLATE "Internal Server Error%s%s"
#define MAX_ERROR_DESC_LEN 32
char error_str[] = "xxx";
char http_response[sizeof(HTTP_500_RESPONSE_TEMPLATE) +
MAX_ERROR_DESC_LEN + 1]; /* For the error description */
const char *error_desc;
const char *desc_separator;
if (IS_ENABLED(CONFIG_HTTP_SERVER_REPORT_FAILURE_REASON)) {
/* Try to fetch error description, fallback to error number if
* not available
*/
error_desc = strerror(error_code);
if (strlen(error_desc) == 0) {
/* Cast error value to uint8_t to avoid truncation warnings. */
(void)snprintk(error_str, sizeof(error_str), "%u",
(uint8_t)error_code);
error_desc = error_str;
}
desc_separator = ": ";
} else {
error_desc = "";
desc_separator = "";
}
if (send_headers_frame(client, HTTP_500_INTERNAL_SERVER_ERROR,
frame->stream_identifier, NULL, 0, NULL, 0) < 0) {
return;
}
(void)snprintk(http_response, sizeof(http_response),
HTTP_500_RESPONSE_TEMPLATE, desc_separator, error_desc);
(void)send_data_frame(client, http_response, strlen(http_response),
frame->stream_identifier, HTTP2_FLAG_END_STREAM);
}
static int handle_http2_static_resource(
struct http_resource_detail_static *static_detail,
struct http2_frame *frame, struct http_client_ctx *client)
{
const char *content_200;
size_t content_len;
int ret;
if (client->method != HTTP_GET) {
return send_http2_405(client, frame);
}
if (client->current_stream == NULL) {
return -ENOENT;
}
content_200 = static_detail->static_data;
content_len = static_detail->static_data_len;
ret = send_headers_frame(client, HTTP_200_OK, frame->stream_identifier,
&static_detail->common, 0, NULL, 0);
if (ret < 0) {
LOG_DBG("Cannot write to socket (%d)", ret);
goto out;
}
ret = send_data_frame(client, content_200, content_len,
frame->stream_identifier,
HTTP2_FLAG_END_STREAM);
if (ret < 0) {
LOG_DBG("Cannot write to socket (%d)", ret);
goto out;
}
client->current_stream->end_stream_sent = true;
out:
return ret;
}
static int handle_http2_static_fs_resource(struct http_resource_detail_static_fs *static_fs_detail,
struct http2_frame *frame,
struct http_client_ctx *client)
{
int ret;
struct fs_file_t file;
char fname[HTTP_SERVER_MAX_URL_LENGTH];
char content_type[HTTP_SERVER_MAX_CONTENT_TYPE_LEN] = "text/html";
struct http_resource_detail res_detail = {
.bitmask_of_supported_http_methods =
static_fs_detail->common.bitmask_of_supported_http_methods,
.content_type = content_type,
.path_len = static_fs_detail->common.path_len,
.type = static_fs_detail->common.type,
};
bool gzipped;
int len;
int remaining;
char tmp[64];
if (client->method != HTTP_GET) {
return send_http2_405(client, frame);
}
if (client->current_stream == NULL) {
return -ENOENT;
}
/* get filename and content-type from url */
len = strlen(client->url_buffer);
if (len == 1) {
/* url is just the leading slash, use index.html as filename */
snprintk(fname, sizeof(fname), "%s/index.html", static_fs_detail->fs_path);
} else {
http_server_get_content_type_from_extension(client->url_buffer, content_type,
sizeof(content_type));
snprintk(fname, sizeof(fname), "%s%s", static_fs_detail->fs_path,
client->url_buffer);
}
/* open file, if it exists */
ret = http_server_find_file(fname, sizeof(fname), &client->data_len, &gzipped);
if (ret < 0) {
LOG_ERR("fs_stat %s: %d", fname, ret);
ret = send_headers_frame(client, HTTP_404_NOT_FOUND, frame->stream_identifier, NULL,
0, NULL, 0);
if (ret < 0) {
LOG_DBG("Cannot write to socket (%d)", ret);
}
return ret;
}
fs_file_t_init(&file);
ret = fs_open(&file, fname, FS_O_READ);
if (ret < 0) {
LOG_ERR("fs_open %s: %d", fname, ret);
if (ret < 0) {
return ret;
}
}
/* send headers */
if (gzipped) {
res_detail.content_encoding = "gzip";
}
ret = send_headers_frame(client, HTTP_200_OK, frame->stream_identifier, &res_detail, 0,
NULL, 0);
if (ret < 0) {
LOG_DBG("Cannot write to socket (%d)", ret);
goto out;
}
/* read and send file */
remaining = client->data_len;
while (remaining > 0) {
len = fs_read(&file, tmp, sizeof(tmp));
if (len < 0) {
LOG_ERR("Filesystem read error (%d)", len);
goto out;
}
remaining -= len;
ret = send_data_frame(client, tmp, len, frame->stream_identifier,
(remaining > 0) ? 0 : HTTP2_FLAG_END_STREAM);
if (ret < 0) {
LOG_DBG("Cannot write to socket (%d)", ret);
goto out;
}
}
client->current_stream->end_stream_sent = true;
out:
/* close file */
fs_close(&file);
return ret;
}
static int http2_dynamic_response(struct http_client_ctx *client, struct http2_frame *frame,
struct http_response_ctx *rsp, enum http_data_status data_status,
struct http_resource_detail_dynamic *dynamic_detail)
{
int ret;
uint8_t flags = 0;
bool final_response = http_response_is_final(rsp, data_status);
if (client->current_stream->headers_sent && (rsp->header_count > 0 || rsp->status != 0)) {
LOG_WRN("Already sent headers, dropping new headers and/or response code");
}
/* Send headers and response code if not already sent */
if (!client->current_stream->headers_sent) {
/* Use '200 OK' status if not specified by application */
if (rsp->status == 0) {
rsp->status = 200;
}
if (rsp->status < HTTP_100_CONTINUE ||
rsp->status > HTTP_511_NETWORK_AUTHENTICATION_REQUIRED) {
LOG_DBG("Invalid HTTP status code: %d", rsp->status);
return -EINVAL;
}
if (rsp->headers == NULL && rsp->header_count > 0) {
LOG_DBG("NULL headers, but count is > 0");
return -EINVAL;
}
if (final_response && rsp->body_len == 0) {
flags |= HTTP2_FLAG_END_STREAM;
client->current_stream->end_stream_sent = true;
}
ret = send_headers_frame(client, rsp->status, frame->stream_identifier,
(struct http_resource_detail *)dynamic_detail, flags,
rsp->headers, rsp->header_count);
if (ret < 0) {
return ret;
}
}
/* Send body data if provided */
if (rsp->body != NULL && rsp->body_len > 0) {
if (final_response) {
flags |= HTTP2_FLAG_END_STREAM;
client->current_stream->end_stream_sent = true;
}
ret = send_data_frame(client, rsp->body, rsp->body_len, frame->stream_identifier,
flags);
if (ret < 0) {
return ret;
}
}
return 0;
}
static int dynamic_get_del_req_v2(struct http_resource_detail_dynamic *dynamic_detail,
struct http_client_ctx *client)
{
int ret, len;
char *ptr;
struct http2_frame *frame = &client->current_frame;
enum http_data_status status;
struct http_request_ctx request_ctx;
struct http_response_ctx response_ctx;
if (client->current_stream == NULL) {
return -ENOENT;
}
/* Start of GET params */
ptr = &client->url_buffer[dynamic_detail->common.path_len];
len = strlen(ptr);
status = HTTP_SERVER_DATA_FINAL;
do {
memset(&response_ctx, 0, sizeof(response_ctx));
populate_request_ctx(&request_ctx, ptr, len, &client->header_capture_ctx);
ret = dynamic_detail->cb(client, status, &request_ctx, &response_ctx,
dynamic_detail->user_data);
if (ret < 0) {
return ret;
}
ret = http2_dynamic_response(client, frame, &response_ctx, status, dynamic_detail);
if (ret < 0) {
return ret;
}
/* URL params are passed in the first cb only */
len = 0;
} while (!http_response_is_final(&response_ctx, status));
if (!client->current_stream->end_stream_sent) {
client->current_stream->end_stream_sent = true;
ret = send_data_frame(client, NULL, 0, frame->stream_identifier,
HTTP2_FLAG_END_STREAM);
if (ret < 0) {
LOG_DBG("Cannot send last frame (%d)", ret);
}
}
dynamic_detail->holder = NULL;
return ret;
}
static int dynamic_post_put_req_v2(struct http_resource_detail_dynamic *dynamic_detail,
struct http_client_ctx *client, bool headers_only)
{
int ret = 0;
char *ptr = client->cursor;
size_t data_len;
enum http_data_status status;
struct http2_frame *frame = &client->current_frame;
struct http_request_ctx request_ctx;
struct http_response_ctx response_ctx;
struct http_header_capture_ctx *request_headers_ctx =
headers_only ? &client->header_capture_ctx : NULL;
if (dynamic_detail == NULL) {
return -ENOENT;
}
if (client->current_stream == NULL) {
return -ENOENT;
}
if (headers_only) {
data_len = 0;
} else {
data_len = MIN(frame->length, client->data_len);
frame->length -= data_len;
client->cursor += data_len;
client->data_len -= data_len;
}
if (frame->length == 0 && is_header_flag_set(frame->flags, HTTP2_FLAG_END_STREAM) &&
!headers_only) {
status = HTTP_SERVER_DATA_FINAL;
} else {
status = HTTP_SERVER_DATA_MORE;
}
memset(&response_ctx, 0, sizeof(response_ctx));
populate_request_ctx(&request_ctx, ptr, data_len, request_headers_ctx);
ret = dynamic_detail->cb(client, status, &request_ctx, &response_ctx,
dynamic_detail->user_data);
if (ret < 0) {
return ret;
}
/* For POST the application might not send a response until all data has been received.
* Don't send a default response until the application has had a chance to respond.
*/
if (http_response_is_provided(&response_ctx)) {
ret = http2_dynamic_response(client, frame, &response_ctx, status, dynamic_detail);
if (ret < 0) {
return ret;
}
}
/* Once all data is transferred to application, repeat cb until response is complete */
while (!http_response_is_final(&response_ctx, status) && status == HTTP_SERVER_DATA_FINAL) {
memset(&response_ctx, 0, sizeof(response_ctx));
populate_request_ctx(&request_ctx, ptr, 0, request_headers_ctx);
ret = dynamic_detail->cb(client, status, &request_ctx, &response_ctx,
dynamic_detail->user_data);
if (ret < 0) {
return ret;
}
ret = http2_dynamic_response(client, frame, &response_ctx, status, dynamic_detail);
if (ret < 0) {
return ret;
}
}
/* At end of stream, ensure response is sent and terminated */
if (frame->length == 0 && !client->current_stream->end_stream_sent &&
is_header_flag_set(frame->flags, HTTP2_FLAG_END_STREAM)) {
if (client->current_stream->headers_sent) {
ret = send_data_frame(client, NULL, 0, frame->stream_identifier,
HTTP2_FLAG_END_STREAM);
} else {
memset(&response_ctx, 0, sizeof(response_ctx));
response_ctx.final_chunk = true;
ret = http2_dynamic_response(client, frame, &response_ctx,
HTTP_SERVER_DATA_FINAL, dynamic_detail);
}
if (ret < 0) {
LOG_DBG("Cannot send last frame (%d)", ret);
}
client->current_stream->end_stream_sent = true;
dynamic_detail->holder = NULL;
}
return ret;
}
static int handle_http2_dynamic_resource(
struct http_resource_detail_dynamic *dynamic_detail,
struct http2_frame *frame, struct http_client_ctx *client)
{
uint32_t user_method;
int ret;
if (dynamic_detail->cb == NULL) {
return -ESRCH;
}
user_method = dynamic_detail->common.bitmask_of_supported_http_methods;
if (!(BIT(client->method) & user_method)) {
return send_http2_405(client, frame);
}
if (dynamic_detail->holder != NULL && dynamic_detail->holder != client) {
ret = send_http2_409(client, frame);
if (ret < 0) {
return ret;
}
return enter_http_done_state(client);
}
dynamic_detail->holder = client;
switch (client->method) {
case HTTP_GET:
case HTTP_DELETE:
if (user_method & BIT(client->method)) {
return dynamic_get_del_req_v2(dynamic_detail, client);
}
goto not_supported;
case HTTP_POST:
case HTTP_PUT:
case HTTP_PATCH:
/* The data will come in DATA frames. Remember the detail ptr
* which needs to be known when passing data to application.
*/
if (user_method & BIT(client->method)) {
client->current_stream->current_detail =
(struct http_resource_detail *)dynamic_detail;
/* If there are any header fields to pass to the application, call the
* dynamic handler now with the header data so that this may be cleared to
* re-use for any other concurrent streams
*/
if (IS_ENABLED(CONFIG_HTTP_SERVER_CAPTURE_HEADERS)) {
ret = dynamic_post_put_req_v2(dynamic_detail, client, true);
if (ret < 0) {
return ret;
}
}
break;
}
goto not_supported;
not_supported:
default:
LOG_DBG("HTTP method %s (%d) not supported.",
http_method_str(client->method),
client->method);
return -ENOTSUP;
}
return 0;
}
int enter_http2_request(struct http_client_ctx *client)
{
int ret;
client->server_state = HTTP_SERVER_FRAME_HEADER_STATE;
client->data_len -= sizeof(HTTP2_PREFACE) - 1;
client->cursor += sizeof(HTTP2_PREFACE) - 1;
/* HTTP/2 client preface received, send server preface
* (settings frame).
*/
if (!client->preface_sent) {
ret = send_settings_frame(client, false);
if (ret < 0) {
return ret;
}
client->preface_sent = true;
}
return 0;
}
static int enter_http_frame_settings_state(struct http_client_ctx *client)
{
client->server_state = HTTP_SERVER_FRAME_SETTINGS_STATE;
return 0;
}
static int enter_http_frame_data_state(struct http_client_ctx *client)
{
struct http2_frame *frame = &client->current_frame;
struct http2_stream_ctx *stream;
if (frame->stream_identifier == 0) {
LOG_DBG("Stream ID 0 is forbidden for data frames.");
return -EBADMSG;
}
stream = find_http_stream_context(client, frame->stream_identifier);
if (stream == NULL) {
LOG_DBG("No stream context found for ID %d",
frame->stream_identifier);
return -EBADMSG;
}
if (stream->stream_state != HTTP2_STREAM_OPEN &&
stream->stream_state != HTTP2_STREAM_HALF_CLOSED_REMOTE) {
LOG_DBG("Stream ID %d in a wrong state %d", stream->stream_id,
stream->stream_state);
return -EBADMSG;
}
stream->window_size -= frame->length;
client->window_size -= frame->length;
client->server_state = HTTP_SERVER_FRAME_DATA_STATE;
client->current_stream = stream;
return 0;
}
static int enter_http_frame_headers_state(struct http_client_ctx *client)
{
struct http2_frame *frame = &client->current_frame;
struct http2_stream_ctx *stream;
stream = find_http_stream_context(client, frame->stream_identifier);
if (!stream) {
LOG_DBG("|| stream ID || %d", frame->stream_identifier);
stream = allocate_http_stream_context(client, frame->stream_identifier);
if (!stream) {
LOG_DBG("No available stream slots. Connection closed.");
return -ENOMEM;
}
}
client->current_stream = stream;
if (!is_header_flag_set(frame->flags, HTTP2_FLAG_END_HEADERS)) {
client->expect_continuation = true;
} else {
client->expect_continuation = false;
}
if (IS_ENABLED(CONFIG_HTTP_SERVER_CAPTURE_HEADERS)) {
/* Reset header capture state for new headers frame */
client->header_capture_ctx.count = 0;
client->header_capture_ctx.cursor = 0;
client->header_capture_ctx.status = HTTP_HEADER_STATUS_OK;
client->header_capture_ctx.current_stream = stream;
}
client->server_state = HTTP_SERVER_FRAME_HEADERS_STATE;
return 0;
}
static int enter_http_frame_continuation_state(struct http_client_ctx *client)
{
struct http2_frame *frame = &client->current_frame;
if (!is_header_flag_set(frame->flags, HTTP2_FLAG_END_HEADERS)) {
client->expect_continuation = true;
} else {
client->expect_continuation = false;
}
client->server_state = HTTP_SERVER_FRAME_CONTINUATION_STATE;
return 0;
}
static int enter_http_frame_window_update_state(struct http_client_ctx *client)
{
client->server_state = HTTP_SERVER_FRAME_WINDOW_UPDATE_STATE;
return 0;
}
static int enter_http_frame_priority_state(struct http_client_ctx *client)
{
client->server_state = HTTP_SERVER_FRAME_PRIORITY_STATE;
return 0;
}
static int enter_http_frame_rst_stream_state(struct http_client_ctx *client)
{
client->server_state = HTTP_SERVER_FRAME_RST_STREAM_STATE;
return 0;
}
static int enter_http_frame_goaway_state(struct http_client_ctx *client)
{
client->server_state = HTTP_SERVER_FRAME_GOAWAY_STATE;
return 0;
}
int handle_http_frame_header(struct http_client_ctx *client)
{
int ret;
LOG_DBG("HTTP_SERVER_FRAME_HEADER");
ret = parse_http_frame_header(client, client->cursor, client->data_len);
if (ret < 0) {
return ret;
}
client->cursor += HTTP2_FRAME_HEADER_SIZE;
client->data_len -= HTTP2_FRAME_HEADER_SIZE;
print_http_frames(client);
if (client->expect_continuation &&
client->current_frame.type != HTTP2_CONTINUATION_FRAME) {
LOG_ERR("Continuation frame expected");
return -EBADMSG;
}
client->current_stream = NULL;
switch (client->current_frame.type) {
case HTTP2_DATA_FRAME:
return enter_http_frame_data_state(client);
case HTTP2_HEADERS_FRAME:
return enter_http_frame_headers_state(client);
case HTTP2_CONTINUATION_FRAME:
return enter_http_frame_continuation_state(client);
case HTTP2_SETTINGS_FRAME:
return enter_http_frame_settings_state(client);
case HTTP2_WINDOW_UPDATE_FRAME:
return enter_http_frame_window_update_state(client);
case HTTP2_RST_STREAM_FRAME:
return enter_http_frame_rst_stream_state(client);
case HTTP2_GOAWAY_FRAME:
return enter_http_frame_goaway_state(client);
case HTTP2_PRIORITY_FRAME:
return enter_http_frame_priority_state(client);
default:
return enter_http_done_state(client);
}
return 0;
}
/* This feature is theoretically obsoleted in RFC9113, but curl for instance
* still uses it, so implement as described in RFC7540.
*/
int handle_http1_to_http2_upgrade(struct http_client_ctx *client)
{
static const char switching_protocols[] =
"HTTP/1.1 101 Switching Protocols\r\n"
"Connection: Upgrade\r\n"
"Upgrade: h2c\r\n"
"\r\n";
struct http2_frame *frame = &client->current_frame;
struct http_resource_detail *detail;
struct http2_stream_ctx *stream;
int path_len;
int ret;
/* Create an artificial Data frame, so that we can proceed with HTTP2
* processing. The HTTP/1.1 request that is sent prior to upgrade is
* assigned a stream identifier of 1.
*/
frame->stream_identifier = 1;
frame->type = HTTP2_DATA_FRAME;
frame->length = client->http1_frag_data_len;
if (client->parser_state == HTTP1_MESSAGE_COMPLETE_STATE) {
frame->flags = HTTP2_FLAG_END_STREAM;
} else {
frame->flags = 0;
}
/* Allocate stream. */
stream = find_http_stream_context(client, frame->stream_identifier);
if (stream == NULL) {
stream = allocate_http_stream_context(client, frame->stream_identifier);
if (!stream) {
LOG_DBG("No available stream slots. Connection closed.");
return -ENOMEM;
}
}
client->current_stream = stream;
if (!client->preface_sent) {
ret = http_server_sendall(client, switching_protocols,
sizeof(switching_protocols) - 1);
if (ret < 0) {
return ret;
}
client->http1_headers_sent = true;
/* The first HTTP/2 frame sent by the server MUST be a server connection
* preface.
*/
ret = send_settings_frame(client, false);
if (ret < 0) {
goto error;
}
client->preface_sent = true;
}
detail = get_resource_detail(client->service, client->url_buffer, &path_len, false);
if (detail != NULL) {
detail->path_len = path_len;
if (detail->type == HTTP_RESOURCE_TYPE_STATIC) {
ret = handle_http2_static_resource(
(struct http_resource_detail_static *)detail,
frame, client);
if (ret < 0) {
goto error;
}
} else if (detail->type == HTTP_RESOURCE_TYPE_STATIC_FS) {
ret = handle_http2_static_fs_resource(
(struct http_resource_detail_static_fs *)detail, frame, client);
if (ret < 0) {
goto error;
}
} else if (detail->type == HTTP_RESOURCE_TYPE_DYNAMIC) {
ret = handle_http2_dynamic_resource(
(struct http_resource_detail_dynamic *)detail,
frame, client);
if (ret < 0) {
goto error;
}
if (client->method == HTTP_POST ||
client->method == HTTP_PUT ||
client->method == HTTP_PATCH) {
ret = dynamic_post_put_req_v2(
(struct http_resource_detail_dynamic *)detail, client,
false);
if (ret < 0) {
goto error;
}
}
}
} else {
ret = send_http2_404(client, frame);
if (ret < 0) {
goto error;
}
}
/* Only after the complete HTTP1 payload has been processed, switch
* to HTTP2.
*/
if (client->parser_state == HTTP1_MESSAGE_COMPLETE_STATE) {
release_http_stream_context(client, frame->stream_identifier);
client->current_detail = NULL;
client->server_state = HTTP_SERVER_PREFACE_STATE;
client->cursor += client->data_len;
client->data_len = 0;
}
return 0;
error:
if (ret != -EAGAIN && client->current_stream &&
!client->current_stream->headers_sent) {
send_http2_500(client, frame, -ret);
}
return ret;
}
static int parse_http_frame_padded_field(struct http_client_ctx *client)
{
struct http2_frame *frame = &client->current_frame;
if (client->data_len == 0) {
return -EAGAIN;
}
frame->padding_len = *client->cursor;
client->cursor++;
client->data_len--;
frame->length--;
if (frame->length <= frame->padding_len) {
return -EBADMSG;
}
/* Subtract the padding length from frame length now to simplify
* payload processing. Padding will be handled based on
* frame->padding_len in a separate state.
*/
frame->length -= frame->padding_len;
/* Clear the padded flag, this indicates that padding field was
* already parsed.
*/
clear_header_flag(&frame->flags, HTTP2_FLAG_PADDED);
return 0;
}
static int parse_http_frame_priority_field(struct http_client_ctx *client)
{
struct http2_frame *frame = &client->current_frame;
if (client->data_len < HTTP2_HEADERS_FRAME_PRIORITY_LEN) {
return -EAGAIN;
}
/* Priority signalling is deprecated by RFC 9113, however it still
* should be expected to receive, just drop the bytes.
*/
client->cursor += HTTP2_HEADERS_FRAME_PRIORITY_LEN;
client->data_len -= HTTP2_HEADERS_FRAME_PRIORITY_LEN;
frame->length -= HTTP2_HEADERS_FRAME_PRIORITY_LEN;
/* Clear the priority flag, this indicates that priority field was
* already parsed.
*/
clear_header_flag(&frame->flags, HTTP2_FLAG_PRIORITY);
return 0;
}
int handle_http_frame_data(struct http_client_ctx *client)
{
struct http2_frame *frame = &client->current_frame;
int ret;
LOG_DBG("HTTP_SERVER_FRAME_DATA_STATE");
if (client->current_stream->current_detail == NULL) {
/* There is no handler */
LOG_DBG("No dynamic handler found.");
(void)send_http2_404(client, frame);
ret = -ENOENT;
goto error;
}
if (is_header_flag_set(frame->flags, HTTP2_FLAG_PADDED)) {
ret = parse_http_frame_padded_field(client);
if (ret < 0) {
goto error;
}
}
ret = dynamic_post_put_req_v2(
(struct http_resource_detail_dynamic *)client->current_stream->current_detail,
client, false);
if (ret < 0 && ret == -ENOENT) {
ret = send_http2_404(client, frame);
}
if (ret < 0) {
goto error;
}
if (frame->length == 0) {
struct http2_stream_ctx *stream =
find_http_stream_context(client, frame->stream_identifier);
if (stream == NULL) {
LOG_DBG("No stream context found for ID %d",
frame->stream_identifier);
ret = -EBADMSG;
goto error;
}
ret = send_window_update_frame(client, stream);
if (ret < 0) {
goto error;
}
ret = send_window_update_frame(client, NULL);
if (ret < 0) {
goto error;
}
if (is_header_flag_set(frame->flags, HTTP2_FLAG_END_STREAM)) {
client->current_stream->current_detail = NULL;
release_http_stream_context(client, frame->stream_identifier);
}
/* Whole frame consumed, expect next one. */
if (frame->padding_len > 0) {
client->server_state = HTTP_SERVER_FRAME_PADDING_STATE;
} else {
client->server_state = HTTP_SERVER_FRAME_HEADER_STATE;
}
}
return 0;
error:
if (ret != -EAGAIN && client->current_stream &&
!client->current_stream->headers_sent) {
send_http2_500(client, frame, -ret);
}
return ret;
}
static void check_user_request_headers_http2(struct http_header_capture_ctx *ctx,
struct http_hpack_header_buf *hdr_buf)
{
size_t required_len;
char *dest = &ctx->buffer[ctx->cursor];
size_t remaining = sizeof(ctx->buffer) - ctx->cursor;
struct http_header *current_header = &ctx->headers[ctx->count];
STRUCT_SECTION_FOREACH(http_header_name, header) {
required_len = hdr_buf->name_len + hdr_buf->value_len + 2;
if (hdr_buf->name_len == strlen(header->name) &&
(strncasecmp(hdr_buf->name, header->name, hdr_buf->name_len) == 0)) {
if (ctx->count == ARRAY_SIZE(ctx->headers)) {
LOG_DBG("Header '%s' dropped: not enough slots", header->name);
ctx->status = HTTP_HEADER_STATUS_DROPPED;
break;
}
if (remaining < required_len) {
LOG_DBG("Header '%s' dropped: buffer too small", header->name);
ctx->status = HTTP_HEADER_STATUS_DROPPED;
break;
}
/* Copy header name from user-registered header to make HTTP1/HTTP2
* transparent to the user - they do not need a case-insensitive comparison
* to check which header was matched.
*/
memcpy(dest, header->name, hdr_buf->name_len);
dest[hdr_buf->name_len] = '\0';
current_header->name = dest;
ctx->cursor += (hdr_buf->name_len + 1);
dest += (hdr_buf->name_len + 1);
/* Copy header value */
memcpy(dest, hdr_buf->value, hdr_buf->value_len);
dest[hdr_buf->value_len] = '\0';
current_header->value = dest;
ctx->cursor += (hdr_buf->value_len + 1);
ctx->count++;
break;
}
}
}
static int process_header(struct http_client_ctx *client,
struct http_hpack_header_buf *header)
{
if (IS_ENABLED(CONFIG_HTTP_SERVER_CAPTURE_HEADERS)) {
check_user_request_headers_http2(&client->header_capture_ctx, header);
}
if (header->name_len == (sizeof(":method") - 1) &&
memcmp(header->name, ":method", header->name_len) == 0) {
const enum http_method supported_methods[] = {
HTTP_GET, HTTP_DELETE, HTTP_POST, HTTP_PUT, HTTP_PATCH
};
for (int i = 0; i < ARRAY_SIZE(supported_methods); i++) {
if ((header->value_len ==
strlen(http_method_str(supported_methods[i]))) &&
(memcmp(header->value,
http_method_str(supported_methods[i]),
header->value_len) == 0)) {
client->method = supported_methods[i];
goto out;
}
}
/* Unknown method */
return -EBADMSG;
} else if (header->name_len == (sizeof(":path") - 1) &&
memcmp(header->name, ":path", header->name_len) == 0) {
if (header->value_len > sizeof(client->url_buffer) - 1) {
/* URL too long to handle */
return -ENOBUFS;
}
memcpy(client->url_buffer, header->value, header->value_len);
client->url_buffer[header->value_len] = '\0';
} else if (header->name_len == (sizeof("content-type") - 1) &&
memcmp(header->name, "content-type", header->name_len) == 0) {
if (header->value_len > sizeof(client->content_type) - 1) {
/* Content-type too long to handle */
return -ENOBUFS;
}
memcpy(client->content_type, header->value, header->value_len);
client->content_type[header->value_len] = '\0';
} else if (header->name_len == (sizeof("content-length") - 1) &&
memcmp(header->name, "content-length", header->name_len) == 0) {
char len_str[16] = { 0 };
char *endptr;
unsigned long len;
memcpy(len_str, header->value, MIN(sizeof(len_str), header->value_len));
len_str[sizeof(len_str) - 1] = '\0';
len = strtoul(len_str, &endptr, 10);
if (*endptr != '\0') {
return -EINVAL;
}
client->content_len = (size_t)len;
} else {
/* Just ignore for now. */
LOG_DBG("Ignoring field %.*s", (int)header->name_len, header->name);
}
out:
return 0;
}
static int handle_incomplete_http_header(struct http_client_ctx *client)
{
struct http2_frame *frame = &client->current_frame;
size_t extra_len, prev_frame_len;
int ret;
if (client->data_len < frame->length) {
/* Still did not receive entire frame content */
return -EAGAIN;
}
if (!client->expect_continuation) {
/* Failed to parse header field while the frame is complete
* and no continuation frame is expected - report protocol
* error.
*/
LOG_ERR("Incomplete header field");
return -EBADMSG;
}
/* A header field can be split between two frames, i. e. header and
* continuation or two continuation frames. Because of this, the
* continuation frame header can be present in the stream in between
* header field data, so in such case we need to check for header here
* and remove it from the stream to unblock further processing of the
* header field.
*/
prev_frame_len = frame->length;
extra_len = client->data_len - frame->length;
ret = parse_http_frame_header(client, client->cursor + prev_frame_len,
extra_len);
if (ret < 0) {
return -EAGAIN;
}
/* Continuation frame expected. */
if (frame->type != HTTP2_CONTINUATION_FRAME) {
LOG_ERR("Continuation frame expected");
return -EBADMSG;
}
print_http_frames(client);
/* Now remove continuation frame header from the stream, and proceed
* with processing.
*/
extra_len -= HTTP2_FRAME_HEADER_SIZE;
client->data_len -= HTTP2_FRAME_HEADER_SIZE;
frame->length += prev_frame_len;
memmove(client->cursor + prev_frame_len,
client->cursor + prev_frame_len + HTTP2_FRAME_HEADER_SIZE,
extra_len);
return enter_http_frame_continuation_state(client);
}
static int handle_http_frame_headers_end_stream(struct http_client_ctx *client)
{
struct http2_frame *frame = &client->current_frame;
struct http_request_ctx request_ctx;
struct http_response_ctx response_ctx;
int ret = 0;
if (client->current_stream == NULL) {
return -ENOENT;
}
if (client->current_stream->current_detail == NULL) {
goto out;
}
if (client->current_stream->current_detail->type == HTTP_RESOURCE_TYPE_DYNAMIC) {
struct http_resource_detail_dynamic *dynamic_detail =
(struct http_resource_detail_dynamic *)
client->current_stream->current_detail;
memset(&response_ctx, 0, sizeof(response_ctx));
populate_request_ctx(&request_ctx, NULL, 0, NULL);
ret = dynamic_detail->cb(client, HTTP_SERVER_DATA_FINAL, &request_ctx,
&response_ctx, dynamic_detail->user_data);
if (ret < 0) {
dynamic_detail->holder = NULL;
goto out;
}
/* Force end stream */
response_ctx.final_chunk = true;
ret = http2_dynamic_response(client, frame, &response_ctx, HTTP_SERVER_DATA_FINAL,
dynamic_detail);
dynamic_detail->holder = NULL;
if (ret < 0) {
goto out;
}
}
if (!client->current_stream->headers_sent) {
ret = send_headers_frame(client, HTTP_200_OK, frame->stream_identifier,
client->current_stream->current_detail,
HTTP2_FLAG_END_STREAM, NULL, 0);
if (ret < 0) {
LOG_DBG("Cannot write to socket (%d)", ret);
goto out;
}
} else if (!client->current_stream->end_stream_sent) {
ret = send_data_frame(client, NULL, 0, frame->stream_identifier,
HTTP2_FLAG_END_STREAM);
if (ret < 0) {
LOG_DBG("Cannot send last frame (%d)", ret);
}
}
client->current_stream->current_detail = NULL;
out:
release_http_stream_context(client, frame->stream_identifier);
return ret;
}
int handle_http_frame_headers(struct http_client_ctx *client)
{
struct http2_frame *frame = &client->current_frame;
struct http_resource_detail *detail;
int ret, path_len;
LOG_DBG("HTTP_SERVER_FRAME_HEADERS");
if (is_header_flag_set(frame->flags, HTTP2_FLAG_PADDED)) {
ret = parse_http_frame_padded_field(client);
if (ret < 0) {
goto error;
}
}
if (is_header_flag_set(frame->flags, HTTP2_FLAG_PRIORITY)) {
ret = parse_http_frame_priority_field(client);
if (ret < 0) {
goto error;
}
}
while (frame->length > 0) {
struct http_hpack_header_buf *header = &client->header_field;
size_t datalen = MIN(client->data_len, frame->length);
ret = http_hpack_decode_header(client->cursor, datalen, header);
if (ret <= 0) {
if (ret == -EAGAIN) {
ret = handle_incomplete_http_header(client);
} else if (ret == 0) {
ret = -EBADMSG;
}
if (ret < 0) {
goto error;
}
return 0;
}
if (ret > frame->length) {
LOG_ERR("Protocol error, frame length exceeded");
ret = -EBADMSG;
goto error;
}
frame->length -= ret;
client->cursor += ret;
client->data_len -= ret;
LOG_DBG("Parsed header: %.*s %.*s", (int)header->name_len,
header->name, (int)header->value_len, header->value);
ret = process_header(client, header);
if (ret < 0) {
goto error;
}
}
if (client->expect_continuation) {
/* More headers to come in the continuation frame. */
client->server_state = HTTP_SERVER_FRAME_HEADER_STATE;
return 0;
}
detail = get_resource_detail(client->service, client->url_buffer, &path_len, false);
if (detail != NULL) {
detail->path_len = path_len;
if (detail->type == HTTP_RESOURCE_TYPE_STATIC) {
ret = handle_http2_static_resource(
(struct http_resource_detail_static *)detail,
frame, client);
if (ret < 0) {
goto error;
}
} else if (detail->type == HTTP_RESOURCE_TYPE_STATIC_FS) {
ret = handle_http2_static_fs_resource(
(struct http_resource_detail_static_fs *)detail, frame, client);
if (ret < 0) {
goto error;
}
} else if (detail->type == HTTP_RESOURCE_TYPE_DYNAMIC) {
ret = handle_http2_dynamic_resource(
(struct http_resource_detail_dynamic *)detail,
frame, client);
if (ret < 0) {
goto error;
}
}
} else {
ret = send_http2_404(client, frame);
if (ret < 0) {
goto error;
}
}
if (is_header_flag_set(frame->flags, HTTP2_FLAG_END_STREAM)) {
ret = handle_http_frame_headers_end_stream(client);
if (ret < 0) {
goto error;
}
}
if (frame->padding_len > 0) {
client->server_state = HTTP_SERVER_FRAME_PADDING_STATE;
} else {
client->server_state = HTTP_SERVER_FRAME_HEADER_STATE;
}
return 0;
error:
if (ret != -EAGAIN && client->current_stream &&
!client->current_stream->headers_sent) {
send_http2_500(client, frame, -ret);
}
return ret;
}
int handle_http_frame_priority(struct http_client_ctx *client)
{
struct http2_frame *frame = &client->current_frame;
LOG_DBG("HTTP_SERVER_FRAME_PRIORITY_STATE");
if (frame->length != HTTP2_PRIORITY_FRAME_LEN) {
return -EBADMSG;
}
if (client->data_len < frame->length) {
return -EAGAIN;
}
/* Priority signalling is deprecated by RFC 9113, however it still
* should be expected to receive, just drop the bytes.
*/
client->data_len -= HTTP2_PRIORITY_FRAME_LEN;
client->cursor += HTTP2_PRIORITY_FRAME_LEN;
client->server_state = HTTP_SERVER_FRAME_HEADER_STATE;
return 0;
}
int handle_http_frame_rst_stream(struct http_client_ctx *client)
{
struct http2_frame *frame = &client->current_frame;
struct http2_stream_ctx *stream_ctx;
uint32_t error_code;
LOG_DBG("FRAME_RST_STREAM");
if (frame->length != HTTP2_RST_STREAM_FRAME_LEN) {
return -EBADMSG;
}
if (client->data_len < frame->length) {
return -EAGAIN;
}
if (frame->stream_identifier == 0) {
return -EBADMSG;
}
stream_ctx = find_http_stream_context(client, frame->stream_identifier);
if (stream_ctx == NULL) {
return -EBADMSG;
}
error_code = sys_get_be32(client->cursor);
LOG_DBG("Stream %u reset with error code %u", stream_ctx->stream_id,
error_code);
release_http_stream_context(client, stream_ctx->stream_id);
client->data_len -= HTTP2_RST_STREAM_FRAME_LEN;
client->cursor += HTTP2_RST_STREAM_FRAME_LEN;
client->server_state = HTTP_SERVER_FRAME_HEADER_STATE;
return 0;
}
int handle_http_frame_settings(struct http_client_ctx *client)
{
struct http2_frame *frame = &client->current_frame;
int bytes_consumed;
LOG_DBG("HTTP_SERVER_FRAME_SETTINGS");
if (client->data_len < frame->length) {
return -EAGAIN;
}
bytes_consumed = client->current_frame.length;
client->data_len -= bytes_consumed;
client->cursor += bytes_consumed;
if (!is_header_flag_set(frame->flags, HTTP2_FLAG_SETTINGS_ACK)) {
int ret;
ret = send_settings_frame(client, true);
if (ret < 0) {
LOG_DBG("Cannot write to socket (%d)", ret);
return ret;
}
}
client->server_state = HTTP_SERVER_FRAME_HEADER_STATE;
return 0;
}
int handle_http_frame_goaway(struct http_client_ctx *client)
{
struct http2_frame *frame = &client->current_frame;
int bytes_consumed;
LOG_DBG("HTTP_SERVER_FRAME_GOAWAY");
if (client->data_len < frame->length) {
return -EAGAIN;
}
bytes_consumed = client->current_frame.length;
client->data_len -= bytes_consumed;
client->cursor += bytes_consumed;
enter_http_done_state(client);
return 0;
}
int handle_http_frame_window_update(struct http_client_ctx *client)
{
struct http2_frame *frame = &client->current_frame;
int bytes_consumed;
LOG_DBG("HTTP_SERVER_FRAME_WINDOW_UPDATE");
/* TODO Implement flow control, for now just ignore. */
if (client->data_len < frame->length) {
return -EAGAIN;
}
bytes_consumed = client->current_frame.length;
client->data_len -= bytes_consumed;
client->cursor += bytes_consumed;
client->server_state = HTTP_SERVER_FRAME_HEADER_STATE;
return 0;
}
int handle_http_frame_continuation(struct http_client_ctx *client)
{
LOG_DBG("HTTP_SERVER_FRAME_CONTINUATION_STATE");
client->server_state = HTTP_SERVER_FRAME_HEADERS_STATE;
return 0;
}
int handle_http_frame_padding(struct http_client_ctx *client)
{
struct http2_frame *frame = &client->current_frame;
size_t bytes_consumed;
if (client->data_len == 0) {
return -EAGAIN;
}
bytes_consumed = MIN(client->data_len, frame->padding_len);
client->data_len -= bytes_consumed;
client->cursor += bytes_consumed;
frame->padding_len -= bytes_consumed;
if (frame->padding_len == 0) {
client->server_state = HTTP_SERVER_FRAME_HEADER_STATE;
}
return 0;
}
const char *get_frame_type_name(enum http2_frame_type type)
{
switch (type) {
case HTTP2_DATA_FRAME:
return "DATA";
case HTTP2_HEADERS_FRAME:
return "HEADERS";
case HTTP2_PRIORITY_FRAME:
return "PRIORITY";
case HTTP2_RST_STREAM_FRAME:
return "RST_STREAM";
case HTTP2_SETTINGS_FRAME:
return "SETTINGS";
case HTTP2_PUSH_PROMISE_FRAME:
return "PUSH_PROMISE";
case HTTP2_PING_FRAME:
return "PING";
case HTTP2_GOAWAY_FRAME:
return "GOAWAY";
case HTTP2_WINDOW_UPDATE_FRAME:
return "WINDOW_UPDATE";
case HTTP2_CONTINUATION_FRAME:
return "CONTINUATION";
default:
return "UNKNOWN";
}
}
int parse_http_frame_header(struct http_client_ctx *client, const uint8_t *buffer,
size_t buflen)
{
struct http2_frame *frame = &client->current_frame;
if (buflen < HTTP2_FRAME_HEADER_SIZE) {
return -EAGAIN;
}
frame->length = sys_get_be24(&buffer[HTTP2_FRAME_LENGTH_OFFSET]);
frame->type = buffer[HTTP2_FRAME_TYPE_OFFSET];
frame->flags = buffer[HTTP2_FRAME_FLAGS_OFFSET];
frame->stream_identifier = sys_get_be32(
&buffer[HTTP2_FRAME_STREAM_ID_OFFSET]);
frame->stream_identifier &= HTTP2_FRAME_STREAM_ID_MASK;
frame->padding_len = 0;
LOG_DBG("Frame len %d type 0x%02x flags 0x%02x id %d",
frame->length, frame->type, frame->flags, frame->stream_identifier);
return 0;
}