http: modularization in preparation for h2 and h3 54/42254/11
authorMatus Fabian <[email protected]>
Mon, 27 Jan 2025 13:12:22 +0000 (08:12 -0500)
committerFlorin Coras <[email protected]>
Tue, 11 Feb 2025 20:49:38 +0000 (20:49 +0000)
Prepare code to handle multiple http versions and move http/1
specific code into http1.c

Type: improvement

Change-Id: I1f2c958dadb7721b305c65b2da7c2943695ad740
Signed-off-by: Matus Fabian <[email protected]>
src/plugins/http/CMakeLists.txt
src/plugins/http/http.c
src/plugins/http/http.h
src/plugins/http/http1.c [new file with mode: 0644]
src/plugins/http/http_private.h [new file with mode: 0644]
src/plugins/http/http_timer.h

index 075b8d6..ad58482 100644 (file)
@@ -16,6 +16,7 @@ add_vpp_plugin(http
   http.c
   http_buffer.c
   http_timer.c
+  http1.c
 )
 
 add_vpp_plugin(http_unittest
index 04a4ad3..c995c99 100644 (file)
  * limitations under the License.
  */
 
+#include <vpp/app/version.h>
+#include <vnet/session/application_interface.h>
+#include <vnet/session/application.h>
+
 #include <http/http.h>
-#include <vnet/session/session.h>
+#include <http/http_private.h>
 #include <http/http_timer.h>
-#include <http/http_status_codes.h>
-#include <http/http_header_names.h>
 
 static http_main_t http_main;
-
-#define HTTP_FIFO_THRESH (16 << 10)
-
-/* HTTP state machine result */
-typedef enum http_sm_result_t_
-{
-  HTTP_SM_STOP = 0,
-  HTTP_SM_CONTINUE = 1,
-  HTTP_SM_ERROR = -1,
-} http_sm_result_t;
+static http_engine_vft_t *http_vfts;
 
 const http_buffer_type_t msg_to_buf_type[] = {
   [HTTP_MSG_DATA_INLINE] = HTTP_BUFFER_FIFO,
   [HTTP_MSG_DATA_PTR] = HTTP_BUFFER_PTR,
 };
 
-const char *http_upgrade_proto_str[] = { "",
-#define _(sym, str) str,
-                                        foreach_http_upgrade_proto
-#undef _
-};
+void
+http_register_engine (const http_engine_vft_t *vft, http_version_t version)
+{
+  vec_validate (http_vfts, version);
+  http_vfts[version] = *vft;
+}
+
+always_inline http_version_t
+http_version_from_handle (http_conn_handle_t hc_handle)
+{
+  /* the first 3 bits are http version */
+  return hc_handle >> 29;
+}
+
+always_inline u32
+http_conn_index_from_handle (http_conn_handle_t hc_handle)
+{
+  return hc_handle & 0x1FFFFFFF;
+}
+
+always_inline http_conn_handle_t
+http_make_handle (u32 hc_index, http_version_t version)
+{
+  ASSERT (hc_index <= 0x1FFFFFFF);
+  return (version << 29) | hc_index;
+}
+
+int
+http_v_find_index (u8 *vec, u32 offset, u32 num, char *str)
+{
+  int start_index = offset;
+  u32 slen = (u32) strnlen_s_inline (str, 16);
+  u32 vlen = vec_len (vec);
+
+  ASSERT (slen > 0);
+
+  if (vlen <= slen)
+    return -1;
+
+  int end_index = vlen - slen;
+  if (num)
+    {
+      if (num < slen)
+       return -1;
+      end_index = clib_min (end_index, offset + num - slen);
+    }
 
-#define expect_char(c)                                                        \
-  if (*p++ != c)                                                              \
-    {                                                                         \
-      clib_warning ("unexpected character");                                  \
-      return -1;                                                              \
+  for (; start_index <= end_index; start_index++)
+    {
+      if (!memcmp (vec + start_index, str, slen))
+       return start_index;
     }
 
-static u8 *
+  return -1;
+}
+
+u8 *
 format_http_req_state (u8 *s, va_list *va)
 {
   http_req_state_t state = va_arg (*va, http_req_state_t);
@@ -68,18 +104,7 @@ format_http_req_state (u8 *s, va_list *va)
   return format (s, "%s", t);
 }
 
-#define http_req_state_change(_hc, _state)                                    \
-  do                                                                          \
-    {                                                                         \
-      HTTP_DBG (1, "changing http req state: %U -> %U",                       \
-               format_http_req_state, (_hc)->req.state,                      \
-               format_http_req_state, _state);                               \
-      ASSERT ((_hc)->req.state != HTTP_REQ_STATE_TUNNEL);                     \
-      (_hc)->req.state = _state;                                              \
-    }                                                                         \
-  while (0)
-
-static u8 *
+u8 *
 format_http_conn_state (u8 *s, va_list *args)
 {
   http_conn_t *hc = va_arg (*args, http_conn_t *);
@@ -98,6 +123,15 @@ format_http_conn_state (u8 *s, va_list *args)
   return format (s, "%s", t);
 }
 
+u8 *
+format_http_time_now (u8 *s, va_list *args)
+{
+  http_conn_t __clib_unused *hc = va_arg (*args, http_conn_t *);
+  http_main_t *hm = &http_main;
+  f64 now = clib_timebase_now (&hm->timebase);
+  return format (s, "%U", format_clib_timebase_time, now);
+}
+
 static inline http_worker_t *
 http_worker_get (u32 thread_index)
 {
@@ -116,6 +150,7 @@ http_conn_alloc_w_thread (u32 thread_index)
   hc->h_hc_index = hc - wrk->conn_pool;
   hc->h_pa_session_handle = SESSION_INVALID_HANDLE;
   hc->h_tc_session_handle = SESSION_INVALID_HANDLE;
+  hc->version = HTTP_VERSION_NA;
   return hc->h_hc_index;
 }
 
@@ -135,10 +170,12 @@ http_conn_get_w_thread_if_valid (u32 hc_index, u32 thread_index)
   return pool_elt_at_index (wrk->conn_pool, hc_index);
 }
 
-void
+static void
 http_conn_free (http_conn_t *hc)
 {
   http_worker_t *wrk = http_worker_get (hc->c_thread_index);
+  if (CLIB_DEBUG)
+    memset (hc, 0xba, sizeof (*hc));
   pool_put (wrk->conn_pool, hc);
 }
 
@@ -149,10 +186,12 @@ http_ho_conn_get (u32 ho_hc_index)
   return pool_elt_at_index (hm->ho_conn_pool, ho_hc_index);
 }
 
-void
+static void
 http_ho_conn_free (http_conn_t *ho_hc)
 {
   http_main_t *hm = &http_main;
+  if (CLIB_DEBUG)
+    memset (ho_hc, 0xba, sizeof (*ho_hc));
   pool_put (hm->ho_conn_pool, ho_hc);
 }
 
@@ -168,6 +207,7 @@ http_ho_conn_alloc (void)
   hc->h_pa_session_handle = SESSION_INVALID_HANDLE;
   hc->h_tc_session_handle = SESSION_INVALID_HANDLE;
   hc->timeout = HTTP_CONN_TIMEOUT;
+  hc->version = HTTP_VERSION_NA;
   return hc->h_hc_index;
 }
 
@@ -178,18 +218,19 @@ http_listener_alloc (void)
   http_conn_t *lhc;
 
   pool_get_zero (hm->listener_pool, lhc);
-  lhc->c_c_index = lhc - hm->listener_pool;
+  lhc->h_hc_index = lhc - hm->listener_pool;
   lhc->timeout = HTTP_CONN_TIMEOUT;
-  return lhc->c_c_index;
+  lhc->version = HTTP_VERSION_NA;
+  return lhc->h_hc_index;
 }
 
-http_conn_t *
+static http_conn_t *
 http_listener_get (u32 lhc_index)
 {
   return pool_elt_at_index (http_main.listener_pool, lhc_index);
 }
 
-void
+static void
 http_listener_free (http_conn_t *lhc)
 {
   http_main_t *hm = &http_main;
@@ -214,1971 +255,339 @@ http_disconnect_transport (http_conn_t *hc)
     clib_warning ("disconnect returned");
 }
 
-static void
-http_conn_invalidate_timer_cb (u32 hs_handle)
-{
-  http_conn_t *hc;
-
-  hc =
-    http_conn_get_w_thread_if_valid (hs_handle & 0x00FFFFFF, hs_handle >> 24);
-
-  HTTP_DBG (1, "hc [%u]%x", hs_handle >> 24, hs_handle & 0x00FFFFFF);
-  if (!hc)
-    {
-      HTTP_DBG (1, "already deleted");
-      return;
-    }
-
-  hc->timer_handle = HTTP_TIMER_HANDLE_INVALID;
-  hc->pending_timer = 1;
-}
-
-static void
-http_conn_timeout_cb (void *hc_handlep)
+http_status_code_t
+http_sc_by_u16 (u16 status_code)
 {
-  http_conn_t *hc;
-  uword hs_handle;
-
-  hs_handle = pointer_to_uword (hc_handlep);
-  hc =
-    http_conn_get_w_thread_if_valid (hs_handle & 0x00FFFFFF, hs_handle >> 24);
-
-  HTTP_DBG (1, "hc [%u]%x", hs_handle >> 24, hs_handle & 0x00FFFFFF);
-  if (!hc)
-    {
-      HTTP_DBG (1, "already deleted");
-      return;
-    }
-
-  if (!hc->pending_timer)
-    {
-      HTTP_DBG (1, "timer not pending");
-      return;
-    }
-
-  session_transport_closing_notify (&hc->connection);
-  http_disconnect_transport (hc);
+  http_main_t *hm = &http_main;
+  return hm->sc_by_u16[status_code];
 }
 
-int
-http_ts_accept_callback (session_t *ts)
+u8 *
+http_get_app_header_list (http_conn_t *hc, http_msg_t *msg)
 {
-  session_t *ts_listener, *as, *asl;
-  app_worker_t *app_wrk;
-  http_conn_t *lhc, *hc;
-  u32 hc_index, thresh;
+  http_main_t *hm = &http_main;
+  session_t *as;
+  u8 *app_headers;
   int rv;
 
-  ts_listener = listen_session_get_from_handle (ts->listener_handle);
-  lhc = http_listener_get (ts_listener->opaque);
-
-  hc_index = http_conn_alloc_w_thread (ts->thread_index);
-  hc = http_conn_get_w_thread (hc_index, ts->thread_index);
-  clib_memcpy_fast (hc, lhc, sizeof (*lhc));
-  hc->timer_handle = HTTP_TIMER_HANDLE_INVALID;
-  hc->c_thread_index = ts->thread_index;
-  hc->h_hc_index = hc_index;
-
-  hc->h_tc_session_handle = session_handle (ts);
-  hc->c_flags |= TRANSPORT_CONNECTION_F_NO_LOOKUP;
-
-  hc->state = HTTP_CONN_STATE_ESTABLISHED;
-  http_req_state_change (hc, HTTP_REQ_STATE_WAIT_TRANSPORT_METHOD);
-
-  ts->session_state = SESSION_STATE_READY;
-  ts->opaque = hc_index;
-
-  /*
-   * Alloc session and initialize
-   */
-  as = session_alloc (hc->c_thread_index);
-  hc->c_s_index = as->session_index;
-
-  as->app_wrk_index = hc->h_pa_wrk_index;
-  as->connection_index = hc->c_c_index;
-  as->session_state = SESSION_STATE_ACCEPTING;
-
-  asl = listen_session_get_from_handle (lhc->h_pa_session_handle);
-  as->session_type = asl->session_type;
-  as->listener_handle = lhc->h_pa_session_handle;
+  as = session_get_from_handle (hc->h_pa_session_handle);
 
-  /*
-   * Init session fifos and notify app
-   */
-  if ((rv = app_worker_init_accepted (as)))
+  if (msg->data.type == HTTP_MSG_DATA_PTR)
     {
-      HTTP_DBG (1, "failed to allocate fifos");
-      hc->h_pa_session_handle = SESSION_INVALID_HANDLE;
-      session_free (as);
-      return rv;
+      uword app_headers_ptr;
+      rv = svm_fifo_dequeue (as->tx_fifo, sizeof (app_headers_ptr),
+                            (u8 *) &app_headers_ptr);
+      ASSERT (rv == sizeof (app_headers_ptr));
+      app_headers = uword_to_pointer (app_headers_ptr, u8 *);
     }
-
-  hc->h_pa_session_handle = session_handle (as);
-  hc->h_pa_wrk_index = as->app_wrk_index;
-  app_wrk = app_worker_get (as->app_wrk_index);
-
-  HTTP_DBG (1, "Accepted on listener %u new connection [%u]%x",
-           ts_listener->opaque, vlib_get_thread_index (), hc_index);
-
-  if ((rv = app_worker_accept_notify (app_wrk, as)))
+  else
     {
-      HTTP_DBG (0, "app accept returned");
-      session_free (as);
-      return rv;
+      app_headers = hm->app_header_lists[hc->c_thread_index];
+      rv = svm_fifo_dequeue (as->tx_fifo, msg->data.headers_len, app_headers);
+      ASSERT (rv == msg->data.headers_len);
     }
 
-  /* Avoid enqueuing small chunks of data on transport tx notifications. If
-   * the fifo is small (under 16K) we set the threshold to it's size, meaning
-   * a notification will be given when the fifo empties.
-   */
-  ts = session_get_from_handle (hc->h_tc_session_handle);
-  thresh = clib_min (svm_fifo_size (ts->tx_fifo), HTTP_FIFO_THRESH);
-  svm_fifo_set_deq_thresh (ts->tx_fifo, thresh);
-
-  http_conn_timer_start (hc);
-
-  return 0;
+  return app_headers;
 }
 
