/* 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_ */