aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/http/http1.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/http/http1.c')
-rw-r--r--src/plugins/http/http1.c1750
1 files changed, 1750 insertions, 0 deletions
diff --git a/src/plugins/http/http1.c b/src/plugins/http/http1.c
new file mode 100644
index 00000000000..44dd099ccee
--- /dev/null
+++ b/src/plugins/http/http1.c
@@ -0,0 +1,1750 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright(c) 2025 Cisco Systems, Inc.
+ */
+
+#include <vnet/session/application.h>
+
+#include <http/http.h>
+#include <http/http_header_names.h>
+#include <http/http_private.h>
+#include <http/http_status_codes.h>
+#include <http/http_timer.h>
+
+const char *http1_upgrade_proto_str[] = { "",
+#define _(sym, str) str,
+ foreach_http_upgrade_proto
+#undef _
+};
+
+/**
+ * http error boilerplate
+ */
+static const char *error_template = "HTTP/1.1 %s\r\n"
+ "Date: %U GMT\r\n"
+ "Connection: close\r\n"
+ "Content-Length: 0\r\n\r\n";
+
+/**
+ * http response boilerplate
+ */
+static const char *response_template = "HTTP/1.1 %s\r\n"
+ "Date: %U GMT\r\n"
+ "Server: %v\r\n";
+
+static const char *content_len_template = "Content-Length: %llu\r\n";
+
+static const char *connection_upgrade_template = "Connection: upgrade\r\n"
+ "Upgrade: %s\r\n";
+
+/**
+ * http request boilerplate
+ */
+static const char *get_request_template = "GET %s HTTP/1.1\r\n"
+ "Host: %v\r\n"
+ "User-Agent: %v\r\n";
+
+static const char *post_request_template = "POST %s HTTP/1.1\r\n"
+ "Host: %v\r\n"
+ "User-Agent: %v\r\n"
+ "Content-Length: %llu\r\n";
+
+static void
+http1_send_error (http_conn_t *hc, http_status_code_t ec,
+ transport_send_params_t *sp)
+{
+ u8 *data;
+
+ if (ec >= HTTP_N_STATUS)
+ ec = HTTP_STATUS_INTERNAL_ERROR;
+
+ data = format (0, error_template, http_status_code_str[ec],
+ format_http_time_now, hc);
+ HTTP_DBG (3, "%v", data);
+ http_io_ts_write (hc, data, vec_len (data), sp);
+ vec_free (data);
+ http_io_ts_after_write (hc, sp, 0, 1);
+}
+
+static int
+http1_read_message (http_conn_t *hc, u8 *rx_buf)
+{
+ u32 max_deq;
+
+ max_deq = http_io_ts_max_read (hc);
+ if (PREDICT_FALSE (max_deq == 0))
+ return -1;
+
+ vec_validate (rx_buf, max_deq - 1);
+ http_io_ts_read (hc, rx_buf, max_deq, 1);
+
+ return 0;
+}
+
+static void
+http1_identify_optional_query (http_req_t *req, u8 *rx_buf)
+{
+ int i;
+ for (i = req->target_path_offset;
+ i < (req->target_path_offset + req->target_path_len); i++)
+ {
+ if (rx_buf[i] == '?')
+ {
+ req->target_query_offset = i + 1;
+ req->target_query_len = req->target_path_offset +
+ req->target_path_len -
+ req->target_query_offset;
+ req->target_path_len =
+ req->target_path_len - req->target_query_len - 1;
+ break;
+ }
+ }
+}
+
+static int
+http1_parse_target (http_req_t *req, u8 *rx_buf)
+{
+ int i;
+ u8 *p, *end;
+
+ /* asterisk-form = "*" */
+ if ((rx_buf[req->target_path_offset] == '*') && (req->target_path_len == 1))
+ {
+ req->target_form = HTTP_TARGET_ASTERISK_FORM;
+ /* we do not support OPTIONS request */
+ return -1;
+ }
+
+ /* origin-form = 1*( "/" segment ) [ "?" query ] */
+ if (rx_buf[req->target_path_offset] == '/')
+ {
+ /* drop leading slash */
+ req->target_path_len--;
+ req->target_path_offset++;
+ req->target_form = HTTP_TARGET_ORIGIN_FORM;
+ http1_identify_optional_query (req, rx_buf);
+ /* can't be CONNECT method */
+ return req->method == HTTP_REQ_CONNECT ? -1 : 0;
+ }
+
+ /* absolute-form =
+ * scheme "://" host [ ":" port ] *( "/" segment ) [ "?" query ] */
+ if (req->target_path_len > 8 &&
+ !memcmp (rx_buf + req->target_path_offset, "http", 4))
+ {
+ req->scheme = HTTP_URL_SCHEME_HTTP;
+ p = rx_buf + req->target_path_offset + 4;
+ if (*p == 's')
+ {
+ p++;
+ req->scheme = HTTP_URL_SCHEME_HTTPS;
+ }
+ if (*p++ == ':')
+ {
+ expect_char ('/');
+ expect_char ('/');
+ req->target_form = HTTP_TARGET_ABSOLUTE_FORM;
+ req->target_authority_offset = p - rx_buf;
+ req->target_authority_len = 0;
+ end = rx_buf + req->target_path_offset + req->target_path_len;
+ while (p < end)
+ {
+ if (*p == '/')
+ {
+ p++; /* drop leading slash */
+ req->target_path_offset = p - rx_buf;
+ req->target_path_len = end - p;
+ break;
+ }
+ req->target_authority_len++;
+ p++;
+ }
+ if (!req->target_path_len)
+ {
+ clib_warning ("zero length host");
+ return -1;
+ }
+ http1_identify_optional_query (req, rx_buf);
+ /* can't be CONNECT method */
+ return req->method == HTTP_REQ_CONNECT ? -1 : 0;
+ }
+ }
+
+ /* authority-form = host ":" port */
+ for (i = req->target_path_offset;
+ i < (req->target_path_offset + req->target_path_len); i++)
+ {
+ if ((rx_buf[i] == ':') && (isdigit (rx_buf[i + 1])))
+ {
+ req->target_authority_len = req->target_path_len;
+ req->target_path_len = 0;
+ req->target_authority_offset = req->target_path_offset;
+ req->target_path_offset = 0;
+ req->target_form = HTTP_TARGET_AUTHORITY_FORM;
+ /* "authority-form" is only used for CONNECT requests */
+ return req->method == HTTP_REQ_CONNECT ? 0 : -1;
+ }
+ }
+
+ return -1;
+}
+
+static int
+http1_parse_request_line (http_req_t *req, u8 *rx_buf, http_status_code_t *ec)
+{
+ int i, target_len;
+ u32 next_line_offset, method_offset;
+
+ /* request-line = method SP request-target SP HTTP-version CRLF */
+ i = http_v_find_index (rx_buf, 8, 0, "\r\n");
+ if (i < 0)
+ {
+ clib_warning ("request line incomplete");
+ *ec = HTTP_STATUS_BAD_REQUEST;
+ return -1;
+ }
+ HTTP_DBG (2, "request line length: %d", i);
+ req->control_data_len = i + 2;
+ next_line_offset = req->control_data_len;
+
+ /* there should be at least one more CRLF */
+ if (vec_len (rx_buf) < (next_line_offset + 2))
+ {
+ clib_warning ("malformed message, too short");
+ *ec = HTTP_STATUS_BAD_REQUEST;
+ return -1;
+ }
+
+ /*
+ * RFC9112 2.2:
+ * In the interest of robustness, a server that is expecting to receive and
+ * parse a request-line SHOULD ignore at least one empty line (CRLF)
+ * received prior to the request-line.
+ */
+ method_offset = rx_buf[0] == '\r' && rx_buf[1] == '\n' ? 2 : 0;
+ /* parse method */
+ if (!memcmp (rx_buf + method_offset, "GET ", 4))
+ {
+ HTTP_DBG (0, "GET method");
+ req->method = HTTP_REQ_GET;
+ req->target_path_offset = method_offset + 4;
+ }
+ else if (!memcmp (rx_buf + method_offset, "POST ", 5))
+ {
+ HTTP_DBG (0, "POST method");
+ req->method = HTTP_REQ_POST;
+ req->target_path_offset = method_offset + 5;
+ }
+ else if (!memcmp (rx_buf + method_offset, "CONNECT ", 8))
+ {
+ HTTP_DBG (0, "CONNECT method");
+ req->method = HTTP_REQ_CONNECT;
+ req->upgrade_proto = HTTP_UPGRADE_PROTO_NA;
+ req->target_path_offset = method_offset + 8;
+ req->is_tunnel = 1;
+ }
+ else
+ {
+ if (rx_buf[method_offset] - 'A' <= 'Z' - 'A')
+ {
+ *ec = HTTP_STATUS_NOT_IMPLEMENTED;
+ return -1;
+ }
+ else
+ {
+ *ec = HTTP_STATUS_BAD_REQUEST;
+ return -1;
+ }
+ }
+
+ /* find version */
+ i = http_v_find_index (rx_buf, next_line_offset - 11, 11, " HTTP/");
+ if (i < 0)
+ {
+ clib_warning ("HTTP version not present");
+ *ec = HTTP_STATUS_BAD_REQUEST;
+ return -1;
+ }
+ /* verify major version */
+ if (isdigit (rx_buf[i + 6]))
+ {
+ if (rx_buf[i + 6] != '1')
+ {
+ clib_warning ("HTTP major version '%c' not supported",
+ rx_buf[i + 6]);
+ *ec = HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED;
+ return -1;
+ }
+ }
+ else
+ {
+ clib_warning ("HTTP major version '%c' is not digit", rx_buf[i + 6]);
+ *ec = HTTP_STATUS_BAD_REQUEST;
+ return -1;
+ }
+
+ /* parse request-target */
+ HTTP_DBG (2, "http at %d", i);
+ target_len = i - req->target_path_offset;
+ HTTP_DBG (2, "target_len %d", target_len);
+ if (target_len < 1)
+ {
+ clib_warning ("request-target not present");
+ *ec = HTTP_STATUS_BAD_REQUEST;
+ return -1;
+ }
+ req->target_path_len = target_len;
+ req->target_query_offset = 0;
+ req->target_query_len = 0;
+ req->target_authority_len = 0;
+ req->target_authority_offset = 0;
+ if (http1_parse_target (req, rx_buf))
+ {
+ clib_warning ("invalid target");
+ *ec = HTTP_STATUS_BAD_REQUEST;
+ return -1;
+ }
+ HTTP_DBG (2, "request-target path length: %u", req->target_path_len);
+ HTTP_DBG (2, "request-target path offset: %u", req->target_path_offset);
+ HTTP_DBG (2, "request-target query length: %u", req->target_query_len);
+ HTTP_DBG (2, "request-target query offset: %u", req->target_query_offset);
+
+ /* set buffer offset to nex line start */
+ req->rx_buf_offset = next_line_offset;
+
+ return 0;
+}
+
+static int
+http1_parse_status_line (http_req_t *req, u8 *rx_buf)
+{
+ int i;
+ u32 next_line_offset;
+ u8 *p, *end;
+ u16 status_code = 0;
+
+ i = http_v_find_index (rx_buf, 0, 0, "\r\n");
+ /* status-line = HTTP-version SP status-code SP [ reason-phrase ] CRLF */
+ if (i < 0)
+ {
+ clib_warning ("status line incomplete");
+ return -1;
+ }
+ HTTP_DBG (2, "status line length: %d", i);
+ if (i < 12)
+ {
+ clib_warning ("status line too short (%d)", i);
+ return -1;
+ }
+ req->control_data_len = i + 2;
+ next_line_offset = req->control_data_len;
+ p = rx_buf;
+ end = rx_buf + i;
+
+ /* there should be at least one more CRLF */
+ if (vec_len (rx_buf) < (next_line_offset + 2))
+ {
+ clib_warning ("malformed message, too short");
+ return -1;
+ }
+
+ /* parse version */
+ expect_char ('H');
+ expect_char ('T');
+ expect_char ('T');
+ expect_char ('P');
+ expect_char ('/');
+ expect_char ('1');
+ expect_char ('.');
+ if (!isdigit (*p++))
+ {
+ clib_warning ("invalid HTTP minor version");
+ return -1;
+ }
+
+ /* skip space(s) */
+ if (*p != ' ')
+ {
+ clib_warning ("no space after HTTP version");
+ return -1;
+ }
+ do
+ {
+ p++;
+ if (p == end)
+ {
+ clib_warning ("no status code");
+ return -1;
+ }
+ }
+ while (*p == ' ');
+
+ /* parse status code */
+ if ((end - p) < 3)
+ {
+ clib_warning ("not enough characters for status code");
+ return -1;
+ }
+ parse_int (status_code, 100);
+ parse_int (status_code, 10);
+ parse_int (status_code, 1);
+ if (status_code < 100 || status_code > 599)
+ {
+ clib_warning ("invalid status code %d", status_code);
+ return -1;
+ }
+ req->status_code = http_sc_by_u16 (status_code);
+ HTTP_DBG (0, "status code: %d", status_code);
+
+ /* set buffer offset to nex line start */
+ req->rx_buf_offset = next_line_offset;
+
+ return 0;
+}
+
+always_inline int
+http1_parse_field_name (u8 **pos, u8 *end, u8 **field_name_start,
+ u32 *field_name_len)
+{
+ u32 name_len = 0;
+ u8 *p;
+
+ static uword tchar[4] = {
+ /* !#$%'*+-.0123456789 */
+ 0x03ff6cba00000000,
+ /* ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`abcdefghijklmnopqrstuvwxyz|~ */
+ 0x57ffffffc7fffffe,
+ 0x0000000000000000,
+ 0x0000000000000000,
+ };
+
+ p = *pos;
+
+ *field_name_start = p;
+ while (p != end)
+ {
+ if (clib_bitmap_get_no_check (tchar, *p))
+ {
+ name_len++;
+ p++;
+ }
+ else if (*p == ':')
+ {
+ if (name_len == 0)
+ {
+ clib_warning ("empty field name");
+ return -1;
+ }
+ *field_name_len = name_len;
+ p++;
+ *pos = p;
+ return 0;
+ }
+ else
+ {
+ clib_warning ("invalid character %d", *p);
+ return -1;
+ }
+ }
+ clib_warning ("field name end not found");
+ return -1;
+}
+
+always_inline int
+http1_parse_field_value (u8 **pos, u8 *end, u8 **field_value_start,
+ u32 *field_value_len)
+{
+ u32 value_len = 0;
+ u8 *p;
+
+ p = *pos;
+
+ /* skip leading whitespace */
+ while (1)
+ {
+ if (p == end)
+ {
+ clib_warning ("field value not found");
+ return -1;
+ }
+ else if (*p != ' ' && *p != '\t')
+ {
+ break;
+ }
+ p++;
+ }
+
+ *field_value_start = p;
+ while (p != end)
+ {
+ if (*p == '\r')
+ {
+ if ((end - p) < 1)
+ {
+ clib_warning ("incorrect field line end");
+ return -1;
+ }
+ p++;
+ if (*p == '\n')
+ {
+ if (value_len == 0)
+ {
+ clib_warning ("empty field value");
+ return -1;
+ }
+ p++;
+ *pos = p;
+ /* skip trailing whitespace */
+ p = *field_value_start + value_len - 1;
+ while (*p == ' ' || *p == '\t')
+ {
+ p--;
+ value_len--;
+ }
+ *field_value_len = value_len;
+ return 0;
+ }
+ clib_warning ("CR without LF");
+ return -1;
+ }
+ if (*p < ' ' && *p != '\t')
+ {
+ clib_warning ("invalid character %d", *p);
+ return -1;
+ }
+ p++;
+ value_len++;
+ }
+
+ clib_warning ("field value end not found");
+ return -1;
+}
+
+static int
+http1_identify_headers (http_req_t *req, u8 *rx_buf, http_status_code_t *ec)
+{
+ int rv;
+ u8 *p, *end, *name_start, *value_start;
+ u32 name_len, value_len;
+ http_field_line_t *field_line;
+ uword header_index;
+
+ vec_reset_length (req->headers);
+ req->content_len_header_index = ~0;
+ req->connection_header_index = ~0;
+ req->upgrade_header_index = ~0;
+ req->host_header_index = ~0;
+ req->headers_offset = req->rx_buf_offset;
+
+ /* check if we have any header */
+ if ((rx_buf[req->rx_buf_offset] == '\r') &&
+ (rx_buf[req->rx_buf_offset + 1] == '\n'))
+ {
+ /* just another CRLF -> no headers */
+ HTTP_DBG (2, "no headers");
+ req->headers_len = 0;
+ req->control_data_len += 2;
+ return 0;
+ }
+
+ end = vec_end (rx_buf);
+ p = rx_buf + req->rx_buf_offset;
+
+ while (1)
+ {
+ rv = http1_parse_field_name (&p, end, &name_start, &name_len);
+ if (rv != 0)
+ {
+ *ec = HTTP_STATUS_BAD_REQUEST;
+ return -1;
+ }
+ rv = http1_parse_field_value (&p, end, &value_start, &value_len);
+ if (rv != 0 || (end - p) < 2)
+ {
+ *ec = HTTP_STATUS_BAD_REQUEST;
+ return -1;
+ }
+
+ vec_add2 (req->headers, field_line, 1);
+ field_line->name_offset = (name_start - rx_buf) - req->headers_offset;
+ field_line->name_len = name_len;
+ field_line->value_offset = (value_start - rx_buf) - req->headers_offset;
+ field_line->value_len = value_len;
+ header_index = field_line - req->headers;
+
+ /* find headers that will be used later in preprocessing */
+ /* names are case-insensitive (RFC9110 section 5.1) */
+ if (req->content_len_header_index == ~0 &&
+ http_token_is_case (
+ (const char *) name_start, name_len,
+ http_header_name_token (HTTP_HEADER_CONTENT_LENGTH)))
+ req->content_len_header_index = header_index;
+ else if (req->connection_header_index == ~0 &&
+ http_token_is_case (
+ (const char *) name_start, name_len,
+ http_header_name_token (HTTP_HEADER_CONNECTION)))
+ req->connection_header_index = header_index;
+ else if (req->upgrade_header_index == ~0 &&
+ http_token_is_case (
+ (const char *) name_start, name_len,
+ http_header_name_token (HTTP_HEADER_UPGRADE)))
+ req->upgrade_header_index = header_index;
+ else if (req->host_header_index == ~0 &&
+ http_token_is_case ((const char *) name_start, name_len,
+ http_header_name_token (HTTP_HEADER_HOST)))
+ req->host_header_index = header_index;
+
+ /* are we done? */
+ if (*p == '\r' && *(p + 1) == '\n')
+ break;
+ }
+
+ req->headers_len = p - (rx_buf + req->headers_offset);
+ req->control_data_len += (req->headers_len + 2);
+ HTTP_DBG (2, "headers length: %u", req->headers_len);
+ HTTP_DBG (2, "headers offset: %u", req->headers_offset);
+
+ return 0;
+}
+
+static int
+http1_identify_message_body (http_req_t *req, u8 *rx_buf,
+ http_status_code_t *ec)
+{
+ int i;
+ u8 *p;
+ u64 body_len = 0, digit;
+ http_field_line_t *field_line;
+
+ req->body_len = 0;
+
+ if (req->headers_len == 0)
+ {
+ HTTP_DBG (2, "no header, no message-body");
+ return 0;
+ }
+ if (req->is_tunnel)
+ {
+ HTTP_DBG (2, "tunnel, no message-body");
+ return 0;
+ }
+
+ /* TODO check for chunked transfer coding */
+
+ if (req->content_len_header_index == ~0)
+ {
+ HTTP_DBG (2, "Content-Length header not present, no message-body");
+ return 0;
+ }
+ field_line = vec_elt_at_index (req->headers, req->content_len_header_index);
+
+ p = rx_buf + req->headers_offset + field_line->value_offset;
+ for (i = 0; i < field_line->value_len; i++)
+ {
+ /* check for digit */
+ if (!isdigit (*p))
+ {
+ clib_warning ("expected digit");
+ *ec = HTTP_STATUS_BAD_REQUEST;
+ return -1;
+ }
+ digit = *p - '0';
+ u64 new_body_len = body_len * 10 + digit;
+ /* check for overflow */
+ if (new_body_len < body_len)
+ {
+ clib_warning ("too big number, overflow");
+ *ec = HTTP_STATUS_BAD_REQUEST;
+ return -1;
+ }
+ body_len = new_body_len;
+ p++;
+ }
+
+ req->body_len = body_len;
+
+ req->body_offset = req->headers_offset + req->headers_len + 2;
+ HTTP_DBG (2, "body length: %llu", req->body_len);
+ HTTP_DBG (2, "body offset: %u", req->body_offset);
+
+ return 0;
+}
+
+static void
+http1_check_connection_upgrade (http_req_t *req, u8 *rx_buf)
+{
+ http_field_line_t *connection, *upgrade;
+ u8 skip;
+
+ skip = (req->method != HTTP_REQ_GET) + (req->connection_header_index == ~0) +
+ (req->upgrade_header_index == ~0);
+ if (skip)
+ return;
+
+ connection = vec_elt_at_index (req->headers, req->connection_header_index);
+ /* connection options are case-insensitive (RFC9110 7.6.1) */
+ if (http_token_is_case (
+ http_field_line_value_token (connection, req, rx_buf),
+ http_token_lit ("upgrade")))
+ {
+ upgrade = vec_elt_at_index (req->headers, req->upgrade_header_index);
+
+ /* check upgrade protocol, we want to ignore something like upgrade to
+ * newer HTTP version, only tunnels are supported */
+ if (0)
+ ;
+#define _(sym, str) \
+ else if (http_token_is_case ( \
+ http_field_line_value_token (upgrade, req, rx_buf), \
+ http_token_lit (str))) req->upgrade_proto = \
+ HTTP_UPGRADE_PROTO_##sym;
+ foreach_http_upgrade_proto
+#undef _
+ else return;
+
+ req->is_tunnel = 1;
+ req->method = HTTP_REQ_CONNECT;
+ }
+}
+
+static void
+http1_target_fixup (http_conn_t *hc, http_req_t *req)
+{
+ http_field_line_t *host;
+
+ if (req->target_form == HTTP_TARGET_ABSOLUTE_FORM)
+ return;
+
+ /* scheme fixup */
+ req->scheme = http_get_transport_proto (hc) == TRANSPORT_PROTO_TLS ?
+ HTTP_URL_SCHEME_HTTPS :
+ HTTP_URL_SCHEME_HTTP;
+
+ if (req->target_form == HTTP_TARGET_AUTHORITY_FORM ||
+ req->connection_header_index == ~0)
+ return;
+
+ /* authority fixup */
+ host = vec_elt_at_index (req->headers, req->connection_header_index);
+ req->target_authority_offset = host->value_offset;
+ req->target_authority_len = host->value_len;
+}
+
+static void
+http1_write_app_headers (http_conn_t *hc, http_msg_t *msg, u8 **tx_buf)
+{
+ u8 *app_headers, *p, *end;
+ u32 *tmp;
+
+ /* read app header list */
+ app_headers = http_get_app_header_list (hc, msg);
+
+ /* serialize app headers to tx_buf */
+ end = app_headers + msg->data.headers_len;
+ while (app_headers < end)
+ {
+ /* custom header name? */
+ tmp = (u32 *) app_headers;
+ if (PREDICT_FALSE (*tmp & HTTP_CUSTOM_HEADER_NAME_BIT))
+ {
+ http_custom_token_t *name, *value;
+ name = (http_custom_token_t *) app_headers;
+ u32 name_len = name->len & ~HTTP_CUSTOM_HEADER_NAME_BIT;
+ app_headers += sizeof (http_custom_token_t) + name_len;
+ value = (http_custom_token_t *) app_headers;
+ app_headers += sizeof (http_custom_token_t) + value->len;
+ vec_add2 (*tx_buf, p, name_len + value->len + 4);
+ clib_memcpy (p, name->token, name_len);
+ p += name_len;
+ *p++ = ':';
+ *p++ = ' ';
+ clib_memcpy (p, value->token, value->len);
+ p += value->len;
+ *p++ = '\r';
+ *p++ = '\n';
+ }
+ else
+ {
+ http_app_header_t *header;
+ header = (http_app_header_t *) app_headers;
+ app_headers += sizeof (http_app_header_t) + header->value.len;
+ http_token_t name = { http_header_name_token (header->name) };
+ vec_add2 (*tx_buf, p, name.len + header->value.len + 4);
+ clib_memcpy (p, name.base, name.len);
+ p += name.len;
+ *p++ = ':';
+ *p++ = ' ';
+ clib_memcpy (p, header->value.token, header->value.len);
+ p += header->value.len;
+ *p++ = '\r';
+ *p++ = '\n';
+ }
+ }
+}
+
+/*************************************/
+/* request state machine handlers RX */
+/*************************************/
+
+static http_sm_result_t
+http1_req_state_wait_transport_reply (http_conn_t *hc, http_req_t *req,
+ transport_send_params_t *sp)
+{
+ int rv;
+ http_msg_t msg = {};
+ u32 len, max_enq, body_sent;
+ http_status_code_t ec;
+ u8 *rx_buf;
+
+ rx_buf = http_get_rx_buf (hc);
+ rv = http1_read_message (hc, rx_buf);
+
+ /* Nothing yet, wait for data or timer expire */
+ if (rv)
+ {
+ HTTP_DBG (1, "no data to deq");
+ return HTTP_SM_STOP;
+ }
+
+ HTTP_DBG (3, "%v", rx_buf);
+
+ if (vec_len (rx_buf) < 8)
+ {
+ clib_warning ("response buffer too short");
+ goto error;
+ }
+
+ rv = http1_parse_status_line (req, rx_buf);
+ if (rv)
+ goto error;
+
+ rv = http1_identify_headers (req, rx_buf, &ec);
+ if (rv)
+ goto error;
+
+ rv = http1_identify_message_body (req, rx_buf, &ec);
+ if (rv)
+ goto error;
+
+ /* send at least "control data" which is necessary minimum,
+ * if there is some space send also portion of body */
+ max_enq = http_io_as_max_write (req);
+ max_enq -= sizeof (msg);
+ if (max_enq < req->control_data_len)
+ {
+ clib_warning ("not enough room for control data in app's rx fifo");
+ goto error;
+ }
+ len = clib_min (max_enq, vec_len (rx_buf));
+
+ msg.type = HTTP_MSG_REPLY;
+ msg.code = req->status_code;
+ msg.data.headers_offset = req->headers_offset;
+ msg.data.headers_len = req->headers_len;
+ msg.data.body_offset = req->body_offset;
+ msg.data.body_len = req->body_len;
+ msg.data.type = HTTP_MSG_DATA_INLINE;
+ msg.data.len = len;
+ msg.data.headers_ctx = pointer_to_uword (req->headers);
+
+ svm_fifo_seg_t segs[2] = { { (u8 *) &msg, sizeof (msg) }, { rx_buf, len } };
+
+ http_io_as_write_segs (req, segs, 2);
+
+ body_sent = len - req->control_data_len;
+ req->to_recv = req->body_len - body_sent;
+ if (req->to_recv == 0)
+ {
+ /* all sent, we are done */
+ http_req_state_change (req, HTTP_REQ_STATE_WAIT_APP_METHOD);
+ }
+ else
+ {
+ /* stream rest of the response body */
+ http_req_state_change (req, HTTP_REQ_STATE_TRANSPORT_IO_MORE_DATA);
+ }
+
+ http_io_ts_drain (hc, len);
+ http_io_ts_after_read (hc, 1);
+ http_app_worker_rx_notify (req);
+ return HTTP_SM_STOP;
+
+error:
+ http_io_ts_drain_all (hc);
+ http_io_ts_after_read (hc, 1);
+ session_transport_closing_notify (&hc->connection);
+ session_transport_closed_notify (&hc->connection);
+ http_disconnect_transport (hc);
+ return HTTP_SM_ERROR;
+}
+
+static http_sm_result_t
+http1_req_state_wait_transport_method (http_conn_t *hc, http_req_t *req,
+ transport_send_params_t *sp)
+{
+ http_status_code_t ec;
+ http_msg_t msg;
+ int rv;
+ u32 len, max_enq, body_sent;
+ u64 max_deq;
+ u8 *rx_buf;
+
+ rx_buf = http_get_rx_buf (hc);
+ rv = http1_read_message (hc, rx_buf);
+
+ /* Nothing yet, wait for data or timer expire */
+ if (rv)
+ return HTTP_SM_STOP;
+
+ HTTP_DBG (3, "%v", rx_buf);
+
+ if (vec_len (rx_buf) < 8)
+ {
+ ec = HTTP_STATUS_BAD_REQUEST;
+ goto error;
+ }
+
+ rv = http1_parse_request_line (req, rx_buf, &ec);
+ if (rv)
+ goto error;
+
+ rv = http1_identify_headers (req, rx_buf, &ec);
+ if (rv)
+ goto error;
+
+ http1_target_fixup (hc, req);
+ http1_check_connection_upgrade (req, rx_buf);
+
+ rv = http1_identify_message_body (req, rx_buf, &ec);
+ if (rv)
+ goto error;
+
+ /* send at least "control data" which is necessary minimum,
+ * if there is some space send also portion of body */
+ max_enq = http_io_as_max_write (req);
+ if (max_enq < req->control_data_len)
+ {
+ clib_warning ("not enough room for control data in app's rx fifo");
+ ec = HTTP_STATUS_INTERNAL_ERROR;
+ goto error;
+ }
+ /* do not dequeue more than one HTTP request, we do not support pipelining */
+ max_deq = clib_min (req->control_data_len + req->body_len, vec_len (rx_buf));
+ len = clib_min (max_enq, max_deq);
+
+ msg.type = HTTP_MSG_REQUEST;
+ msg.method_type = req->method;
+ msg.data.type = HTTP_MSG_DATA_INLINE;
+ msg.data.len = len;
+ msg.data.scheme = req->scheme;
+ msg.data.target_authority_offset = req->target_authority_offset;
+ msg.data.target_authority_len = req->target_authority_len;
+ msg.data.target_path_offset = req->target_path_offset;
+ msg.data.target_path_len = req->target_path_len;
+ msg.data.target_query_offset = req->target_query_offset;
+ msg.data.target_query_len = req->target_query_len;
+ msg.data.headers_offset = req->headers_offset;
+ msg.data.headers_len = req->headers_len;
+ msg.data.body_offset = req->body_offset;
+ msg.data.body_len = req->body_len;
+ msg.data.headers_ctx = pointer_to_uword (req->headers);
+ msg.data.upgrade_proto = req->upgrade_proto;
+
+ svm_fifo_seg_t segs[2] = { { (u8 *) &msg, sizeof (msg) }, { rx_buf, len } };
+
+ http_io_as_write_segs (req, segs, 2);
+
+ body_sent = len - req->control_data_len;
+ req->to_recv = req->body_len - body_sent;
+ if (req->to_recv == 0)
+ {
+ /* drop everything, we do not support pipelining */
+ http_io_ts_drain_all (hc);
+ /* all sent, we are done */
+ http_req_state_change (req, HTTP_REQ_STATE_WAIT_APP_REPLY);
+ }
+ else
+ {
+ http_io_ts_drain (hc, len);
+ /* stream rest of the response body */
+ http_req_state_change (req, HTTP_REQ_STATE_TRANSPORT_IO_MORE_DATA);
+ }
+
+ http_app_worker_rx_notify (req);
+ http_io_ts_after_read (hc, 1);
+
+ return HTTP_SM_STOP;
+
+error:
+ http_io_ts_drain_all (hc);
+ http_io_ts_after_read (hc, 1);
+ http1_send_error (hc, ec, 0);
+ session_transport_closing_notify (&hc->connection);
+ http_disconnect_transport (hc);
+
+ return HTTP_SM_ERROR;
+}
+
+static http_sm_result_t
+http1_req_state_transport_io_more_data (http_conn_t *hc, http_req_t *req,
+ transport_send_params_t *sp)
+{
+ u32 max_len, max_deq, max_enq, n_segs = 2;
+ svm_fifo_seg_t segs[n_segs];
+ int n_written;
+
+ max_deq = http_io_ts_max_read (hc);
+ if (max_deq == 0)
+ {
+ HTTP_DBG (1, "no data to deq");
+ return HTTP_SM_STOP;
+ }
+
+ max_enq = http_io_as_max_write (req);
+ if (max_enq == 0)
+ {
+ HTTP_DBG (1, "app's rx fifo full");
+ http_io_as_want_deq_ntf (req);
+ return HTTP_SM_STOP;
+ }
+
+ max_len = clib_min (max_enq, max_deq);
+ http_io_ts_read_segs (hc, segs, &n_segs, max_len);
+
+ n_written = http_io_as_write_segs (req, segs, n_segs);
+
+ if (n_written > req->to_recv)
+ {
+ clib_warning ("http protocol error: received more data than expected");
+ session_transport_closing_notify (&hc->connection);
+ http_disconnect_transport (hc);
+ http_req_state_change (req, HTTP_REQ_STATE_WAIT_APP_METHOD);
+ return HTTP_SM_ERROR;
+ }
+ req->to_recv -= n_written;
+ http_io_ts_drain (hc, n_written);
+ HTTP_DBG (1, "drained %d from ts; remains %lu", n_written, req->to_recv);
+
+ /* Finished transaction:
+ * server back to HTTP_REQ_STATE_WAIT_APP_REPLY
+ * client to HTTP_REQ_STATE_WAIT_APP_METHOD */
+ if (req->to_recv == 0)
+ http_req_state_change (req, hc->is_server ?
+ HTTP_REQ_STATE_WAIT_APP_REPLY :
+ HTTP_REQ_STATE_WAIT_APP_METHOD);
+
+ http_app_worker_rx_notify (req);
+
+ http_io_ts_after_read (hc, 0);
+
+ return HTTP_SM_STOP;
+}
+
+static http_sm_result_t
+http1_req_state_tunnel_rx (http_conn_t *hc, http_req_t *req,
+ transport_send_params_t *sp)
+{
+ u32 max_deq, max_enq, max_read, n_segs = 2;
+ svm_fifo_seg_t segs[n_segs];
+ int n_written = 0;
+
+ HTTP_DBG (1, "tunnel received data from client");
+
+ max_deq = http_io_ts_max_read (hc);
+ if (PREDICT_FALSE (max_deq == 0))
+ {
+ HTTP_DBG (1, "max_deq == 0");
+ return HTTP_SM_STOP;
+ }
+ max_enq = http_io_as_max_write (req);
+ if (max_enq == 0)
+ {
+ HTTP_DBG (1, "app's rx fifo full");
+ http_io_as_want_deq_ntf (req);
+ return HTTP_SM_STOP;
+ }
+ max_read = clib_min (max_enq, max_deq);
+ http_io_ts_read_segs (hc, segs, &n_segs, max_read);
+ n_written = http_io_as_write_segs (req, segs, n_segs);
+ http_io_ts_drain (hc, n_written);
+ HTTP_DBG (1, "transfered %u bytes", n_written);
+ http_app_worker_rx_notify (req);
+ http_io_ts_after_read (hc, 0);
+
+ return HTTP_SM_STOP;
+}
+
+static http_sm_result_t
+http1_req_state_udp_tunnel_rx (http_conn_t *hc, http_req_t *req,
+ transport_send_params_t *sp)
+{
+ u32 to_deq, capsule_size, dgram_size, n_read, n_written = 0;
+ int rv;
+ u8 payload_offset = 0;
+ u64 payload_len = 0;
+ session_dgram_hdr_t hdr;
+ u8 *buf = 0;
+
+ HTTP_DBG (1, "udp tunnel received data from client");
+
+ buf = http_get_rx_buf (hc);
+ to_deq = http_io_ts_max_read (hc);
+
+ while (to_deq > 0)
+ {
+ /* some bytes remaining to skip? */
+ if (PREDICT_FALSE (req->to_skip))
+ {
+ if (req->to_skip >= to_deq)
+ {
+ http_io_ts_drain (hc, to_deq);
+ req->to_skip -= to_deq;
+ goto done;
+ }
+ else
+ {
+ http_io_ts_drain (hc, req->to_skip);
+ req->to_skip = 0;
+ }
+ }
+ n_read = http_io_ts_read (hc, buf, HTTP_CAPSULE_HEADER_MAX_SIZE, 1);
+ rv = http_decap_udp_payload_datagram (buf, n_read, &payload_offset,
+ &payload_len);
+ HTTP_DBG (1, "rv=%d, payload_offset=%u, payload_len=%llu", rv,
+ payload_offset, payload_len);
+ if (PREDICT_FALSE (rv != 0))
+ {
+ if (rv < 0)
+ {
+ /* capsule datagram is invalid (session need to be aborted) */
+ http_io_ts_drain_all (hc);
+ session_transport_closing_notify (&hc->connection);
+ session_transport_closed_notify (&hc->connection);
+ http_disconnect_transport (hc);
+ return HTTP_SM_STOP;
+ }
+ else
+ {
+ /* unknown capsule should be skipped */
+ if (payload_len <= to_deq)
+ {
+ http_io_ts_drain (hc, payload_len);
+ to_deq -= payload_len;
+ continue;
+ }
+ else
+ {
+ http_io_ts_drain (hc, to_deq);
+ req->to_skip = payload_len - to_deq;
+ goto done;
+ }
+ }
+ }
+ capsule_size = payload_offset + payload_len;
+ /* check if we have the full capsule */
+ if (PREDICT_FALSE (to_deq < capsule_size))
+ {
+ HTTP_DBG (1, "capsule not complete");
+ goto done;
+ }
+
+ dgram_size = sizeof (hdr) + payload_len;
+ if (http_io_as_max_write (req) < dgram_size)
+ {
+ HTTP_DBG (1, "app's rx fifo full");
+ http_io_as_want_deq_ntf (req);
+ goto done;
+ }
+
+ http_io_ts_drain (hc, payload_offset);
+
+ /* read capsule payload */
+ http_io_ts_read (hc, buf, payload_len, 0);
+
+ hdr.data_length = payload_len;
+ hdr.data_offset = 0;
+
+ /* send datagram header and payload */
+ svm_fifo_seg_t segs[2] = { { (u8 *) &hdr, sizeof (hdr) },
+ { buf, payload_len } };
+ http_io_as_write_segs (req, segs, 2);
+
+ n_written += dgram_size;
+ to_deq -= capsule_size;
+ }
+
+done:
+ HTTP_DBG (1, "written %lu bytes", n_written);
+
+ if (n_written)
+ http_app_worker_rx_notify (req);
+
+ http_io_ts_after_read (hc, 0);
+
+ return HTTP_SM_STOP;
+}
+
+/*************************************/
+/* request state machine handlers TX */
+/*************************************/
+
+static http_sm_result_t
+http1_req_state_wait_app_reply (http_conn_t *hc, http_req_t *req,
+ transport_send_params_t *sp)
+{
+ u8 *response;
+ u32 max_enq;
+ http_status_code_t sc;
+ http_msg_t msg;
+ http_sm_result_t sm_result = HTTP_SM_ERROR;
+ http_req_state_t next_state = HTTP_REQ_STATE_WAIT_TRANSPORT_METHOD;
+
+ http_get_app_msg (req, &msg);
+
+ if (msg.data.type > HTTP_MSG_DATA_PTR)
+ {
+ clib_warning ("no data");
+ sc = HTTP_STATUS_INTERNAL_ERROR;
+ goto error;
+ }
+
+ if (msg.type != HTTP_MSG_REPLY)
+ {
+ clib_warning ("unexpected message type %d", msg.type);
+ sc = HTTP_STATUS_INTERNAL_ERROR;
+ goto error;
+ }
+
+ if (msg.code >= HTTP_N_STATUS)
+ {
+ clib_warning ("unsupported status code: %d", msg.code);
+ return HTTP_SM_ERROR;
+ }
+
+ response = http_get_tx_buf (hc);
+ /*
+ * Add "protocol layer" headers:
+ * - current time
+ * - server name
+ * - data length
+ */
+ response =
+ format (response, response_template, http_status_code_str[msg.code],
+ /* Date */
+ format_http_time_now, hc,
+ /* Server */
+ hc->app_name);
+
+ /* RFC9110 8.6: A server MUST NOT send Content-Length header field in a
+ * 2xx (Successful) response to CONNECT or with a status code of 101
+ * (Switching Protocols). */
+ if (req->is_tunnel && (http_status_code_str[msg.code][0] == '2' ||
+ msg.code == HTTP_STATUS_SWITCHING_PROTOCOLS))
+ {
+ ASSERT (msg.data.body_len == 0);
+ next_state = HTTP_REQ_STATE_TUNNEL;
+ if (req->upgrade_proto > HTTP_UPGRADE_PROTO_NA)
+ {
+ response = format (response, connection_upgrade_template,
+ http1_upgrade_proto_str[req->upgrade_proto]);
+ if (req->upgrade_proto == HTTP_UPGRADE_PROTO_CONNECT_UDP &&
+ hc->udp_tunnel_mode == HTTP_UDP_TUNNEL_DGRAM)
+ next_state = HTTP_REQ_STATE_UDP_TUNNEL;
+ }
+ /* cleanup some stuff we don't need anymore in tunnel mode */
+ vec_free (req->headers);
+ http_buffer_free (&req->tx_buf);
+ req->to_skip = 0;
+ }
+ else
+ response = format (response, content_len_template, msg.data.body_len);
+
+ /* Add headers from app (if any) */
+ if (msg.data.headers_len)
+ {
+ HTTP_DBG (0, "got headers from app, len %d", msg.data.headers_len);
+ http1_write_app_headers (hc, &msg, &response);
+ }
+ /* Add empty line after headers */
+ response = format (response, "\r\n");
+ HTTP_DBG (3, "%v", response);
+
+ max_enq = http_io_ts_max_write (hc, sp);
+ if (max_enq < vec_len (response))
+ {
+ clib_warning ("sending status-line and headers failed!");
+ sc = HTTP_STATUS_INTERNAL_ERROR;
+ goto error;
+ }
+ http_io_ts_write (hc, response, vec_len (response), sp);
+
+ if (msg.data.body_len)
+ {
+ /* Start sending the actual data */
+ http_req_tx_buffer_init (req, &msg);
+ next_state = HTTP_REQ_STATE_APP_IO_MORE_DATA;
+ sm_result = HTTP_SM_CONTINUE;
+ }
+ else
+ {
+ /* No response body, we are done */
+ sm_result = HTTP_SM_STOP;
+ }
+
+ http_req_state_change (req, next_state);
+
+ http_io_ts_after_write (hc, sp, 0, 1);
+ return sm_result;
+
+error:
+ http1_send_error (hc, sc, sp);
+ session_transport_closing_notify (&hc->connection);
+ http_disconnect_transport (hc);
+ return HTTP_SM_STOP;
+}
+
+static http_sm_result_t
+http1_req_state_wait_app_method (http_conn_t *hc, http_req_t *req,
+ transport_send_params_t *sp)
+{
+ http_msg_t msg;
+ u8 *request = 0, *target;
+ u32 max_enq;
+ http_sm_result_t sm_result = HTTP_SM_ERROR;
+ http_req_state_t next_state;
+
+ http_get_app_msg (req, &msg);
+
+ if (msg.data.type > HTTP_MSG_DATA_PTR)
+ {
+ clib_warning ("no data");
+ goto error;
+ }
+
+ if (msg.type != HTTP_MSG_REQUEST)
+ {
+ clib_warning ("unexpected message type %d", msg.type);
+ goto error;
+ }
+
+ /* read request target */
+ target = http_get_app_target (req, &msg);
+
+ request = http_get_tx_buf (hc);
+ /* currently we support only GET and POST method */
+ if (msg.method_type == HTTP_REQ_GET)
+ {
+ if (msg.data.body_len)
+ {
+ clib_warning ("GET request shouldn't include data");
+ goto error;
+ }
+ /*
+ * Add "protocol layer" headers:
+ * - host
+ * - user agent
+ */
+ request = format (request, get_request_template,
+ /* target */
+ target,
+ /* Host */
+ hc->host,
+ /* User-Agent */
+ hc->app_name);
+
+ next_state = HTTP_REQ_STATE_WAIT_TRANSPORT_REPLY;
+ sm_result = HTTP_SM_STOP;
+ }
+ else if (msg.method_type == HTTP_REQ_POST)
+ {
+ if (!msg.data.body_len)
+ {
+ clib_warning ("POST request should include data");
+ goto error;
+ }
+ /*
+ * Add "protocol layer" headers:
+ * - host
+ * - user agent
+ * - content length
+ */
+ request = format (request, post_request_template,
+ /* target */
+ target,
+ /* Host */
+ hc->host,
+ /* User-Agent */
+ hc->app_name,
+ /* Content-Length */
+ msg.data.body_len);
+
+ http_req_tx_buffer_init (req, &msg);
+
+ next_state = HTTP_REQ_STATE_APP_IO_MORE_DATA;
+ sm_result = HTTP_SM_CONTINUE;
+ }
+ else
+ {
+ clib_warning ("unsupported method %d", msg.method_type);
+ goto error;
+ }
+
+ /* Add headers from app (if any) */
+ if (msg.data.headers_len)
+ {
+ HTTP_DBG (0, "got headers from app, len %d", msg.data.headers_len);
+ http1_write_app_headers (hc, &msg, &request);
+ }
+ /* Add empty line after headers */
+ request = format (request, "\r\n");
+ HTTP_DBG (3, "%v", request);
+
+ max_enq = http_io_ts_max_write (hc, sp);
+ if (max_enq < vec_len (request))
+ {
+ clib_warning ("sending request-line and headers failed!");
+ sm_result = HTTP_SM_ERROR;
+ goto error;
+ }
+ http_io_ts_write (hc, request, vec_len (request), sp);
+
+ http_req_state_change (req, next_state);
+
+ http_io_ts_after_write (hc, sp, 0, 1);
+ goto done;
+
+error:
+ http_io_as_drain_all (req);
+ session_transport_closing_notify (&hc->connection);
+ session_transport_closed_notify (&hc->connection);
+ http_disconnect_transport (hc);
+
+done:
+ return sm_result;
+}
+
+static http_sm_result_t
+http1_req_state_app_io_more_data (http_conn_t *hc, http_req_t *req,
+ transport_send_params_t *sp)
+{
+ u32 max_write, n_segs, n_written = 0;
+ http_buffer_t *hb = &req->tx_buf;
+ svm_fifo_seg_t *seg;
+ u8 finished = 0;
+
+ ASSERT (!http_buffer_is_drained (hb));
+ max_write = http_io_ts_max_write (hc, sp);
+ if (max_write == 0)
+ {
+ HTTP_DBG (1, "ts tx fifo full");
+ goto check_fifo;
+ }
+
+ seg = http_buffer_get_segs (hb, max_write, &n_segs);
+ if (!seg)
+ {
+ HTTP_DBG (1, "no data to deq");
+ goto check_fifo;
+ }
+
+ n_written = http_io_ts_write_segs (hc, seg, n_segs, sp);
+
+ http_buffer_drain (hb, n_written);
+ finished = http_buffer_is_drained (hb);
+
+ if (finished)
+ {
+ /* Finished transaction:
+ * server back to HTTP_REQ_STATE_WAIT_TRANSPORT_METHOD
+ * client to HTTP_REQ_STATE_WAIT_TRANSPORT_REPLY */
+ http_req_state_change (req, hc->is_server ?
+ HTTP_REQ_STATE_WAIT_TRANSPORT_METHOD :
+ HTTP_REQ_STATE_WAIT_TRANSPORT_REPLY);
+ http_buffer_free (hb);
+ }
+
+check_fifo:
+ http_io_ts_after_write (hc, sp, finished, !!n_written);
+ return HTTP_SM_STOP;
+}
+
+static http_sm_result_t
+http1_req_state_tunnel_tx (http_conn_t *hc, http_req_t *req,
+ transport_send_params_t *sp)
+{
+ u32 max_deq, max_enq, max_read, n_segs = 2;
+ svm_fifo_seg_t segs[n_segs];
+ int n_written = 0;
+
+ HTTP_DBG (1, "tunnel received data from target");
+
+ max_deq = http_io_as_max_read (req);
+ if (PREDICT_FALSE (max_deq == 0))
+ {
+ HTTP_DBG (1, "max_deq == 0");
+ goto check_fifo;
+ }
+ max_enq = http_io_ts_max_write (hc, sp);
+ if (max_enq == 0)
+ {
+ HTTP_DBG (1, "ts tx fifo full");
+ goto check_fifo;
+ }
+ max_read = clib_min (max_enq, max_deq);
+ http_io_as_read_segs (req, segs, &n_segs, max_read);
+ n_written = http_io_ts_write_segs (hc, segs, n_segs, sp);
+ http_io_as_drain (req, n_written);
+
+check_fifo:
+ http_io_ts_after_write (hc, sp, 0, !!n_written);
+
+ return HTTP_SM_STOP;
+}
+
+static http_sm_result_t
+http1_req_state_udp_tunnel_tx (http_conn_t *hc, http_req_t *req,
+ transport_send_params_t *sp)
+{
+ u32 to_deq, capsule_size, dgram_size;
+ u8 written = 0;
+ session_dgram_hdr_t hdr;
+ u8 *buf;
+ u8 *payload;
+
+ HTTP_DBG (1, "udp tunnel received data from target");
+
+ buf = http_get_tx_buf (hc);
+ to_deq = http_io_as_max_read (req);
+
+ while (to_deq > 0)
+ {
+ /* read datagram header */
+ http_io_as_read (req, (u8 *) &hdr, sizeof (hdr), 1);
+ ASSERT (hdr.data_length <= HTTP_UDP_PAYLOAD_MAX_LEN);
+ dgram_size = hdr.data_length + SESSION_CONN_HDR_LEN;
+ ASSERT (to_deq >= dgram_size);
+
+ if (http_io_ts_max_write (hc, sp) <
+ (hdr.data_length + HTTP_UDP_PROXY_DATAGRAM_CAPSULE_OVERHEAD))
+ {
+ HTTP_DBG (1, "ts tx fifo full");
+ goto done;
+ }
+
+ /* create capsule header */
+ payload = http_encap_udp_payload_datagram (buf, hdr.data_length);
+ capsule_size = (payload - buf) + hdr.data_length;
+ /* read payload */
+ http_io_as_read (req, payload, hdr.data_length, 1);
+ http_io_as_drain (req, dgram_size);
+ /* send capsule */
+ http_io_ts_write (hc, buf, capsule_size, sp);
+
+ written = 1;
+ to_deq -= dgram_size;
+ }
+
+done:
+ http_io_ts_after_write (hc, sp, 0, written);
+
+ return HTTP_SM_STOP;
+}
+
+/*************************/
+/* request state machine */
+/*************************/
+
+static http_sm_handler tx_state_funcs[HTTP_REQ_N_STATES] = {
+ 0, /* idle */
+ http1_req_state_wait_app_method,
+ 0, /* wait transport reply */
+ 0, /* transport io more data */
+ 0, /* wait transport method */
+ http1_req_state_wait_app_reply,
+ http1_req_state_app_io_more_data,
+ http1_req_state_tunnel_tx,
+ http1_req_state_udp_tunnel_tx,
+};
+
+static http_sm_handler rx_state_funcs[HTTP_REQ_N_STATES] = {
+ 0, /* idle */
+ 0, /* wait app method */
+ http1_req_state_wait_transport_reply,
+ http1_req_state_transport_io_more_data,
+ http1_req_state_wait_transport_method,
+ 0, /* wait app reply */
+ 0, /* app io more data */
+ http1_req_state_tunnel_rx,
+ http1_req_state_udp_tunnel_rx,
+};
+
+static_always_inline int
+http1_req_state_is_tx_valid (http_req_t *req)
+{
+ return tx_state_funcs[req->state] ? 1 : 0;
+}
+
+static_always_inline int
+http1_req_state_is_rx_valid (http_req_t *req)
+{
+ return rx_state_funcs[req->state] ? 1 : 0;
+}
+
+static_always_inline void
+http1_req_run_state_machine (http_conn_t *hc, http_req_t *req,
+ transport_send_params_t *sp, u8 is_tx)
+{
+ http_sm_result_t res;
+
+ do
+ {
+ if (is_tx)
+ res = tx_state_funcs[req->state](hc, req, sp);
+ else
+ res = rx_state_funcs[req->state](hc, req, 0);
+ if (res == HTTP_SM_ERROR)
+ {
+ HTTP_DBG (1, "error in state machine %d", res);
+ return;
+ }
+ }
+ while (res == HTTP_SM_CONTINUE);
+
+ /* Reset the session expiration timer */
+ http_conn_timer_update (hc);
+}
+
+/*****************/
+/* http core VFT */
+/*****************/
+
+static void
+http1_app_tx_callback (http_conn_t *hc, transport_send_params_t *sp)
+{
+ http_req_t *req;
+
+ req = http_get_req_if_valid (hc, 0);
+ if (!req)
+ {
+ http_alloc_req (hc);
+ req = http_get_req (hc, 0);
+ req->app_session_handle = hc->h_pa_session_handle;
+ http_req_state_change (req, HTTP_REQ_STATE_WAIT_APP_METHOD);
+ }
+
+ if (!http1_req_state_is_tx_valid (req))
+ {
+ /* Sometimes the server apps can send the response earlier
+ * than expected (e.g when rejecting a bad request)*/
+ if (req->state == HTTP_REQ_STATE_TRANSPORT_IO_MORE_DATA && hc->is_server)
+ {
+ http_io_ts_drain_all (hc);
+ http_req_state_change (req, HTTP_REQ_STATE_WAIT_APP_REPLY);
+ }
+ else
+ {
+ clib_warning ("hc [%u]%x invalid tx state: http req state "
+ "'%U', session state '%U'",
+ hc->c_thread_index, hc->h_hc_index,
+ format_http_req_state, req->state,
+ format_http_conn_state, hc);
+ http_io_as_drain_all (req);
+ return;
+ }
+ }
+
+ HTTP_DBG (1, "run state machine");
+ http1_req_run_state_machine (hc, req, sp, 1);
+}
+
+static void
+http1_app_rx_evt_callback (http_conn_t *hc)
+{
+ http_req_t *req;
+
+ req = http_get_req (hc, 0);
+
+ if (req->state == HTTP_REQ_STATE_TUNNEL)
+ http1_req_state_tunnel_rx (hc, req, 0);
+}
+
+static void
+http1_app_close_callback (http_conn_t *hc)
+{
+ http_req_t *req;
+
+ req = http_get_req_if_valid (hc, 0);
+ /* Nothing more to send, confirm close */
+ if (!req || !http_io_as_max_read (req))
+ {
+ session_transport_closed_notify (&hc->connection);
+ http_disconnect_transport (hc);
+ }
+ else
+ {
+ /* Wait for all data to be written to ts */
+ hc->state = HTTP_CONN_STATE_APP_CLOSED;
+ }
+}
+
+static void
+http1_app_reset_callback (http_conn_t *hc)
+{
+ session_transport_closed_notify (&hc->connection);
+ http_disconnect_transport (hc);
+}
+
+static void
+http1_transport_rx_callback (http_conn_t *hc)
+{
+ http_req_t *req;
+
+ req = http_get_req_if_valid (hc, 0);
+ if (!req)
+ {
+ http_alloc_req (hc);
+ req = http_get_req (hc, 0);
+ req->app_session_handle = hc->h_pa_session_handle;
+ http_req_state_change (req, HTTP_REQ_STATE_WAIT_TRANSPORT_METHOD);
+ }
+
+ if (!http1_req_state_is_rx_valid (req))
+ {
+ clib_warning ("hc [%u]%x invalid rx state: http req state "
+ "'%U', session state '%U'",
+ hc->c_thread_index, hc->h_hc_index, format_http_req_state,
+ req->state, format_http_conn_state, hc);
+ http_io_ts_drain_all (hc);
+ return;
+ }
+
+ HTTP_DBG (1, "run state machine");
+ http1_req_run_state_machine (hc, req, 0, 0);
+}
+
+static void
+http1_transport_close_callback (http_conn_t *hc)
+{
+ /* Nothing more to rx, propagate to app */
+ if (!http_io_ts_max_read (hc))
+ session_transport_closing_notify (&hc->connection);
+}
+
+const static http_engine_vft_t http1_engine = {
+ .app_tx_callback = http1_app_tx_callback,
+ .app_rx_evt_callback = http1_app_rx_evt_callback,
+ .app_close_callback = http1_app_close_callback,
+ .app_reset_callback = http1_app_reset_callback,
+ .transport_rx_callback = http1_transport_rx_callback,
+ .transport_close_callback = http1_transport_close_callback,
+};
+
+static clib_error_t *
+http1_init (vlib_main_t *vm)
+{
+ http_register_engine (&http1_engine, HTTP_VERSION_1);
+ return 0;
+}
+
+VLIB_INIT_FUNCTION (http1_init) = {
+ .runs_after = VLIB_INITS ("http_transport_init"),
+};