-static int
-http_ts_connected_callback (u32 http_app_index, u32 ho_hc_index, session_t *ts,
-                           session_error_t err)
+u8 *
+http_get_app_target (http_req_t *req, http_msg_t *msg)
 {
-  u32 new_hc_index;
   session_t *as;
-  http_conn_t *hc, *ho_hc;
-  app_worker_t *app_wrk;
+  u8 *target;
   int rv;
 
-  ho_hc = http_ho_conn_get (ho_hc_index);
-  ASSERT (ho_hc->state == HTTP_CONN_STATE_CONNECTING);
-
-  if (err)
-    {
-      clib_warning ("half-open hc index %d, error: %U", ho_hc_index,
-                   format_session_error, err);
-      app_wrk = app_worker_get_if_valid (ho_hc->h_pa_wrk_index);
-      if (app_wrk)
-       app_worker_connect_notify (app_wrk, 0, err, ho_hc->h_pa_app_api_ctx);
-      return 0;
-    }
-
-  new_hc_index = http_conn_alloc_w_thread (ts->thread_index);
-  hc = http_conn_get_w_thread (new_hc_index, ts->thread_index);
-
-  clib_memcpy_fast (hc, ho_hc, sizeof (*hc));
-
-  hc->timer_handle = HTTP_TIMER_HANDLE_INVALID;
-  hc->c_thread_index = ts->thread_index;
-  hc->h_tc_session_handle = session_handle (ts);
-  hc->c_c_index = new_hc_index;
-  hc->c_flags |= TRANSPORT_CONNECTION_F_NO_LOOKUP;
-  hc->state = HTTP_CONN_STATE_ESTABLISHED;
-  http_req_state_change (hc, HTTP_REQ_STATE_WAIT_APP_METHOD);
-
-  ts->session_state = SESSION_STATE_READY;
-  ts->opaque = new_hc_index;
-
-  /* allocate app session and initialize */
-
-  as = session_alloc (hc->c_thread_index);
-  hc->c_s_index = as->session_index;
-  as->connection_index = hc->c_c_index;
-  as->app_wrk_index = hc->h_pa_wrk_index;
-  as->session_state = SESSION_STATE_READY;
-  as->opaque = hc->h_pa_app_api_ctx;
-  as->session_type = session_type_from_proto_and_ip (
-    TRANSPORT_PROTO_HTTP, session_type_is_ip4 (ts->session_type));
-
-  HTTP_DBG (1, "half-open hc index %x,  hc [%u]%x", ho_hc_index,
-           ts->thread_index, new_hc_index);
-
-  app_wrk = app_worker_get (hc->h_pa_wrk_index);
-  if (!app_wrk)
-    {
-      clib_warning ("no app worker");
-      return -1;
-    }
+  as = session_get_from_handle (req->app_session_handle);
 
-  if ((rv = app_worker_init_connected (app_wrk, as)))
+  if (msg->data.type == HTTP_MSG_DATA_PTR)
     {
-      HTTP_DBG (1, "failed to allocate fifos");
-      session_free (as);
-      return rv;
-    }
-  app_worker_connect_notify (app_wrk, as, err, hc->h_pa_app_api_ctx);
-  hc->h_pa_session_handle = session_handle (as);
-  http_conn_timer_start (hc);
-
-  return 0;
-}
-
-static void
-http_ts_disconnect_callback (session_t *ts)
-{
-  http_conn_t *hc;
-
-  hc = http_conn_get_w_thread (ts->opaque, ts->thread_index);
-
-  if (hc->state < HTTP_CONN_STATE_TRANSPORT_CLOSED)
-    hc->state = HTTP_CONN_STATE_TRANSPORT_CLOSED;
-
-  /* Nothing more to rx, propagate to app */
-  if (!svm_fifo_max_dequeue_cons (ts->rx_fifo))
-    session_transport_closing_notify (&hc->connection);
-}
-
-static void
-http_ts_reset_callback (session_t *ts)
-{
-  http_conn_t *hc;
-
-  hc = http_conn_get_w_thread (ts->opaque, ts->thread_index);
-
-  hc->state = HTTP_CONN_STATE_CLOSED;
-  http_buffer_free (&hc->req.tx_buf);
-  http_req_state_change (hc, HTTP_REQ_STATE_WAIT_TRANSPORT_METHOD);
-  session_transport_reset_notify (&hc->connection);
-
-  http_disconnect_transport (hc);
-}
-
-/**
- * http error boilerplate
- */
-static const char *http_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 *http_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 *http_get_request_template = "GET %s HTTP/1.1\r\n"
-                                              "Host: %v\r\n"
-                                              "User-Agent: %v\r\n";
-
-static const char *http_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 u32
-http_send_data (http_conn_t *hc, u8 *data, u32 length)
-{
-  const u32 max_burst = 64 << 10;
-  session_t *ts;
-  u32 to_send;
-  int rv;
-
-  ts = session_get_from_handle (hc->h_tc_session_handle);
-
-  to_send = clib_min (length, max_burst);
-  rv = svm_fifo_enqueue (ts->tx_fifo, to_send, data);
-  if (rv <= 0)
-    {
-      clib_warning ("svm_fifo_enqueue failed, rv %d", rv);
-      return 0;
-    }
-
-  if (svm_fifo_set_event (ts->tx_fifo))
-    session_program_tx_io_evt (ts->handle, SESSION_IO_EVT_TX);
-
-  return rv;
-}
-
-static void
-http_send_error (http_conn_t *hc, http_status_code_t ec)
-{
-  http_main_t *hm = &http_main;
-  u8 *data;
-  f64 now;
-
-  if (ec >= HTTP_N_STATUS)
-    ec = HTTP_STATUS_INTERNAL_ERROR;
-
-  now = clib_timebase_now (&hm->timebase);
-  data = format (0, http_error_template, http_status_code_str[ec],
-                format_clib_timebase_time, now);
-  HTTP_DBG (3, "%v", data);
-  http_send_data (hc, data, vec_len (data));
-  vec_free (data);
-}
-
-static int
-http_read_message (http_conn_t *hc)
-{
-  u32 max_deq;
-  session_t *ts;
-  int n_read;
-
-  ts = session_get_from_handle (hc->h_tc_session_handle);
-
-  max_deq = svm_fifo_max_dequeue (ts->rx_fifo);
-  if (PREDICT_FALSE (max_deq == 0))
-    return -1;
-
-  vec_validate (hc->req.rx_buf, max_deq - 1);
-  n_read = svm_fifo_peek (ts->rx_fifo, 0, max_deq, hc->req.rx_buf);
-  ASSERT (n_read == max_deq);
-  HTTP_DBG (1, "read %u bytes from rx_fifo", n_read);
-
-  return 0;
-}
-
-static void
-http_read_message_drop (http_conn_t *hc, u32 len)
-{
-  session_t *ts;
-
-  ts = session_get_from_handle (hc->h_tc_session_handle);
-  svm_fifo_dequeue_drop (ts->rx_fifo, len);
-  vec_reset_length (hc->req.rx_buf);
-
-  if (svm_fifo_is_empty (ts->rx_fifo))
-    svm_fifo_unset_event (ts->rx_fifo);
-}
-
-static void
-http_read_message_drop_all (http_conn_t *hc)
-{
-  session_t *ts;
-
-  ts = session_get_from_handle (hc->h_tc_session_handle);
-  svm_fifo_dequeue_drop_all (ts->rx_fifo);
-  vec_reset_length (hc->req.rx_buf);
-
-  if (svm_fifo_is_empty (ts->rx_fifo))
-    svm_fifo_unset_event (ts->rx_fifo);
-}
-
-/**
- * @brief Find the first occurrence of the string in the vector.
- *
- * @param vec The vector to be scanned.
- * @param offset Search offset in the vector.
- * @param num Maximum number of characters to be searched if non-zero.
- * @param str The string to be searched.
- *
- * @return @c -1 if the string is not found within the vector; index otherwise.
- */
-static inline int
-v_find_index (u8 *vec, u32 offset, u32 num, char *str)
-{
-  int start_index = offset;
-  u32 slen = (u32) strnlen_s_inline (str, 16);
-  u32 vlen = vec_len (vec);
-
-  ASSERT (slen > 0);
-
-  if (vlen <= slen)
-    return -1;
-
-  int end_index = vlen - slen;
-  if (num)
-    {
-      if (num < slen)
-       return -1;
-      end_index = clib_min (end_index, offset + num - slen);
-    }
-
-  for (; start_index <= end_index; start_index++)
-    {
-      if (!memcmp (vec + start_index, str, slen))
-       return start_index;
-    }
-
-  return -1;
-}
-
-static void
-http_identify_optional_query (http_req_t *req)
-{
-  int i;
-  for (i = req->target_path_offset;
-       i < (req->target_path_offset + req->target_path_len); i++)
-    {
-      if (req->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
-http_parse_target (http_req_t *req)
-{
-  int i;
-  u8 *p, *end;
-
-  /* asterisk-form  = "*" */
-  if ((req->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 (req->rx_buf[req->target_path_offset] == '/')
-    {
-      /* drop leading slash */
-      req->target_path_len--;
-      req->target_path_offset++;
-      req->target_form = HTTP_TARGET_ORIGIN_FORM;
-      http_identify_optional_query (req);
-      /* 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 (req->rx_buf + req->target_path_offset, "http", 4))
-    {
-      req->scheme = HTTP_URL_SCHEME_HTTP;
-      p = req->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 - req->rx_buf;
-         req->target_authority_len = 0;
-         end = req->rx_buf + req->target_path_offset + req->target_path_len;
-         while (p < end)
-           {
-             if (*p == '/')
-               {
-                 p++; /* drop leading slash */
-                 req->target_path_offset = p - req->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;
-           }
-         http_identify_optional_query (req);
-         /* 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 ((req->rx_buf[i] == ':') && (isdigit (req->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
-http_parse_request_line (http_req_t *req, 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 = v_find_index (req->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 (req->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 = req->rx_buf[0] == '\r' && req->rx_buf[1] == '\n' ? 2 : 0;
-  /* parse method */
-  if (!memcmp (req->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 (req->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 (req->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 (req->rx_buf[method_offset] - 'A' <= 'Z' - 'A')
-       {
-         clib_warning ("method not implemented: %8v", req->rx_buf);
-         *ec = HTTP_STATUS_NOT_IMPLEMENTED;
-         return -1;
-       }
-      else
-       {
-         clib_warning ("not method name: %8v", req->rx_buf);
-         *ec = HTTP_STATUS_BAD_REQUEST;
-         return -1;
-       }
-    }
-
-  /* find version */
-  i = v_find_index (req->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 (req->rx_buf[i + 6]))
-    {
-      if (req->rx_buf[i + 6] != '1')
-       {
-         clib_warning ("HTTP major version '%c' not supported",
-                       req->rx_buf[i + 6]);
-         *ec = HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED;
-         return -1;
-       }
-    }
-  else
-    {
-      clib_warning ("HTTP major version '%c' is not digit",
-                   req->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 (http_parse_target (req))
-    {
-      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;
-}
-
-#define parse_int(val, mul)                                                   \
-  do                                                                          \
-    {                                                                         \
-      if (!isdigit (*p))                                                      \
-       {                                                                     \
-         clib_warning ("expected digit");                                    \
-         return -1;                                                          \
-       }                                                                     \
-      val += mul * (*p++ - '0');                                              \
-    }                                                                         \
-  while (0)
-
-static int
-http_parse_status_line (http_req_t *req)
-{
-  int i;
-  u32 next_line_offset;
-  u8 *p, *end;
-  u16 status_code = 0;
-  http_main_t *hm = &http_main;
-
-  i = v_find_index (req->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 = req->rx_buf;
-  end = req->rx_buf + i;
-
-  /* there should be at least one more CRLF */
-  if (vec_len (req->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 = hm->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;
-}
-
-static int
-http_identify_headers (http_req_t *req, 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 ((req->rx_buf[req->rx_buf_offset] == '\r') &&
-      (req->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 = req->rx_buf + vec_len (req->rx_buf);
-  p = req->rx_buf + req->rx_buf_offset;
-
-  while (1)
-    {
-      rv = _parse_field_name (&p, end, &name_start, &name_len);
-      if (rv != 0)
-       {
-         *ec = HTTP_STATUS_BAD_REQUEST;
-         return -1;
-       }
-      rv = _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 - req->rx_buf) - req->headers_offset;
-      field_line->name_len = name_len;
-      field_line->value_offset =
-       (value_start - req->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 - (req->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
-http_identify_message_body (http_req_t *req, 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 = req->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 http_sm_result_t
-http_req_state_wait_transport_reply (http_conn_t *hc,
-                                    transport_send_params_t *sp)
-{
-  int rv;
-  http_msg_t msg = {};
-  app_worker_t *app_wrk;
-  session_t *as;
-  u32 len, max_enq, body_sent;
-  http_status_code_t ec;
-
-  rv = http_read_message (hc);
-
-  /* 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", hc->req.rx_buf);
-
-  if (vec_len (hc->req.rx_buf) < 8)
-    {
-      clib_warning ("response buffer too short");
-      goto error;
-    }
-
-  rv = http_parse_status_line (&hc->req);
-  if (rv)
-    goto error;
-
-  rv = http_identify_headers (&hc->req, &ec);
-  if (rv)
-    goto error;
-
-  rv = http_identify_message_body (&hc->req, &ec);
-  if (rv)
-    goto error;
-
-  /* send at least "control data" which is necessary minimum,
-   * if there is some space send also portion of body */
-  as = session_get_from_handle (hc->h_pa_session_handle);
-  max_enq = svm_fifo_max_enqueue (as->rx_fifo);
-  max_enq -= sizeof (msg);
-  if (max_enq < hc->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 (hc->req.rx_buf));
-
-  msg.type = HTTP_MSG_REPLY;
-  msg.code = hc->req.status_code;
-  msg.data.headers_offset = hc->req.headers_offset;
-  msg.data.headers_len = hc->req.headers_len;
-  msg.data.body_offset = hc->req.body_offset;
-  msg.data.body_len = hc->req.body_len;
-  msg.data.type = HTTP_MSG_DATA_INLINE;
-  msg.data.len = len;
-  msg.data.headers_ctx = pointer_to_uword (hc->req.headers);
-
-  svm_fifo_seg_t segs[2] = { { (u8 *) &msg, sizeof (msg) },
-                            { hc->req.rx_buf, len } };
-
-  rv = svm_fifo_enqueue_segments (as->rx_fifo, segs, 2, 0 /* allow partial */);
-  ASSERT (rv == (sizeof (msg) + len));
-
-  http_read_message_drop (hc, len);
-
-  body_sent = len - hc->req.control_data_len;
-  hc->req.to_recv = hc->req.body_len - body_sent;
-  if (hc->req.to_recv == 0)
-    {
-      /* all sent, we are done */
-      http_req_state_change (hc, HTTP_REQ_STATE_WAIT_APP_METHOD);
-    }
-  else
-    {
-      /* stream rest of the response body */
-      http_req_state_change (hc, HTTP_REQ_STATE_TRANSPORT_IO_MORE_DATA);
-    }
-
-  app_wrk = app_worker_get_if_valid (as->app_wrk_index);
-  if (app_wrk)
-    app_worker_rx_notify (app_wrk, as);
-  return HTTP_SM_STOP;
-
-error:
-  http_read_message_drop_all (hc);
-  session_transport_closing_notify (&hc->connection);
-  session_transport_closed_notify (&hc->connection);
-  http_disconnect_transport (hc);
-  return HTTP_SM_ERROR;
-}
-
-#define http_field_line_value_token(_fl, _req)                                \
-  (const char *) ((_req)->rx_buf + (_req)->headers_offset +                   \
-                 (_fl)->value_offset),                                       \
-    (_fl)->value_len
-
-static void
-http_check_connection_upgrade (http_req_t *req)
-{
-  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),
-                         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),    \
-                              http_token_lit (str))) req->upgrade_proto =    \
-    HTTP_UPGRADE_PROTO_##sym;
-      foreach_http_upgrade_proto
-#undef _
-       else return;
-
-      HTTP_DBG (1, "connection upgrade: %U", format_http_bytes,
-               req->rx_buf + req->headers_offset + upgrade->value_offset,
-               upgrade->value_len);
-      req->is_tunnel = 1;
-      req->method = HTTP_REQ_CONNECT;
-    }
-}
-
-static void
-http_target_fixup (http_conn_t *hc)
-{
-  http_field_line_t *host;
-
-  if (hc->req.target_form == HTTP_TARGET_ABSOLUTE_FORM)
-    return;
-
-  /* scheme fixup */
-  hc->req.scheme = session_get_transport_proto (session_get_from_handle (
-                    hc->h_tc_session_handle)) == TRANSPORT_PROTO_TLS ?
-                    HTTP_URL_SCHEME_HTTPS :
-                    HTTP_URL_SCHEME_HTTP;
-
-  if (hc->req.target_form == HTTP_TARGET_AUTHORITY_FORM ||
-      hc->req.connection_header_index == ~0)
-    return;
-
-  /* authority fixup */
-  host = vec_elt_at_index (hc->req.headers, hc->req.connection_header_index);
-  hc->req.target_authority_offset = host->value_offset;
-  hc->req.target_authority_len = host->value_len;
-}
-
-static http_sm_result_t
-http_req_state_wait_transport_method (http_conn_t *hc,
-                                     transport_send_params_t *sp)
-{
-  http_status_code_t ec;
-  app_worker_t *app_wrk;
-  http_msg_t msg;
-  session_t *as;
-  int rv;
-  u32 len, max_enq, body_sent;
-  u64 max_deq;
-
-  rv = http_read_message (hc);
-
-  /* Nothing yet, wait for data or timer expire */
-  if (rv)
-    return HTTP_SM_STOP;
-
-  HTTP_DBG (3, "%v", hc->req.rx_buf);
-
-  if (vec_len (hc->req.rx_buf) < 8)
-    {
-      ec = HTTP_STATUS_BAD_REQUEST;
-      goto error;
-    }
-
-  rv = http_parse_request_line (&hc->req, &ec);
-  if (rv)
-    goto error;
-
-  rv = http_identify_headers (&hc->req, &ec);
-  if (rv)
-    goto error;
-
-  http_target_fixup (hc);
-  http_check_connection_upgrade (&hc->req);
-
-  rv = http_identify_message_body (&hc->req, &ec);
-  if (rv)
-    goto error;
-
-  /* send at least "control data" which is necessary minimum,
-   * if there is some space send also portion of body */
-  as = session_get_from_handle (hc->h_pa_session_handle);
-  max_enq = svm_fifo_max_enqueue (as->rx_fifo);
-  if (max_enq < hc->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 (hc->req.control_data_len + hc->req.body_len,
-                     vec_len (hc->req.rx_buf));
-  len = clib_min (max_enq, max_deq);
-
-  msg.type = HTTP_MSG_REQUEST;
-  msg.method_type = hc->req.method;
-  msg.data.type = HTTP_MSG_DATA_INLINE;
-  msg.data.len = len;
-  msg.data.scheme = hc->req.scheme;
-  msg.data.target_authority_offset = hc->req.target_authority_offset;
-  msg.data.target_authority_len = hc->req.target_authority_len;
-  msg.data.target_path_offset = hc->req.target_path_offset;
-  msg.data.target_path_len = hc->req.target_path_len;
-  msg.data.target_query_offset = hc->req.target_query_offset;
-  msg.data.target_query_len = hc->req.target_query_len;
-  msg.data.headers_offset = hc->req.headers_offset;
-  msg.data.headers_len = hc->req.headers_len;
-  msg.data.body_offset = hc->req.body_offset;
-  msg.data.body_len = hc->req.body_len;
-  msg.data.headers_ctx = pointer_to_uword (hc->req.headers);
-  msg.data.upgrade_proto = hc->req.upgrade_proto;
-
-  svm_fifo_seg_t segs[2] = { { (u8 *) &msg, sizeof (msg) },
-                            { hc->req.rx_buf, len } };
-
-  rv = svm_fifo_enqueue_segments (as->rx_fifo, segs, 2, 0 /* allow partial */);
-  ASSERT (rv == (sizeof (msg) + len));
-
-  body_sent = len - hc->req.control_data_len;
-  hc->req.to_recv = hc->req.body_len - body_sent;
-  if (hc->req.to_recv == 0)
-    {
-      /* drop everything, we do not support pipelining */
-      http_read_message_drop_all (hc);
-      /* all sent, we are done */
-      http_req_state_change (hc, HTTP_REQ_STATE_WAIT_APP_REPLY);
-    }
-  else
-    {
-      http_read_message_drop (hc, len);
-      /* stream rest of the response body */
-      http_req_state_change (hc, HTTP_REQ_STATE_TRANSPORT_IO_MORE_DATA);
-    }
-
-  app_wrk = app_worker_get_if_valid (as->app_wrk_index);
-  if (app_wrk)
-    app_worker_rx_notify (app_wrk, as);
-
-  return HTTP_SM_STOP;
-
-error:
-  http_read_message_drop_all (hc);
-  http_send_error (hc, ec);
-  session_transport_closing_notify (&hc->connection);
-  http_disconnect_transport (hc);
-
-  return HTTP_SM_ERROR;
-}
-
-static void
-http_write_app_headers (http_conn_t *hc, http_msg_t *msg, u8 **tx_buf)
-{
-  http_main_t *hm = &http_main;
-  session_t *as;
-  u8 *app_headers, *p, *end;
-  u32 *tmp;
-  int rv;
-
-  as = session_get_from_handle (hc->h_pa_session_handle);
-
-  /* read app header list */
-  if (msg->data.type == HTTP_MSG_DATA_PTR)
-    {
-      uword app_headers_ptr;
-      rv = svm_fifo_dequeue (as->tx_fifo, sizeof (app_headers_ptr),
-                            (u8 *) &app_headers_ptr);
-      ASSERT (rv == sizeof (app_headers_ptr));
-      app_headers = uword_to_pointer (app_headers_ptr, u8 *);
-    }
-  else
-    {
-      app_headers = hm->app_header_lists[hc->c_thread_index];
-      rv = svm_fifo_dequeue (as->tx_fifo, msg->data.headers_len, app_headers);
-      ASSERT (rv == msg->data.headers_len);
-    }
-
-  /* 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';
-       }
-    }
-}
-
-static http_sm_result_t
-http_req_state_wait_app_reply (http_conn_t *hc, transport_send_params_t *sp)
-{
-  http_main_t *hm = &http_main;
-  u8 *response;
-  u32 sent;
-  f64 now;
-  session_t *as;
-  http_status_code_t sc;
-  http_msg_t msg;
-  int rv;
-  http_sm_result_t sm_result = HTTP_SM_ERROR;
-  http_req_state_t next_state = HTTP_REQ_STATE_WAIT_TRANSPORT_METHOD;
-
-  as = session_get_from_handle (hc->h_pa_session_handle);
-
-  rv = svm_fifo_dequeue (as->tx_fifo, sizeof (msg), (u8 *) &msg);
-  ASSERT (rv == sizeof (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 = hm->tx_bufs[hc->c_thread_index];
-  vec_reset_length (response);
-  /*
-   * Add "protocol layer" headers:
-   * - current time
-   * - server name
-   * - data length
-   */
-  now = clib_timebase_now (&hm->timebase);
-  response =
-    format (response, http_response_template, http_status_code_str[msg.code],
-           /* Date */
-           format_clib_timebase_time, now,
-           /* 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 (hc->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 (hc->req.upgrade_proto > HTTP_UPGRADE_PROTO_NA)
-       {
-         response = format (response, connection_upgrade_template,
-                            http_upgrade_proto_str[hc->req.upgrade_proto]);
-         if (hc->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 (hc->req.rx_buf);
-      vec_free (hc->req.headers);
-      http_buffer_free (&hc->req.tx_buf);
-      hc->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);
-      http_write_app_headers (hc, &msg, &response);
-    }
-  /* Add empty line after headers */
-  response = format (response, "\r\n");
-  HTTP_DBG (3, "%v", response);
-
-  sent = http_send_data (hc, response, vec_len (response));
-  if (sent != vec_len (response))
-    {
-      clib_warning ("sending status-line and headers failed!");
-      sc = HTTP_STATUS_INTERNAL_ERROR;
-      goto error;
-    }
-
-  if (msg.data.body_len)
-    {
-      /* Start sending the actual data */
-      http_buffer_init (&hc->req.tx_buf, msg_to_buf_type[msg.data.type],
-                       as->tx_fifo, msg.data.body_len);
-      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 (hc, next_state);
-
-  ASSERT (sp->max_burst_size >= sent);
-  sp->max_burst_size -= sent;
-  return sm_result;
-
-error:
-  http_send_error (hc, sc);
-  session_transport_closing_notify (&hc->connection);
-  http_disconnect_transport (hc);
-  return HTTP_SM_STOP;
-}
-
-static http_sm_result_t
-http_req_state_wait_app_method (http_conn_t *hc, transport_send_params_t *sp)
-{
-  http_main_t *hm = &http_main;
-  http_msg_t msg;
-  session_t *as;
-  u8 *target_buff = 0, *request = 0, *target;
-  u32 sent;
-  int rv;
-  http_sm_result_t sm_result = HTTP_SM_ERROR;
-  http_req_state_t next_state;
-
-  as = session_get_from_handle (hc->h_pa_session_handle);
-
-  rv = svm_fifo_dequeue (as->tx_fifo, sizeof (msg), (u8 *) &msg);
-  ASSERT (rv == sizeof (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 */
-  if (msg.data.type == HTTP_MSG_DATA_PTR)
-    {
-      uword target_ptr;
-      rv = svm_fifo_dequeue (as->tx_fifo, sizeof (target_ptr),
-                            (u8 *) &target_ptr);
-      ASSERT (rv == sizeof (target_ptr));
-      target = uword_to_pointer (target_ptr, u8 *);
-    }
-  else
-    {
-      vec_validate (target_buff, msg.data.target_path_len - 1);
-      rv =
-       svm_fifo_dequeue (as->tx_fifo, msg.data.target_path_len, target_buff);
-      ASSERT (rv == msg.data.target_path_len);
-      target = target_buff;
-    }
-
-  request = hm->tx_bufs[hc->c_thread_index];
-  vec_reset_length (request);
-  /* 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, http_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, http_post_request_template,
-                       /* target */
-                       target,
-                       /* Host */
-                       hc->host,
-                       /* User-Agent */
-                       hc->app_name,
-                       /* Content-Length */
-                       msg.data.body_len);
-
-      http_buffer_init (&hc->req.tx_buf, msg_to_buf_type[msg.data.type],
-                       as->tx_fifo, msg.data.body_len);
-
-      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);
-      http_write_app_headers (hc, &msg, &request);
-    }
-  /* Add empty line after headers */
-  request = format (request, "\r\n");
-  HTTP_DBG (3, "%v", request);
-
-  sent = http_send_data (hc, request, vec_len (request));
-  if (sent != vec_len (request))
-    {
-      clib_warning ("sending request-line and headers failed!");
-      sm_result = HTTP_SM_ERROR;
-      goto error;
-    }
-
-  http_req_state_change (hc, next_state);
-  goto done;
-
-error:
-  svm_fifo_dequeue_drop_all (as->tx_fifo);
-  session_transport_closing_notify (&hc->connection);
-  session_transport_closed_notify (&hc->connection);
-  http_disconnect_transport (hc);
-
-done:
-  vec_free (target_buff);
-  return sm_result;
-}
-
-static http_sm_result_t
-http_req_state_transport_io_more_data (http_conn_t *hc,
-                                      transport_send_params_t *sp)
-{
-  session_t *as, *ts;
-  app_worker_t *app_wrk;
-  svm_fifo_seg_t _seg, *seg = &_seg;
-  u32 max_len, max_deq, max_enq, n_segs = 1;
-  int rv, len;
-
-  as = session_get_from_handle (hc->h_pa_session_handle);
-  ts = session_get_from_handle (hc->h_tc_session_handle);
-
-  max_deq = svm_fifo_max_dequeue (ts->rx_fifo);
-  if (max_deq == 0)
-    {
-      HTTP_DBG (1, "no data to deq");
-      return HTTP_SM_STOP;
-    }
-
-  max_enq = svm_fifo_max_enqueue (as->rx_fifo);
-  if (max_enq == 0)
-    {
-      HTTP_DBG (1, "app's rx fifo full");
-      svm_fifo_add_want_deq_ntf (as->rx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
-      return HTTP_SM_STOP;
-    }
-
-  max_len = clib_min (max_enq, max_deq);
-  len = svm_fifo_segments (ts->rx_fifo, 0, seg, &n_segs, max_len);
-  if (len < 0)
-    {
-      HTTP_DBG (1, "svm_fifo_segments() len %d", len);
-      return HTTP_SM_STOP;
-    }
-
-  rv = svm_fifo_enqueue_segments (as->rx_fifo, seg, 1, 0 /* allow partial */);
-  if (rv < 0)
-    {
-      clib_warning ("data enqueue failed, rv: %d", rv);
-      return HTTP_SM_ERROR;
-    }
-
-  svm_fifo_dequeue_drop (ts->rx_fifo, rv);
-  if (rv > hc->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 (hc, HTTP_REQ_STATE_WAIT_APP_METHOD);
-      return HTTP_SM_ERROR;
-    }
-  hc->req.to_recv -= rv;
-  HTTP_DBG (1, "drained %d from ts; remains %lu", rv, hc->req.to_recv);
-
-  /* Finished transaction:
-   * server back to HTTP_REQ_STATE_WAIT_APP_REPLY
-   * client to HTTP_REQ_STATE_WAIT_APP_METHOD */
-  if (hc->req.to_recv == 0)
-    http_req_state_change (hc, hc->is_server ? HTTP_REQ_STATE_WAIT_APP_REPLY :
-                                              HTTP_REQ_STATE_WAIT_APP_METHOD);
-
-  app_wrk = app_worker_get_if_valid (as->app_wrk_index);
-  if (app_wrk)
-    app_worker_rx_notify (app_wrk, as);
-
-  if (svm_fifo_max_dequeue_cons (ts->rx_fifo))
-    session_enqueue_notify (ts);
-
-  return HTTP_SM_STOP;
-}
-
-static http_sm_result_t
-http_req_state_app_io_more_data (http_conn_t *hc, transport_send_params_t *sp)
-{
-  u32 max_send = 64 << 10, n_segs;
-  http_buffer_t *hb = &hc->req.tx_buf;
-  svm_fifo_seg_t *seg;
-  session_t *ts;
-  int sent = 0;
-
-  max_send = clib_min (max_send, sp->max_burst_size);
-  ts = session_get_from_handle (hc->h_tc_session_handle);
-  if ((seg = http_buffer_get_segs (hb, max_send, &n_segs)))
-    sent = svm_fifo_enqueue_segments (ts->tx_fifo, seg, n_segs,
-                                     1 /* allow partial */);
-
-  if (sent > 0)
-    {
-      /* Ask scheduler to notify app of deq event if needed */
-      sp->bytes_dequeued += http_buffer_drain (hb, sent);
-      sp->max_burst_size -= sent;
-    }
-
-  /* Not finished sending all data */
-  if (!http_buffer_is_drained (hb))
-    {
-      if (sent && svm_fifo_set_event (ts->tx_fifo))
-       session_program_tx_io_evt (ts->handle, SESSION_IO_EVT_TX);
-
-      if (svm_fifo_max_enqueue (ts->tx_fifo) < HTTP_FIFO_THRESH)
-       {
-         /* Deschedule http session and wait for deq notification if
-          * underlying ts tx fifo almost full */
-         svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
-         transport_connection_deschedule (&hc->connection);
-         sp->flags |= TRANSPORT_SND_F_DESCHED;
-       }
+      uword target_ptr;
+      rv = svm_fifo_dequeue (as->tx_fifo, sizeof (target_ptr),
+                            (u8 *) &target_ptr);
+      ASSERT (rv == sizeof (target_ptr));
+      target = uword_to_pointer (target_ptr, u8 *);
     }
   else
     {
-      if (sent && svm_fifo_set_event (ts->tx_fifo))
-       session_program_tx_io_evt (ts->handle, SESSION_IO_EVT_TX_FLUSH);
-
-      /* Finished transaction:
-       * server back to HTTP_REQ_STATE_WAIT_TRANSPORT_METHOD
-       * client to HTTP_REQ_STATE_WAIT_TRANSPORT_REPLY */
-      http_req_state_change (hc, hc->is_server ?
-                                  HTTP_REQ_STATE_WAIT_TRANSPORT_METHOD :
-                                  HTTP_REQ_STATE_WAIT_TRANSPORT_REPLY);
-      http_buffer_free (hb);
+      vec_reset_length (req->target);
+      vec_validate (req->target, msg->data.target_path_len - 1);
+      rv =
+       svm_fifo_dequeue (as->tx_fifo, msg->data.target_path_len, req->target);
+      ASSERT (rv == msg->data.target_path_len);
+      target = req->target;
     }
+  return target;
+}
 
-  return HTTP_SM_STOP;
+u8 *
+http_get_tx_buf (http_conn_t *hc)
+{
+  http_main_t *hm = &http_main;
+  u8 *buf = hm->tx_bufs[hc->c_thread_index];
+  vec_reset_length (buf);
+  return buf;
 }
 
-static http_sm_result_t
-http_req_state_tunnel_rx (http_conn_t *hc, transport_send_params_t *sp)
+u8 *
+http_get_rx_buf (http_conn_t *hc)
 {
-  u32 max_deq, max_enq, max_read, n_segs = 2;
-  svm_fifo_seg_t segs[n_segs];
-  int n_written = 0;
-  session_t *as, *ts;
-  app_worker_t *app_wrk;
+  http_main_t *hm = &http_main;
+  u8 *buf = hm->rx_bufs[hc->c_thread_index];
+  vec_reset_length (buf);
+  return buf;
+}
 
-  HTTP_DBG (1, "tunnel received data from client");
+void
+http_req_tx_buffer_init (http_req_t *req, http_msg_t *msg)
+{
+  session_t *as = session_get_from_handle (req->app_session_handle);
+  http_buffer_init (&req->tx_buf, msg_to_buf_type[msg->data.type], as->tx_fifo,
+                   msg->data.body_len);
+}
 
-  as = session_get_from_handle (hc->h_pa_session_handle);
-  ts = session_get_from_handle (hc->h_tc_session_handle);
+static void
+http_conn_invalidate_timer_cb (u32 hs_handle)
+{
+  http_conn_t *hc;
 
-  max_deq = svm_fifo_max_dequeue (ts->rx_fifo);
-  if (PREDICT_FALSE (max_deq == 0))
-    {
-      HTTP_DBG (1, "max_deq == 0");
-      return HTTP_SM_STOP;
-    }
-  max_enq = svm_fifo_max_enqueue (as->rx_fifo);
-  if (max_enq == 0)
+  hc =
+    http_conn_get_w_thread_if_valid (hs_handle & 0x00FFFFFF, hs_handle >> 24);
+
+  HTTP_DBG (1, "hc [%u]%x", hs_handle >> 24, hs_handle & 0x00FFFFFF);
+  if (!hc)
     {
-      HTTP_DBG (1, "app's rx fifo full");
-      svm_fifo_add_want_deq_ntf (as->rx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
-      return HTTP_SM_STOP;
+      HTTP_DBG (1, "already deleted");
+      return;
     }
-  max_read = clib_min (max_enq, max_deq);
-  svm_fifo_segments (ts->rx_fifo, 0, segs, &n_segs, max_read);
-  n_written = svm_fifo_enqueue_segments (as->rx_fifo, segs, n_segs, 0);
-  ASSERT (n_written > 0);
-  HTTP_DBG (1, "transfered %u bytes", n_written);
-  svm_fifo_dequeue_drop (ts->rx_fifo, n_written);
-  app_wrk = app_worker_get_if_valid (as->app_wrk_index);
-  if (app_wrk)
-    app_worker_rx_notify (app_wrk, as);
-  if (svm_fifo_max_dequeue_cons (ts->rx_fifo))
-    session_program_rx_io_evt (session_handle (ts));
-
-  return HTTP_SM_STOP;
+
+  hc->timer_handle = HTTP_TIMER_HANDLE_INVALID;
+  hc->pending_timer = 1;
 }
 
-static http_sm_result_t
-http_req_state_tunnel_tx (http_conn_t *hc, transport_send_params_t *sp)
+static void
+http_conn_timeout_cb (void *hc_handlep)
 {
-  u32 max_deq, max_enq, max_read, n_segs = 2;
-  svm_fifo_seg_t segs[n_segs];
-  session_t *as, *ts;
-  int n_written = 0;
-
-  HTTP_DBG (1, "tunnel received data from target");
+  http_conn_t *hc;
+  uword hs_handle;
 
-  as = session_get_from_handle (hc->h_pa_session_handle);
-  ts = session_get_from_handle (hc->h_tc_session_handle);
+  hs_handle = pointer_to_uword (hc_handlep);
+  hc =
+    http_conn_get_w_thread_if_valid (hs_handle & 0x00FFFFFF, hs_handle >> 24);
 
-  max_deq = svm_fifo_max_dequeue_cons (as->tx_fifo);
-  if (PREDICT_FALSE (max_deq == 0))
-    {
-      HTTP_DBG (1, "max_deq == 0");
-      goto check_fifo;
-    }
-  max_enq = svm_fifo_max_enqueue_prod (ts->tx_fifo);
-  if (max_enq == 0)
+  HTTP_DBG (1, "hc [%u]%x", hs_handle >> 24, hs_handle & 0x00FFFFFF);
+  if (!hc)
     {
-      HTTP_DBG (1, "ts tx fifo full");
-      goto check_fifo;
+      HTTP_DBG (1, "already deleted");
+      return;
     }
-  max_read = clib_min (max_enq, max_deq);
-  max_read = clib_min (max_read, sp->max_burst_size);
-  svm_fifo_segments (as->tx_fifo, 0, segs, &n_segs, max_read);
-  n_written = svm_fifo_enqueue_segments (ts->tx_fifo, segs, n_segs, 0);
-  ASSERT (n_written > 0);
-  HTTP_DBG (1, "transfered %u bytes", n_written);
-  sp->bytes_dequeued += n_written;
-  sp->max_burst_size -= n_written;
-  svm_fifo_dequeue_drop (as->tx_fifo, n_written);
-  if (svm_fifo_set_event (ts->tx_fifo))
-    session_program_tx_io_evt (ts->handle, SESSION_IO_EVT_TX);
-
-check_fifo:
-  /* Deschedule and wait for deq notification if ts fifo is almost full */
-  if (svm_fifo_max_enqueue (ts->tx_fifo) < HTTP_FIFO_THRESH)
+
+  if (!hc->pending_timer)
     {
-      svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
-      transport_connection_deschedule (&hc->connection);
-      sp->flags |= TRANSPORT_SND_F_DESCHED;
+      HTTP_DBG (1, "timer not pending");
+      return;
     }
 
-  return HTTP_SM_STOP;
+  session_transport_closing_notify (&hc->connection);
+  http_disconnect_transport (hc);
 }
 
-static http_sm_result_t
-http_req_state_udp_tunnel_rx (http_conn_t *hc, transport_send_params_t *sp)
+/*************************/
+/* session VFT callbacks */
+/*************************/
+
+int
+http_ts_accept_callback (session_t *ts)
 {
-  http_main_t *hm = &http_main;
-  u32 to_deq, capsule_size, dgram_size, n_written = 0;
-  int rv, n_read;
-  session_t *as, *ts;
+  session_t *ts_listener, *as, *asl;
   app_worker_t *app_wrk;
-  u8 payload_offset;
-  u64 payload_len;
-  session_dgram_hdr_t hdr;
-  u8 *buf = 0;
+  http_conn_t *lhc, *hc;
+  u32 hc_index, thresh;
+  int rv;
 
-  HTTP_DBG (1, "udp tunnel received data from client");
+  ts_listener = listen_session_get_from_handle (ts->listener_handle);
+  lhc = http_listener_get (ts_listener->opaque);
 
-  as = session_get_from_handle (hc->h_pa_session_handle);
-  ts = session_get_from_handle (hc->h_tc_session_handle);
-  buf = hm->rx_bufs[hc->c_thread_index];
-  to_deq = svm_fifo_max_dequeue_cons (ts->rx_fifo);
+  hc_index = http_conn_alloc_w_thread (ts->thread_index);
+  hc = http_conn_get_w_thread (hc_index, ts->thread_index);
+  clib_memcpy_fast (hc, lhc, sizeof (*lhc));
+  hc->timer_handle = HTTP_TIMER_HANDLE_INVALID;
+  hc->c_thread_index = ts->thread_index;
+  hc->h_hc_index = hc_index;
 
-  while (to_deq > 0)
-    {
-      /* some bytes remaining to skip? */
-      if (PREDICT_FALSE (hc->req.to_skip))
-       {
-         if (hc->req.to_skip >= to_deq)
-           {
-             svm_fifo_dequeue_drop (ts->rx_fifo, to_deq);
-             hc->req.to_skip -= to_deq;
-             goto done;
-           }
-         else
-           {
-             svm_fifo_dequeue_drop (ts->rx_fifo, hc->req.to_skip);
-             hc->req.to_skip = 0;
-           }
-       }
-      n_read =
-       svm_fifo_peek (ts->rx_fifo, 0, HTTP_CAPSULE_HEADER_MAX_SIZE, buf);
-      ASSERT (n_read > 0);
-      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) */
-             svm_fifo_dequeue_drop_all (ts->rx_fifo);
-             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)
-               {
-                 svm_fifo_dequeue_drop (ts->rx_fifo, payload_len);
-                 to_deq -= payload_len;
-                 continue;
-               }
-             else
-               {
-                 svm_fifo_dequeue_drop (ts->rx_fifo, to_deq);
-                 hc->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;
-       }
+  hc->h_tc_session_handle = session_handle (ts);
+  hc->c_flags |= TRANSPORT_CONNECTION_F_NO_LOOKUP;
+  hc->state = HTTP_CONN_STATE_ESTABLISHED;
 
-      dgram_size = sizeof (hdr) + payload_len;
-      if (svm_fifo_max_enqueue_prod (as->rx_fifo) < dgram_size)
-       {
-         HTTP_DBG (1, "app's rx fifo full");
-         svm_fifo_add_want_deq_ntf (as->rx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
-         goto done;
-       }
+  ts->session_state = SESSION_STATE_READY;
+  /* TODO: TLS set by ALPN result, TCP: first try HTTP/1 */
+  hc->version = HTTP_VERSION_1;
+  ts->opaque = http_make_handle (hc_index, hc->version);
 
-      /* read capsule payload */
-      rv = svm_fifo_peek (ts->rx_fifo, payload_offset, payload_len, buf);
-      ASSERT (rv == payload_len);
-      svm_fifo_dequeue_drop (ts->rx_fifo, capsule_size);
+  /*
+   * Alloc session and initialize
+   */
+  as = session_alloc (hc->c_thread_index);
+  hc->c_s_index = as->session_index;
 
-      hdr.data_length = payload_len;
-      hdr.data_offset = 0;
+  as->app_wrk_index = hc->h_pa_wrk_index;
+  as->connection_index = hc->h_hc_index;
+  as->session_state = SESSION_STATE_ACCEPTING;
 
-      /* send datagram header and payload */
-      svm_fifo_seg_t segs[2] = { { (u8 *) &hdr, sizeof (hdr) },
-                                { buf, payload_len } };
-      rv = svm_fifo_enqueue_segments (as->rx_fifo, segs, 2, 0);
-      ASSERT (rv > 0);
+  asl = listen_session_get_from_handle (lhc->h_pa_session_handle);
+  as->session_type = asl->session_type;
+  as->listener_handle = lhc->h_pa_session_handle;
 
-      n_written += dgram_size;
-      to_deq -= capsule_size;
+  /*
+   * Init session fifos and notify app
+   */
+  if ((rv = app_worker_init_accepted (as)))
+    {
+      HTTP_DBG (1, "failed to allocate fifos");
+      hc->h_pa_session_handle = SESSION_INVALID_HANDLE;
+      session_free (as);
+      return rv;
     }
 
-done:
-  HTTP_DBG (1, "written %lu bytes", n_written);
+  hc->h_pa_session_handle = session_handle (as);
+  hc->h_pa_wrk_index = as->app_wrk_index;
+  app_wrk = app_worker_get (as->app_wrk_index);
+
+  HTTP_DBG (1, "Accepted on listener %u new connection [%u]%x",
+           ts_listener->opaque, vlib_get_thread_index (), hc_index);
 
-  if (n_written)
+  if ((rv = app_worker_accept_notify (app_wrk, as)))
     {
-      app_wrk = app_worker_get_if_valid (as->app_wrk_index);
-      if (app_wrk)
-       app_worker_rx_notify (app_wrk, as);
+      HTTP_DBG (0, "app accept returned");
+      session_free (as);
+      return rv;
     }
-  if (svm_fifo_max_dequeue_cons (ts->rx_fifo))
-    session_program_rx_io_evt (session_handle (ts));
 
-  return HTTP_SM_STOP;
+  /* Avoid enqueuing small chunks of data on transport tx notifications. If
+   * the fifo is small (under 16K) we set the threshold to it's size, meaning
+   * a notification will be given when the fifo empties.
+   */
+  ts = session_get_from_handle (hc->h_tc_session_handle);
+  thresh = clib_min (svm_fifo_size (ts->tx_fifo), HTTP_FIFO_THRESH);
+  svm_fifo_set_deq_thresh (ts->tx_fifo, thresh);
+
+  http_conn_timer_start (hc);
+
+  return 0;
 }
 
-static http_sm_result_t
-http_req_state_udp_tunnel_tx (http_conn_t *hc, transport_send_params_t *sp)
+static int
+http_ts_connected_callback (u32 http_app_index, u32 ho_hc_index, session_t *ts,
+                           session_error_t err)
 {
-  http_main_t *hm = &http_main;
-  u32 to_deq, capsule_size, dgram_size, n_written = 0;
-  session_t *as, *ts;
+  u32 new_hc_index;
+  session_t *as;
+  http_conn_t *hc, *ho_hc;
+  app_worker_t *app_wrk;
   int rv;
-  session_dgram_pre_hdr_t hdr;
-  u8 *buf;
-  u8 *payload;
-
-  HTTP_DBG (1, "udp tunnel received data from target");
 
-  as = session_get_from_handle (hc->h_pa_session_handle);
-  ts = session_get_from_handle (hc->h_tc_session_handle);
-  buf = hm->tx_bufs[hc->c_thread_index];
-  to_deq = svm_fifo_max_dequeue_cons (as->tx_fifo);
+  ho_hc = http_ho_conn_get (ho_hc_index);
+  ASSERT (ho_hc->state == HTTP_CONN_STATE_CONNECTING);
 
-  while (to_deq > 0)
+  if (err)
     {
-      /* read datagram header */
-      rv = svm_fifo_peek (as->tx_fifo, 0, sizeof (hdr), (u8 *) &hdr);
-      ASSERT (rv == sizeof (hdr) &&
-             hdr.data_length <= HTTP_UDP_PAYLOAD_MAX_LEN);
-      ASSERT (to_deq >= hdr.data_length + SESSION_CONN_HDR_LEN);
-      dgram_size = hdr.data_length + SESSION_CONN_HDR_LEN;
-
-      if (svm_fifo_max_enqueue_prod (ts->tx_fifo) <
-         (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 */
-      rv = svm_fifo_peek (as->tx_fifo, SESSION_CONN_HDR_LEN, hdr.data_length,
-                         payload);
-      ASSERT (rv == hdr.data_length);
-      svm_fifo_dequeue_drop (as->tx_fifo, dgram_size);
-      /* send capsule */
-      rv = svm_fifo_enqueue (ts->tx_fifo, capsule_size, buf);
-      ASSERT (rv == capsule_size);
-
-      n_written += capsule_size;
-      to_deq -= dgram_size;
+      clib_warning ("half-open hc index %d, error: %U", ho_hc_index,
+                   format_session_error, err);
+      app_wrk = app_worker_get_if_valid (ho_hc->h_pa_wrk_index);
+      if (app_wrk)
+       app_worker_connect_notify (app_wrk, 0, err, ho_hc->h_pa_app_api_ctx);
+      return 0;
     }
 
-done:
-  HTTP_DBG (1, "written %lu bytes", n_written);
-  if (n_written)
+  new_hc_index = http_conn_alloc_w_thread (ts->thread_index);
+  hc = http_conn_get_w_thread (new_hc_index, ts->thread_index);
+
+  clib_memcpy_fast (hc, ho_hc, sizeof (*hc));
+
+  hc->timer_handle = HTTP_TIMER_HANDLE_INVALID;
+  hc->c_thread_index = ts->thread_index;
+  hc->h_tc_session_handle = session_handle (ts);
+  hc->h_hc_index = new_hc_index;
+  hc->c_flags |= TRANSPORT_CONNECTION_F_NO_LOOKUP;
+  hc->state = HTTP_CONN_STATE_ESTABLISHED;
+  ts->session_state = SESSION_STATE_READY;
+  /* TODO: TLS set by ALPN result, TCP: prior knowledge (set in ho) */
+  ts->opaque = http_make_handle (new_hc_index, hc->version);
+
+  /* allocate app session and initialize */
+
+  as = session_alloc (hc->c_thread_index);
+  hc->c_s_index = as->session_index;
+  as->connection_index = new_hc_index;
+  as->app_wrk_index = hc->h_pa_wrk_index;
+  as->session_state = SESSION_STATE_READY;
+  as->opaque = hc->h_pa_app_api_ctx;
+  as->session_type = session_type_from_proto_and_ip (
+    TRANSPORT_PROTO_HTTP, session_type_is_ip4 (ts->session_type));
+
+  HTTP_DBG (1, "half-open hc index %x,  hc [%u]%x", ho_hc_index,
+           ts->thread_index, new_hc_index);
+
+  app_wrk = app_worker_get (hc->h_pa_wrk_index);
+  if (!app_wrk)
     {
-      if (svm_fifo_set_event (ts->tx_fifo))
-       session_program_tx_io_evt (ts->handle, SESSION_IO_EVT_TX);
+      clib_warning ("no app worker");
+      return -1;
     }
 
-  /* Deschedule and wait for deq notification if ts fifo is almost full */
-  if (svm_fifo_max_enqueue (ts->tx_fifo) < HTTP_FIFO_THRESH)
+  if ((rv = app_worker_init_connected (app_wrk, as)))
     {
-      svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
-      transport_connection_deschedule (&hc->connection);
-      sp->flags |= TRANSPORT_SND_F_DESCHED;
+      HTTP_DBG (1, "failed to allocate fifos");
+      session_free (as);
+      return rv;
     }
+  app_worker_connect_notify (app_wrk, as, err, hc->h_pa_app_api_ctx);
+  hc->h_pa_session_handle = session_handle (as);
+  http_conn_timer_start (hc);
 
-  return HTTP_SM_STOP;
+  return 0;
 }
 
-typedef http_sm_result_t (*http_sm_handler) (http_conn_t *,
-                                            transport_send_params_t *sp);
-
-static http_sm_handler tx_state_funcs[HTTP_REQ_N_STATES] = {
-  0, /* idle */
-  http_req_state_wait_app_method,
-  0, /* wait transport reply */
-  0, /* transport io more data */
-  0, /* wait transport method */
-  http_req_state_wait_app_reply,
-  http_req_state_app_io_more_data,
-  http_req_state_tunnel_tx,
-  http_req_state_udp_tunnel_tx,
-};
-
-static_always_inline int
-http_req_state_is_tx_valid (http_conn_t *hc)
+static void
+http_ts_disconnect_callback (session_t *ts)
 {
-  return tx_state_funcs[hc->req.state] ? 1 : 0;
-}
+  http_conn_t *hc;
+  u32 hc_index = http_conn_index_from_handle (ts->opaque);
 
-static http_sm_handler rx_state_funcs[HTTP_REQ_N_STATES] = {
-  0, /* idle */
-  0, /* wait app method */
-  http_req_state_wait_transport_reply,
-  http_req_state_transport_io_more_data,
-  http_req_state_wait_transport_method,
-  0, /* wait app reply */
-  0, /* app io more data */
-  http_req_state_tunnel_rx,
-  http_req_state_udp_tunnel_rx,
-};
+  HTTP_DBG (1, "hc [%u]%x", ts->thread_index, hc_index);
 
-static_always_inline int
-http_req_state_is_rx_valid (http_conn_t *hc)
-{
-  return rx_state_funcs[hc->req.state] ? 1 : 0;
+  hc = http_conn_get_w_thread (hc_index, ts->thread_index);
+
+  if (hc->state < HTTP_CONN_STATE_TRANSPORT_CLOSED)
+    hc->state = HTTP_CONN_STATE_TRANSPORT_CLOSED;
+
+  http_vfts[hc->version].transport_close_callback (hc);
 }
 
-static_always_inline void
-http_req_run_state_machine (http_conn_t *hc, transport_send_params_t *sp,
-                           u8 is_tx)
+static void
+http_ts_reset_callback (session_t *ts)
 {
-  http_sm_result_t res;
+  http_conn_t *hc;
+  u32 hc_index = http_conn_index_from_handle (ts->opaque);
 
-  do
-    {
-      if (is_tx)
-       res = tx_state_funcs[hc->req.state](hc, sp);
-      else
-       res = rx_state_funcs[hc->req.state](hc, sp);
-      if (res == HTTP_SM_ERROR)
-       {
-         HTTP_DBG (1, "error in state machine %d", res);
-         return;
-       }
-    }
-  while (res == HTTP_SM_CONTINUE);
+  HTTP_DBG (1, "hc [%u]%x", ts->thread_index, hc_index);
 
-  /* Reset the session expiration timer */
-  http_conn_timer_update (hc);
+  hc = http_conn_get_w_thread (hc_index, ts->thread_index);
+
+  hc->state = HTTP_CONN_STATE_CLOSED;
+  session_transport_reset_notify (&hc->connection);
+
+  http_disconnect_transport (hc);
 }
 
 static int
 http_ts_rx_callback (session_t *ts)
 {
   http_conn_t *hc;
+  u32 hc_index = http_conn_index_from_handle (ts->opaque);
 
-  HTTP_DBG (1, "hc [%u]%x", ts->thread_index, ts->opaque);
+  HTTP_DBG (1, "hc [%u]%x", ts->thread_index, hc_index);
 
-  hc = http_conn_get_w_thread (ts->opaque, ts->thread_index);
+  hc = http_conn_get_w_thread (hc_index, ts->thread_index);
 
   if (hc->state == HTTP_CONN_STATE_CLOSED)
     {
@@ -2187,18 +596,7 @@ http_ts_rx_callback (session_t *ts)
       return 0;
     }
 
-  if (!http_req_state_is_rx_valid (hc))
-    {
-      clib_warning ("hc [%u]%x invalid rx state: http req state "
-                   "'%U', session state '%U'",
-                   ts->thread_index, ts->opaque, format_http_req_state,
-                   hc->req.state, format_http_conn_state, hc);
-      svm_fifo_dequeue_drop_all (ts->rx_fifo);
-      return 0;
-    }
-
-  HTTP_DBG (1, "run state machine");
-  http_req_run_state_machine (hc, 0, 0);
+  http_vfts[http_version_from_handle (ts->opaque)].transport_rx_callback (hc);
 
   if (hc->state == HTTP_CONN_STATE_TRANSPORT_CLOSED)
     {
@@ -2213,7 +611,8 @@ http_ts_builtin_tx_callback (session_t *ts)
 {
   http_conn_t *hc;
 
-  hc = http_conn_get_w_thread (ts->opaque, ts->thread_index);
+  hc = http_conn_get_w_thread (http_conn_index_from_handle (ts->opaque),
+                              ts->thread_index);
   HTTP_DBG (1, "transport connection reschedule");
   transport_connection_reschedule (&hc->connection);
 
@@ -2224,18 +623,24 @@ static void
 http_ts_cleanup_callback (session_t *ts, session_cleanup_ntf_t ntf)
 {
   http_conn_t *hc;
+  http_req_t *req;
+  u32 hc_index;
 
   if (ntf == SESSION_CLEANUP_TRANSPORT)
     return;
 
-  hc = http_conn_get_w_thread (ts->opaque, ts->thread_index);
-
-  HTTP_DBG (1, "going to free hc [%u]%x", ts->thread_index, ts->opaque);
+  hc_index = http_conn_index_from_handle (ts->opaque);
+  hc = http_conn_get_w_thread (hc_index, ts->thread_index);
 
-  vec_free (hc->req.rx_buf);
-  vec_free (hc->req.headers);
+  HTTP_DBG (1, "going to free hc [%u]%x", ts->thread_index, hc_index);
 
-  http_buffer_free (&hc->req.tx_buf);
+  pool_foreach (req, hc->req_pool)
+    {
+      vec_free (req->headers);
+      vec_free (req->target);
+      http_buffer_free (&req->tx_buf);
+    }
+  pool_free (hc->req_pool);
 
   if (hc->pending_timer == 0)
     http_conn_timer_stop (hc);
@@ -2254,8 +659,9 @@ static void
 http_ts_ho_cleanup_callback (session_t *ts)
 {
   http_conn_t *ho_hc;
-  HTTP_DBG (1, "half open: %x", ts->opaque);
-  ho_hc = http_ho_conn_get (ts->opaque);
+  u32 ho_hc_index = http_conn_index_from_handle (ts->opaque);
+  HTTP_DBG (1, "half open: %x", ho_hc_index);
+  ho_hc = http_ho_conn_get (ho_hc_index);
   session_half_open_delete_notify (&ho_hc->connection);
   http_ho_conn_free (ho_hc);
 }
@@ -2286,6 +692,10 @@ static session_cb_vft_t http_app_cb_vft = {
   .builtin_app_tx_callback = http_ts_builtin_tx_callback,
 };
 
+/*********************************/
+/* transport proto VFT callbacks */
+/*********************************/
+
 static clib_error_t *
 http_transport_enable (vlib_main_t *vm, u8 is_en)
 {
@@ -2380,6 +790,8 @@ http_transport_connect (transport_endpoint_cfg_t *tep)
   hc->h_pa_wrk_index = sep->app_wrk_index;
   hc->h_pa_app_api_ctx = sep->opaque;
   hc->state = HTTP_CONN_STATE_CONNECTING;
+  /* TODO: set to HTTP_VERSION_NA in case of TLS (when supported) */
+  hc->version = HTTP_VERSION_1;
   cargs->api_context = hc_index;
 
   ext_cfg = session_endpoint_get_ext_cfg (sep, TRANSPORT_ENDPT_EXT_CFG_HTTP);
@@ -2518,7 +930,6 @@ http_stop_listen (u32 listener_index)
 static void
 http_transport_close (u32 hc_index, u32 thread_index)
 {
-  session_t *as;
   http_conn_t *hc;
 
   HTTP_DBG (1, "App disconnecting [%u]%x", thread_index, hc_index);
@@ -2535,19 +946,25 @@ http_transport_close (u32 hc_index, u32 thread_index)
       HTTP_DBG (1, "nothing to do, already closed");
       return;
     }
-  as = session_get_from_handle (hc->h_pa_session_handle);
 
-  /* Nothing more to send, confirm close */
-  if (!svm_fifo_max_dequeue_cons (as->tx_fifo))
-    {
-      session_transport_closed_notify (&hc->connection);
-      http_disconnect_transport (hc);
-    }
-  else
+  http_vfts[hc->version].app_close_callback (hc);
+}
+
+static void
+http_transport_reset (u32 hc_index, u32 thread_index)
+{
+  http_conn_t *hc;
+
+  HTTP_DBG (1, "App disconnecting [%u]%x", thread_index, hc_index);
+
+  hc = http_conn_get_w_thread (hc_index, thread_index);
+  if (hc->state == HTTP_CONN_STATE_CLOSED)
     {
-      /* Wait for all data to be written to ts */
-      hc->state = HTTP_CONN_STATE_APP_CLOSED;
+      HTTP_DBG (1, "nothing to do, already closed");
+      return;
     }
+
+  http_vfts[hc->version].app_reset_callback (hc);
 }
 
 static transport_connection_t *
@@ -2585,30 +1002,7 @@ http_app_tx_callback (void *session, transport_send_params_t *sp)
   max_burst_sz = sp->max_burst_size * TRANSPORT_PACER_MIN_MSS;
   sp->max_burst_size = max_burst_sz;
 
-  if (!http_req_state_is_tx_valid (hc))
-    {
-      /* Sometimes the server apps can send the response earlier
-       * than expected (e.g when rejecting a bad request)*/
-      if (hc->req.state == HTTP_REQ_STATE_TRANSPORT_IO_MORE_DATA &&
-         hc->is_server)
-       {
-         svm_fifo_dequeue_drop_all (as->rx_fifo);
-         hc->req.state = HTTP_REQ_STATE_WAIT_APP_REPLY;
-       }
-      else
-       {
-         clib_warning ("hc [%u]%x invalid tx state: http req state "
-                       "'%U', session state '%U'",
-                       as->thread_index, as->connection_index,
-                       format_http_req_state, hc->req.state,
-                       format_http_conn_state, hc);
-         svm_fifo_dequeue_drop_all (as->tx_fifo);
-         return 0;
-       }
-    }
-
-  HTTP_DBG (1, "run state machine");
-  http_req_run_state_machine (hc, sp, 1);
+  http_vfts[hc->version].app_tx_callback (hc, sp);
 
   if (hc->state == HTTP_CONN_STATE_APP_CLOSED)
     {
@@ -2630,8 +1024,7 @@ http_app_rx_evt_cb (transport_connection_t *tc)
   http_conn_t *hc = (http_conn_t *) tc;
   HTTP_DBG (1, "hc [%u]%x", vlib_get_thread_index (), hc->h_hc_index);
 
-  if (hc->req.state == HTTP_REQ_STATE_TUNNEL)
-    http_req_state_tunnel_rx (hc, 0);
+  http_vfts[hc->version].app_rx_evt_callback (hc);
 
   return 0;
 }
@@ -2759,6 +1152,7 @@ static const transport_proto_vft_t http_proto = {
   .start_listen = http_start_listen,
   .stop_listen = http_stop_listen,
   .close = http_transport_close,
+  .reset = http_transport_reset,
   .cleanup_ho = http_transport_cleanup_ho,
   .custom_tx = http_app_tx_callback,
   .app_rx_evt = http_app_rx_evt_cb,
index d1e81ab..178bdd1 100644 (file)
 #define SRC_PLUGINS_HTTP_HTTP_H_
 
 #include <ctype.h>
-
 #include <vnet/plugin/plugin.h>
-#include <vpp/app/version.h>
-
-#include <vppinfra/time_range.h>
-
-#include <vnet/session/application_interface.h>
-#include <vnet/session/application.h>
-#include <http/http_buffer.h>
+#include <vnet/ip/format.h>
+#include <vnet/ip/ip46_address.h>
 
 #define HTTP_DEBUG 0
 
@@ -49,20 +43,6 @@ typedef struct transport_endpt_cfg_http
   http_udp_tunnel_mode_t udp_tunnel_mode; /**< connect-udp mode */
 } transport_endpt_cfg_http_t;
 
-typedef struct http_conn_id_
-{
-  union
-  {
-    session_handle_t app_session_handle;
-    u32 parent_app_api_ctx;
-  };
-  session_handle_t tc_session_handle;
-  u32 parent_app_wrk_index;
-} http_conn_id_t;
-
-STATIC_ASSERT (sizeof (http_conn_id_t) <= TRANSPORT_CONN_ID_LEN,
-              "ctx id must be less than TRANSPORT_CONN_ID_LEN");
-
 typedef struct
 {
   char *base;
@@ -71,40 +51,6 @@ typedef struct
 
 #define http_token_lit(s) (s), sizeof (s) - 1
 
-#define foreach_http_conn_state                                               \
-  _ (LISTEN, "LISTEN")                                                        \
-  _ (CONNECTING, "CONNECTING")                                                \
-  _ (ESTABLISHED, "ESTABLISHED")                                              \
-  _ (TRANSPORT_CLOSED, "TRANSPORT-CLOSED")                                    \
-  _ (APP_CLOSED, "APP-CLOSED")                                                \
-  _ (CLOSED, "CLOSED")
-
-typedef enum http_conn_state_
-{
-#define _(s, str) HTTP_CONN_STATE_##s,
-  foreach_http_conn_state
-#undef _
-} http_conn_state_t;
-
-#define foreach_http_req_state                                                \
-  _ (0, IDLE, "idle")                                                         \
-  _ (1, WAIT_APP_METHOD, "wait app method")                                   \
-  _ (2, WAIT_TRANSPORT_REPLY, "wait transport reply")                         \
-  _ (3, TRANSPORT_IO_MORE_DATA, "transport io more data")                     \
-  _ (4, WAIT_TRANSPORT_METHOD, "wait transport method")                       \
-  _ (5, WAIT_APP_REPLY, "wait app reply")                                     \
-  _ (6, APP_IO_MORE_DATA, "app io more data")                                 \
-  _ (7, TUNNEL, "tunnel")                                                     \
-  _ (8, UDP_TUNNEL, "udp tunnel")
-
-typedef enum http_req_state_
-{
-#define _(n, s, str) HTTP_REQ_STATE_##s = n,
-  foreach_http_req_state
-#undef _
-    HTTP_REQ_N_STATES
-} http_req_state_t;
-
 typedef enum http_req_method_
 {
   HTTP_REQ_GET = 0,
@@ -118,14 +64,6 @@ typedef enum http_msg_type_
   HTTP_MSG_REPLY
 } http_msg_type_t;
 
-typedef enum http_target_form_
-{
-  HTTP_TARGET_ORIGIN_FORM,
-  HTTP_TARGET_ABSOLUTE_FORM,
-  HTTP_TARGET_AUTHORITY_FORM,
-  HTTP_TARGET_ASTERISK_FORM
-} http_target_form_t;
-
 #define foreach_http_content_type                                             \
   _ (APP_7Z, ".7z", "application/x-7z-compressed")                            \
   _ (APP_DOC, ".doc", "application/msword")                                   \
@@ -432,118 +370,6 @@ typedef struct http_msg_
   http_msg_data_t data;
 } http_msg_t;
 
-typedef struct http_req_
-{
-  http_req_state_t state; /* state-machine state */
-
-  http_buffer_t tx_buf; /* message body from app to be sent */
-
-  /*
-   * for parsing of incoming message from transport
-   */
-  u8 *rx_buf;          /* this should hold at least control data */
-  u32 rx_buf_offset;   /* current offset during parsing */
-  u32 control_data_len; /* start line + headers + empty line */
-
-  union
-  {
-    u64 to_recv; /* remaining bytes of body to receive from transport */
-    u64 to_skip; /* remaining bytes of capsule to skip */
-  };
-
-  u8 is_tunnel;
-
-  /*
-   * parsed metadata for app
-   */
-  union
-  {
-    http_status_code_t status_code;
-    http_req_method_t method;
-  };
-
-  http_target_form_t target_form;
-  http_url_scheme_t scheme;
-  u32 target_authority_offset;
-  u32 target_authority_len;
-  u32 target_path_offset;
-  u32 target_path_len;
-  u32 target_query_offset;
-  u32 target_query_len;
-
-  u32 headers_offset;
-  u32 headers_len;
-
-  u32 body_offset;
-  u64 body_len;
-
-  http_field_line_t *headers;
-  uword content_len_header_index;
-  uword connection_header_index;
-  uword upgrade_header_index;
-  uword host_header_index;
-
-  http_upgrade_proto_t upgrade_proto;
-} http_req_t;
-
-typedef struct http_tc_
-{
-  union
-  {
-    transport_connection_t connection;
-    http_conn_id_t c_http_conn_id;
-  };
-#define h_tc_session_handle c_http_conn_id.tc_session_handle
-#define h_pa_wrk_index     c_http_conn_id.parent_app_wrk_index
-#define h_pa_session_handle c_http_conn_id.app_session_handle
-#define h_pa_app_api_ctx    c_http_conn_id.parent_app_api_ctx
-#define h_hc_index         connection.c_index
-
-  http_conn_state_t state;
-  u32 timer_handle;
-  u32 timeout;
-  u8 pending_timer;
-  u8 *app_name;
-  u8 *host;
-  u8 is_server;
-  http_udp_tunnel_mode_t udp_tunnel_mode;
-
-  http_req_t req;
-} http_conn_t;
-
-typedef struct http_worker_
-{
-  http_conn_t *conn_pool;
-} http_worker_t;
-
-typedef struct http_main_
-{
-  http_worker_t *wrk;
-  http_conn_t *listener_pool;
-  http_conn_t *ho_conn_pool;
-  u32 app_index;
-
-  u8 **rx_bufs;
-  u8 **tx_bufs;
-  u8 **app_header_lists;
-
-  clib_timebase_t timebase;
-
-  u16 *sc_by_u16;
-  /*
-   * Runtime config
-   */
-  u8 debug_level;
-  u8 is_init;
-
-  /*
-   * Config
-   */
-  u64 first_seg_size;
-  u64 add_seg_size;
-  u32 fifo_size;
-} http_main_t;
-
 always_inline u8 *
 format_http_bytes (u8 *s, va_list *va)
 {
@@ -736,124 +562,6 @@ http_path_remove_dot_segments (u8 *path)
   return new_path;
 }
 
-always_inline int
-_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
-_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;
-}
-
 typedef struct
 {
   http_token_t name;
@@ -873,6 +581,16 @@ typedef struct
     .values = 0, .value_by_name = 0, .buf = 0, .concatenated_values = 0,      \
   }
 
+/**
+ * Case-sensitive comparison of two tokens.
+ *
+ * @param actual       Pointer to the first token.
+ * @param actual_len   Length of the first token.
+ * @param expected     Pointer to the second token.
+ * @param expected_len Length of the second token.
+ *
+ * @return @c 1 if tokens are same, @c 0 otherwise.
+ */
 always_inline u8
 http_token_is (const char *actual, uword actual_len, const char *expected,
               uword expected_len)
@@ -903,6 +621,16 @@ http_tolower_word (uword x)
   return (x | y);
 }
 
+/**
+ * Case-insensitive comparison of two tokens.
+ *
+ * @param actual       Pointer to the first token.
+ * @param actual_len   Length of the first token.
+ * @param expected     Pointer to the second token.
+ * @param expected_len Length of the second token.
+ *
+ * @return @c 1 if tokens are same, @c 0 otherwise.
+ */
 always_inline u8
 http_token_is_case (const char *actual, uword actual_len, const char *expected,
                    uword expected_len)
@@ -934,6 +662,16 @@ http_token_is_case (const char *actual, uword actual_len, const char *expected,
   return 1;
 }
 
+/**
+ * Check if there is occurrence of token in another token.
+ *
+ * @param haystack     Pointer to the token being searched.
+ * @param haystack_len Length of the token being searched.
+ * @param needle       The token to search for.
+ * @param needle_len   Length of the token to search for.
+ *
+ * @return @c 1 if in case of success, @c 0 otherwise.
+ */
 always_inline u8
 http_token_contains (const char *haystack, uword haystack_len,
                     const char *needle, uword needle_len)
@@ -1158,6 +896,13 @@ typedef struct
 /* Use high bit of header name length as custom header name bit. */
 #define HTTP_CUSTOM_HEADER_NAME_BIT (1 << 31)
 
+/**
+ * Initialize headers list context.
+ *
+ * @param ctx Headers list context.
+ * @param buf Buffer, which store headers list, provided by app.
+ * @param len Length of headers list buffer.
+ */
 always_inline void
 http_init_headers_ctx (http_headers_ctx_t *ctx, u8 *buf, u32 len)
 {
@@ -1166,6 +911,14 @@ http_init_headers_ctx (http_headers_ctx_t *ctx, u8 *buf, u32 len)
   ctx->buf = buf;
 }
 
+/**
+ * Add header with predefined name to the headers list.
+ *
+ * @param ctx       Headers list context.
+ * @param name      Header name ID (see @ref http_header_name_t).
+ * @param value     Header value pointer.
+ * @param value_len Header value length.
+ */
 always_inline void
 http_add_header (http_headers_ctx_t *ctx, http_header_name_t name,
                 const char *value, uword value_len)
@@ -1182,6 +935,15 @@ http_add_header (http_headers_ctx_t *ctx, http_header_name_t name,
   ctx->tail_offset += sizeof (http_app_header_t) + value_len;
 }
 
+/**
+ * Add header with custom name to the headers list.
+ *
+ * @param ctx       Headers list context.
+ * @param name      Header name pointer.
+ * @param name_len  Header name length.
+ * @param value     Header value pointer.
+ * @param value_len Header value length.
+ */
 always_inline void
 http_add_custom_header (http_headers_ctx_t *ctx, const char *name,
                        uword name_len, const char *value, uword value_len)
@@ -1491,6 +1253,15 @@ http_parse_authority (u8 *authority, u32 authority_len,
   return token_start == end ? 0 : -1;
 }
 
+/**
+ * Format given authority (RFC3986 section 3.2)
+ *
+ * @param authority Authority to format.
+ *
+ * @return New vector with formated authority.
+ *
+ * The caller is always responsible to free the returned vector.
+ */
 always_inline u8 *
 http_serialize_authority (http_uri_authority_t *authority)
 {
diff --git a/src/plugins/http/http1.c b/src/plugins/http/http1.c
new file mode 100644 (file)
index 0000000..44dd099
--- /dev/null
@@ -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"),
+};
diff --git a/src/plugins/http/http_private.h b/src/plugins/http/http_private.h
new file mode 100644 (file)
index 0000000..187d1fe
--- /dev/null
@@ -0,0 +1,672 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright(c) 2025 Cisco Systems, Inc.
+ */
+
+#ifndef SRC_PLUGINS_HTTP_HTTP_PRIVATE_H_
+#define SRC_PLUGINS_HTTP_HTTP_PRIVATE_H_
+
+#include <vppinfra/time_range.h>
+#include <vnet/session/application.h>
+#include <vnet/session/session.h>
+#include <vnet/session/transport.h>
+#include <http/http.h>
+#include <http/http_buffer.h>
+
+#define HTTP_FIFO_THRESH (16 << 10)
+
+typedef u32 http_conn_handle_t;
+
+typedef struct http_conn_id_
+{
+  union
+  {
+    session_handle_t app_session_handle;
+    u32 parent_app_api_ctx;
+  };
+  session_handle_t tc_session_handle;
+  u32 parent_app_wrk_index;
+} http_conn_id_t;
+
+STATIC_ASSERT (sizeof (http_conn_id_t) <= TRANSPORT_CONN_ID_LEN,
+              "ctx id must be less than TRANSPORT_CONN_ID_LEN");
+
+#define foreach_http_conn_state                                               \
+  _ (LISTEN, "LISTEN")                                                        \
+  _ (CONNECTING, "CONNECTING")                                                \
+  _ (ESTABLISHED, "ESTABLISHED")                                              \
+  _ (TRANSPORT_CLOSED, "TRANSPORT-CLOSED")                                    \
+  _ (APP_CLOSED, "APP-CLOSED")                                                \
+  _ (CLOSED, "CLOSED")
+
+typedef enum http_conn_state_
+{
+#define _(s, str) HTTP_CONN_STATE_##s,
+  foreach_http_conn_state
+#undef _
+} http_conn_state_t;
+
+#define foreach_http_req_state                                                \
+  _ (0, IDLE, "idle")                                                         \
+  _ (1, WAIT_APP_METHOD, "wait app method")                                   \
+  _ (2, WAIT_TRANSPORT_REPLY, "wait transport reply")                         \
+  _ (3, TRANSPORT_IO_MORE_DATA, "transport io more data")                     \
+  _ (4, WAIT_TRANSPORT_METHOD, "wait transport method")                       \
+  _ (5, WAIT_APP_REPLY, "wait app reply")                                     \
+  _ (6, APP_IO_MORE_DATA, "app io more data")                                 \
+  _ (7, TUNNEL, "tunnel")                                                     \
+  _ (8, UDP_TUNNEL, "udp tunnel")
+
+typedef enum http_req_state_
+{
+#define _(n, s, str) HTTP_REQ_STATE_##s = n,
+  foreach_http_req_state
+#undef _
+    HTTP_REQ_N_STATES
+} http_req_state_t;
+
+typedef enum http_target_form_
+{
+  HTTP_TARGET_ORIGIN_FORM,
+  HTTP_TARGET_ABSOLUTE_FORM,
+  HTTP_TARGET_AUTHORITY_FORM,
+  HTTP_TARGET_ASTERISK_FORM
+} http_target_form_t;
+
+typedef enum http_version_
+{
+  HTTP_VERSION_1,
+  HTTP_VERSION_2,
+  HTTP_VERSION_3,
+  HTTP_VERSION_NA = 7,
+} http_version_t;
+
+typedef struct http_req_
+{
+  /* in case of multiplexing we have app session for each stream */
+  session_handle_t app_session_handle;
+  u32 as_fifo_offset; /* for peek */
+
+  http_req_state_t state; /* state-machine state */
+
+  http_buffer_t tx_buf; /* message body from app to be sent */
+
+  /*
+   * for parsing of incoming message from transport
+   */
+  u32 rx_buf_offset;   /* current offset during parsing */
+  u32 control_data_len; /* start line + headers + empty line */
+
+  union
+  {
+    u64 to_recv; /* remaining bytes of body to receive from transport */
+    u64 to_skip; /* remaining bytes of capsule to skip */
+  };
+
+  u8 is_tunnel;
+
+  /*
+   * parsed metadata for app
+   */
+  union
+  {
+    http_status_code_t status_code;
+    http_req_method_t method;
+  };
+
+  http_target_form_t target_form;
+  u8 *target;
+  http_url_scheme_t scheme;
+  u32 target_authority_offset;
+  u32 target_authority_len;
+  u32 target_path_offset;
+  u32 target_path_len;
+  u32 target_query_offset;
+  u32 target_query_len;
+
+  u32 headers_offset;
+  u32 headers_len;
+
+  u32 body_offset;
+  u64 body_len;
+
+  http_field_line_t *headers;
+  uword content_len_header_index;
+  uword connection_header_index;
+  uword upgrade_header_index;
+  uword host_header_index;
+
+  http_upgrade_proto_t upgrade_proto;
+} http_req_t;
+
+typedef struct http_tc_
+{
+  union
+  {
+    transport_connection_t connection;
+    http_conn_id_t c_http_conn_id;
+  };
+#define h_tc_session_handle c_http_conn_id.tc_session_handle
+#define h_pa_wrk_index     c_http_conn_id.parent_app_wrk_index
+#define h_pa_session_handle c_http_conn_id.app_session_handle
+#define h_pa_app_api_ctx    c_http_conn_id.parent_app_api_ctx
+#define h_hc_index         connection.c_index
+
+  http_version_t version;
+  http_conn_state_t state;
+  u32 timer_handle;
+  u32 timeout;
+  u8 pending_timer;
+  u8 *app_name;
+  u8 *host;
+  u8 is_server;
+  http_udp_tunnel_mode_t udp_tunnel_mode;
+
+  http_req_t *req_pool; /* multiplexing => request per stream */
+} http_conn_t;
+
+typedef struct http_worker_
+{
+  http_conn_t *conn_pool;
+} http_worker_t;
+
+typedef struct http_main_
+{
+  http_worker_t *wrk;
+  http_conn_t *listener_pool;
+  http_conn_t *ho_conn_pool;
+  u32 app_index;
+
+  u8 **rx_bufs;
+  u8 **tx_bufs;
+  u8 **app_header_lists;
+
+  clib_timebase_t timebase;
+
+  http_status_code_t *sc_by_u16;
+  /*
+   * Runtime config
+   */
+  u8 is_init;
+
+  /*
+   * Config
+   */
+  u64 first_seg_size;
+  u64 add_seg_size;
+  u32 fifo_size;
+} http_main_t;
+
+typedef struct http_engine_vft_
+{
+  void (*app_tx_callback) (http_conn_t *hc, transport_send_params_t *sp);
+  void (*app_rx_evt_callback) (http_conn_t *hc);
+  void (*app_close_callback) (http_conn_t *hc);
+  void (*app_reset_callback) (http_conn_t *hc);
+  void (*transport_rx_callback) (http_conn_t *hc);
+  void (*transport_close_callback) (http_conn_t *hc);
+} http_engine_vft_t;
+
+void http_register_engine (const http_engine_vft_t *vft,
+                          http_version_t version);
+
+/* HTTP state machine result */
+typedef enum http_sm_result_t_
+{
+  HTTP_SM_STOP = 0,
+  HTTP_SM_CONTINUE = 1,
+  HTTP_SM_ERROR = -1,
+} http_sm_result_t;
+
+typedef http_sm_result_t (*http_sm_handler) (http_conn_t *hc, http_req_t *req,
+                                            transport_send_params_t *sp);
+
+#define expect_char(c)                                                        \
+  if (*p++ != c)                                                              \
+    {                                                                         \
+      clib_warning ("unexpected character");                                  \
+      return -1;                                                              \
+    }
+
+#define parse_int(val, mul)                                                   \
+  do                                                                          \
+    {                                                                         \
+      if (!isdigit (*p))                                                      \
+       {                                                                     \
+         clib_warning ("expected digit");                                    \
+         return -1;                                                          \
+       }                                                                     \
+      val += mul * (*p++ - '0');                                              \
+    }                                                                         \
+  while (0)
+
+#define http_field_line_value_token(_fl, _req, _rx_buf)                       \
+  (const char *) ((_rx_buf) + (_req)->headers_offset + (_fl)->value_offset),  \
+    (_fl)->value_len
+
+u8 *format_http_req_state (u8 *s, va_list *va);
+u8 *format_http_conn_state (u8 *s, va_list *args);
+u8 *format_http_time_now (u8 *s, va_list *args);
+
+/**
+ * @brief Find the first occurrence of the string in the vector.
+ *
+ * @param vec The vector to be scanned.
+ * @param offset Search offset in the vector.
+ * @param num Maximum number of characters to be searched if non-zero.
+ * @param str The string to be searched.
+ *
+ * @return @c -1 if the string is not found within the vector; index otherwise.
+ */
+int http_v_find_index (u8 *vec, u32 offset, u32 num, char *str);
+
+/**
+ * Disconnect HTTP connection.
+ *
+ * @param hc HTTP connection to disconnect.
+ */
+void http_disconnect_transport (http_conn_t *hc);
+
+/**
+ * Convert numeric representation of status code to @c http_status_code_t.
+ *
+ * @param status_code Status code within the range of 100 to 599, inclusive.
+ *
+ * @return Registered status code or in case of unrecognized status code as
+ * equivalent to the x00 status code of that class.
+ */
+http_status_code_t http_sc_by_u16 (u16 status_code);
+
+/**
+ * Read header list sent by app.
+ *
+ * @param hc  HTTP connection.
+ * @param msg HTTP msg sent by app.
+ *
+ * @return Pointer to the header list.
+ *
+ * @note For immediate processing, not for buffering.
+ */
+u8 *http_get_app_header_list (http_conn_t *hc, http_msg_t *msg);
+
+/**
+ * Get pre-allocated TX buffer/vector.
+ *
+ * @param hc HTTP connection.
+ *
+ * @return Pointer to the vector.
+ *
+ * @note Vector length is reset to zero, use as temporary storage.
+ */
+u8 *http_get_tx_buf (http_conn_t *hc);
+
+/**
+ * Get pre-allocated RX buffer/vector.
+ *
+ * @param hc HTTP connection.
+ *
+ * @return Pointer to the vector.
+ *
+ * @note Vector length is reset to zero, use as temporary storage.
+ */
+u8 *http_get_rx_buf (http_conn_t *hc);
+
+/**
+ * Read request target path sent by app.
+ *
+ * @param hc  HTTP connection.
+ * @param msg HTTP msg sent by app.
+ *
+ * @return Pointer to the target path.
+ *
+ * @note Valid only with request lifetime.
+ */
+u8 *http_get_app_target (http_req_t *req, http_msg_t *msg);
+
+/**
+ * Initialize per-request HTTP TX buffer.
+ *
+ * @param req HTTP request.
+ * @param msg HTTP msg sent by app.
+ *
+ * @note Use for streaming of body sent by app.
+ */
+void http_req_tx_buffer_init (http_req_t *req, http_msg_t *msg);
+
+/**
+ * Allocate new request within given HTTP connection.
+ *
+ * @param hc  HTTP connection.
+ *
+ * @return Request index in per-connection pool.
+ */
+always_inline u32
+http_alloc_req (http_conn_t *hc)
+{
+  http_req_t *req;
+  pool_get_zero (hc->req_pool, req);
+  req->app_session_handle = SESSION_INVALID_HANDLE;
+  return (req - hc->req_pool);
+}
+
+/**
+ * Get request in per-connection pool.
+ *
+ * @param hc        HTTP connection.
+ * @param req_index Request index.
+ *
+ * @return Pointer to the request data.
+ */
+always_inline http_req_t *
+http_get_req (http_conn_t *hc, u32 req_index)
+{
+  return pool_elt_at_index (hc->req_pool, req_index);
+}
+
+/**
+ * Get request in per-connection pool if valid.
+ *
+ * @param hc        HTTP connection.
+ * @param req_index Request index.
+ *
+ * @return Pointer to the request data or @c 0 if not valid.
+ */
+always_inline http_req_t *
+http_get_req_if_valid (http_conn_t *hc, u32 req_index)
+{
+  if (pool_is_free_index (hc->req_pool, req_index))
+    return 0;
+  return pool_elt_at_index (hc->req_pool, req_index);
+}
+
+/**
+ * Free request in per-connection pool.
+ *
+ * @param hc  HTTP connection.
+ * @param req Pointer to the request.
+ */
+always_inline void
+http_req_free (http_conn_t *hc, http_req_t *req)
+{
+  vec_free (req->headers);
+  vec_free (req->target);
+  http_buffer_free (&req->tx_buf);
+  if (CLIB_DEBUG)
+    memset (req, 0xba, sizeof (*req));
+  pool_put (hc->req_pool, req);
+}
+
+/**
+ * Change state of given HTTP request.
+ *
+ * @param req   HTTP request.
+ * @param state New state.
+ */
+always_inline void
+http_req_state_change (http_req_t *req, http_req_state_t state)
+{
+  HTTP_DBG (1, "changing http req state: %U -> %U", format_http_req_state,
+           req->state, format_http_req_state, state);
+  ASSERT (req->state != HTTP_REQ_STATE_TUNNEL);
+  req->state = state;
+}
+
+/**
+ * Send RX event to the app worker.
+ *
+ * @param req HTTP request.
+ */
+always_inline void
+http_app_worker_rx_notify (http_req_t *req)
+{
+  session_t *as;
+  app_worker_t *app_wrk;
+
+  as = session_get_from_handle (req->app_session_handle);
+  app_wrk = app_worker_get_if_valid (as->app_wrk_index);
+  if (app_wrk)
+    app_worker_rx_notify (app_wrk, as);
+}
+
+/**
+ * Get underlying transport protocol of the HTTP connection.
+ *
+ * @param hc HTTP connection.
+ *
+ * @return Transport protocol, @ref transport_proto_t.
+ */
+always_inline transport_proto_t
+http_get_transport_proto (http_conn_t *hc)
+{
+  return session_get_transport_proto (
+    session_get_from_handle (hc->h_tc_session_handle));
+}
+
+/**
+ * Read HTTP msg sent by app.
+ *
+ * @param req HTTP request.
+ * @param msg HTTP msq will be stored here.
+ */
+always_inline void
+http_get_app_msg (http_req_t *req, http_msg_t *msg)
+{
+  session_t *as;
+  int rv;
+
+  as = session_get_from_handle (req->app_session_handle);
+  rv = svm_fifo_dequeue (as->tx_fifo, sizeof (*msg), (u8 *) msg);
+  ASSERT (rv == sizeof (*msg));
+}
+
+/* Abstraction of app session fifo operations */
+
+always_inline void
+http_io_as_want_deq_ntf (http_req_t *req)
+{
+  session_t *as = session_get_from_handle (req->app_session_handle);
+  svm_fifo_add_want_deq_ntf (as->rx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
+}
+
+always_inline u32
+http_io_as_max_write (http_req_t *req)
+{
+  session_t *as = session_get_from_handle (req->app_session_handle);
+  return svm_fifo_max_enqueue_prod (as->rx_fifo);
+}
+
+always_inline u32
+http_io_as_max_read (http_req_t *req)
+{
+  session_t *as = session_get_from_handle (req->app_session_handle);
+  return svm_fifo_max_dequeue_cons (as->tx_fifo);
+}
+
+always_inline u32
+http_io_as_write_segs (http_req_t *req, const svm_fifo_seg_t segs[],
+                      u32 n_segs)
+{
+  int n_written;
+  session_t *as = session_get_from_handle (req->app_session_handle);
+  n_written = svm_fifo_enqueue_segments (as->rx_fifo, segs, n_segs, 0);
+  ASSERT (n_written > 0);
+  return (u32) n_written;
+}
+
+always_inline u32
+http_io_as_read (http_req_t *req, u8 *buf, u32 len, u8 peek)
+{
+  int n_read;
+  session_t *as = session_get_from_handle (req->app_session_handle);
+
+  if (peek)
+    {
+      n_read = svm_fifo_peek (as->tx_fifo, req->as_fifo_offset, len, buf);
+      ASSERT (n_read > 0);
+      req->as_fifo_offset += len;
+      return (u32) n_read;
+    }
+
+  n_read = svm_fifo_dequeue (as->tx_fifo, len, buf);
+  ASSERT (n_read == len);
+  return (u32) n_read;
+}
+
+always_inline void
+http_io_as_read_segs (http_req_t *req, svm_fifo_seg_t *segs, u32 *n_segs,
+                     u32 max_bytes)
+{
+  int n_read;
+  session_t *as = session_get_from_handle (req->app_session_handle);
+  n_read = svm_fifo_segments (as->tx_fifo, 0, segs, n_segs, max_bytes);
+  ASSERT (n_read > 0);
+}
+
+always_inline void
+http_io_as_drain (http_req_t *req, u32 len)
+{
+  session_t *as = session_get_from_handle (req->app_session_handle);
+  svm_fifo_dequeue_drop (as->tx_fifo, len);
+  req->as_fifo_offset = 0;
+}
+
+always_inline void
+http_io_as_drain_all (http_req_t *req)
+{
+  session_t *as = session_get_from_handle (req->app_session_handle);
+  svm_fifo_dequeue_drop_all (as->tx_fifo);
+  req->as_fifo_offset = 0;
+}
+
+/* Abstraction of transport session fifo operations */
+
+always_inline u32
+http_io_ts_max_read (http_conn_t *hc)
+{
+  session_t *ts = session_get_from_handle (hc->h_tc_session_handle);
+  return svm_fifo_max_dequeue_cons (ts->rx_fifo);
+}
+
+always_inline u32
+http_io_ts_max_write (http_conn_t *hc, transport_send_params_t *sp)
+{
+  session_t *ts = session_get_from_handle (hc->h_tc_session_handle);
+  return clib_min (svm_fifo_max_enqueue_prod (ts->tx_fifo),
+                  sp->max_burst_size);
+}
+
+always_inline u32
+http_io_ts_read (http_conn_t *hc, u8 *buf, u32 len, u8 peek)
+{
+  int n_read;
+  session_t *ts = session_get_from_handle (hc->h_tc_session_handle);
+
+  if (peek)
+    {
+      n_read = svm_fifo_peek (ts->rx_fifo, 0, len, buf);
+      ASSERT (n_read > 0);
+      return (u32) n_read;
+    }
+
+  n_read = svm_fifo_dequeue (ts->rx_fifo, len, buf);
+  ASSERT (n_read == len);
+  return (u32) n_read;
+}
+
+always_inline void
+http_io_ts_read_segs (http_conn_t *hc, svm_fifo_seg_t *segs, u32 *n_segs,
+                     u32 max_bytes)
+{
+  int n_read;
+  session_t *ts = session_get_from_handle (hc->h_tc_session_handle);
+  n_read = svm_fifo_segments (ts->rx_fifo, 0, segs, n_segs, max_bytes);
+  ASSERT (n_read > 0);
+}
+
+always_inline void
+http_io_ts_drain (http_conn_t *hc, u32 len)
+{
+  session_t *ts = session_get_from_handle (hc->h_tc_session_handle);
+  svm_fifo_dequeue_drop (ts->rx_fifo, len);
+}
+
+always_inline void
+http_io_ts_drain_all (http_conn_t *hc)
+{
+  session_t *ts = session_get_from_handle (hc->h_tc_session_handle);
+  svm_fifo_dequeue_drop_all (ts->rx_fifo);
+}
+
+always_inline void
+http_io_ts_after_read (http_conn_t *hc, u8 clear_evt)
+{
+  session_t *ts = session_get_from_handle (hc->h_tc_session_handle);
+  if (clear_evt)
+    {
+      if (svm_fifo_is_empty_cons (ts->rx_fifo))
+       svm_fifo_unset_event (ts->rx_fifo);
+    }
+  else
+    {
+      if (svm_fifo_max_dequeue_cons (ts->rx_fifo))
+       session_program_rx_io_evt (hc->h_tc_session_handle);
+    }
+}
+
+always_inline void
+http_io_ts_write (http_conn_t *hc, u8 *data, u32 len,
+                 transport_send_params_t *sp)
+{
+  int n_written;
+  session_t *ts = session_get_from_handle (hc->h_tc_session_handle);
+
+  n_written = svm_fifo_enqueue (ts->tx_fifo, len, data);
+  ASSERT (n_written == len);
+  if (sp)
+    {
+      ASSERT (sp->max_burst_size >= len);
+      sp->bytes_dequeued += len;
+      sp->max_burst_size -= len;
+    }
+}
+
+always_inline u32
+http_io_ts_write_segs (http_conn_t *hc, const svm_fifo_seg_t segs[],
+                      u32 n_segs, transport_send_params_t *sp)
+{
+  int n_written;
+  session_t *ts = session_get_from_handle (hc->h_tc_session_handle);
+  n_written = svm_fifo_enqueue_segments (ts->tx_fifo, segs, n_segs, 0);
+  ASSERT (n_written > 0);
+  sp->bytes_dequeued += n_written;
+  sp->max_burst_size -= n_written;
+  return (u32) n_written;
+}
+
+always_inline void
+http_io_ts_after_write (http_conn_t *hc, transport_send_params_t *sp, u8 flush,
+                       u8 written)
+{
+  session_t *ts = session_get_from_handle (hc->h_tc_session_handle);
+
+  if (!flush)
+    {
+      if (written && svm_fifo_set_event (ts->tx_fifo))
+       session_program_tx_io_evt (ts->handle, SESSION_IO_EVT_TX);
+
+      if (sp && (svm_fifo_max_enqueue (ts->tx_fifo) < HTTP_FIFO_THRESH))
+       {
+         /* Deschedule http session and wait for deq notification if
+          * underlying ts tx fifo almost full */
+         svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
+         transport_connection_deschedule (&hc->connection);
+         sp->flags |= TRANSPORT_SND_F_DESCHED;
+       }
+    }
+  else
+    {
+      if (written && svm_fifo_set_event (ts->tx_fifo))
+       session_program_tx_io_evt (ts->handle, SESSION_IO_EVT_TX_FLUSH);
+    }
+}
+
+#endif /* SRC_PLUGINS_HTTP_HTTP_PRIVATE_H_ */
index 43d20d0..3bd12f7 100644 (file)
@@ -16,7 +16,7 @@
 #ifndef SRC_PLUGINS_HTTP_HTTP_TIMER_H_
 #define SRC_PLUGINS_HTTP_HTTP_TIMER_H_
 
-#include <http/http.h>
+#include <http/http_private.h>
 #include <vppinfra/tw_timer_2t_1w_2048sl.h>
 
 #define HTTP_CONN_TIMEOUT        60
@@ -45,7 +45,8 @@ http_conn_timer_start (http_conn_t *hc)
   u32 hs_handle;
 
   ASSERT (hc->timer_handle == HTTP_TIMER_HANDLE_INVALID);
-  hs_handle = hc->c_thread_index << 24 | hc->c_c_index;
+  ASSERT (hc->h_hc_index <= 0x00FFFFFF);
+  hs_handle = hc->c_thread_index << 24 | hc->h_hc_index;
 
   clib_spinlock_lock (&twc->tw_lock);
   hc->timer_handle =
@@ -79,7 +80,8 @@ http_conn_timer_update (http_conn_t *hc)
     tw_timer_update_2t_1w_2048sl (&twc->tw, hc->timer_handle, hc->timeout);
   else
     {
-      hs_handle = hc->c_thread_index << 24 | hc->c_c_index;
+      ASSERT (hc->h_hc_index <= 0x00FFFFFF);
+      hs_handle = hc->c_thread_index << 24 | hc->h_hc_index;
       hc->timer_handle =
        tw_timer_start_2t_1w_2048sl (&twc->tw, hs_handle, 0, hc->timeout);
     }