#ifndef _HAPROXY_MUX_QUIC_H
#define _HAPROXY_MUX_QUIC_H

#ifdef USE_QUIC
#ifndef USE_OPENSSL
#error "Must define USE_OPENSSL"
#endif

#include <haproxy/api.h>
#include <haproxy/connection.h>
#include <haproxy/list.h>
#include <haproxy/mux_quic-t.h>
#include <haproxy/quic_tune.h>
#include <haproxy/stconn.h>

#include <haproxy/h3.h>
#include <haproxy/hq_interop.h>

#define qcc_report_glitch(qcc, inc, ...) ({		\
		COUNT_GLITCH(__VA_ARGS__);		\
		_qcc_report_glitch(qcc, inc); 		\
	})

void qcc_set_error(struct qcc *qcc, int err, int app, int tevt);
void qcc_report_term_evt(struct qcc *qcc, enum muxc_term_event_type type);
int _qcc_report_glitch(struct qcc *qcc, int inc);
int qcc_fctl_avail_streams(const struct qcc *qcc, int bidi);
struct qcs *qcc_init_stream_local(struct qcc *qcc, int bidi);
void qcs_send_metadata(struct qcs *qcs);
int qcs_attach_sc(struct qcs *qcs, struct buffer *buf, char fin);
int qcs_is_close_local(struct qcs *qcs);
int qcs_is_close_remote(struct qcs *qcs);

int qcs_subscribe(struct qcs *qcs, int event_type, struct wait_event *es);
void qcs_notify_recv(struct qcs *qcs);
void qcs_notify_send(struct qcs *qcs);
void qcc_notify_buf(struct qcc *qcc, uint64_t free_size);

struct buffer *qcc_get_stream_rxbuf(struct qcs *qcs);
struct buffer *qcc_get_stream_txbuf(struct qcs *qcs, int *err, int small);
struct buffer *qcc_realloc_stream_txbuf(struct qcs *qcs);
int qcc_realign_stream_txbuf(const struct qcs *qcs, struct buffer *out);
int qcc_release_stream_txbuf(struct qcs *qcs);
int qcc_stream_can_send(const struct qcs *qcs);
void qcc_reset_stream(struct qcs *qcs, int err, int term_evt);
void qcc_send_stream(struct qcs *qcs, int urg, int count);
void qcc_abort_stream_read(struct qcs *qcs, int err);
void qcc_update_shut_id(struct qcc *qcc, uint64_t val);
int qcc_recv(struct qcc *qcc, uint64_t id, uint64_t len, uint64_t offset,
             char fin, char *data);
int qcc_recv_max_data(struct qcc *qcc, uint64_t max);
int qcc_recv_max_stream_data(struct qcc *qcc, uint64_t id, uint64_t max);
int qcc_recv_max_streams(struct qcc *qcc, uint64_t max, int bidi);
int qcc_recv_reset_stream(struct qcc *qcc, uint64_t id, uint64_t err, uint64_t final_size);
int qcc_recv_stop_sending(struct qcc *qcc, uint64_t id, uint64_t err);

static inline int qcm_stream_rx_bufsz(void)
{
	return global.tune.bufsize - NCB_RESERVED_SZ;
}

#define QCS_ID_TYPE_MASK         0x3
/* The less significant bit of a stream ID is set for a server initiated stream */
#define QCS_ID_SRV_INTIATOR_BIT  0x1
/* This bit is set for unidirectional streams */
#define QCS_ID_DIR_BIT           0x2

/* Maximum bidirectional stream ID that a client can open. */
#define QCS_ID_MAX_STRM_CL_BIDI (QUIC_VARINT_8_BYTE_MAX - 3)

static inline enum qcs_type qcs_id_type(uint64_t id)
{
	return id & QCS_ID_TYPE_MASK;
}

/* Return true if stream has been opened locally. */
static inline int quic_stream_is_local(struct qcc *qcc, uint64_t id)
{
	return conn_is_back(qcc->conn) == !(id & QCS_ID_SRV_INTIATOR_BIT);
}

/* Return true if stream is opened by peer. */
static inline int quic_stream_is_remote(struct qcc *qcc, uint64_t id)
{
	return !quic_stream_is_local(qcc, id);
}

static inline char *qcs_st_to_str(enum qcs_state st)
{
	switch (st) {
	case QC_SS_IDLE: return "IDL";
	case QC_SS_OPEN: return "OPN";
	case QC_SS_HLOC: return "HCL";
	case QC_SS_HREM: return "HCR";
	case QC_SS_CLO:  return "CLO";
	default:         return "???";
	}
}

int qcc_install_app_ops(struct qcc *qcc);

/* Flags <qcs> as a request stream. The connection will be considered as active
 * until all request streams are closed or on inactivity timeout. On the
 * frontend side, http-request timeout will be applied on the stream to ensure
 * headers are received in time.
 *
 * This function should be called by the application protocol layer on request
 * streams initialization.
 */
static inline void qcs_wait_http_req(struct qcs *qcs)
{
	struct qcc *qcc = qcs->qcc;

	/* For frontend connections, register the stream in QCC opening_list.
	 * This is necessary for http-request timeout.
	 */
	if (!conn_is_back(qcc->conn)) {
		/* A stream cannot be registered several times. */
		BUG_ON_HOT(tick_isset(qcs->start));
		qcs->start = now_ms;

		/* qcc.opening_list size is limited by flow-control so no
		 * custom restriction is needed here.
		 */
		LIST_APPEND(&qcc->opening_list, &qcs->el_opening);
	}

	/* Ensure flag is only set once per stream to avoid nb_hreq counter wrapping. */
	BUG_ON_HOT(qcs->flags & QC_SF_HREQ_RECV);
	qcs->flags |= QC_SF_HREQ_RECV;
	++qcc->nb_hreq;

	/* On BE side avail_streams cb should prevent opening of too many concurrent streams. */
	BUG_ON(conn_is_back(qcc->conn) && qcc->nb_hreq > quic_tune.be.stream_max_concurrent);
}

void qcc_show_quic(struct qcc *qcc);

void qcc_wakeup(struct qcc *qcc);

static inline const struct qcc_app_ops *quic_alpn_to_app_ops(const char *alpn, int alpn_len)
{
	if (alpn_len >= 2 && memcmp(alpn, "h3", 2) == 0)
		return &h3_ops;
	else if (alpn_len >= 10 && memcmp(alpn, "hq-interop", 10) == 0)
		return &hq_interop_ops;

	return NULL;
}

#endif /* USE_QUIC */

#endif /* _HAPROXY_MUX_QUIC_H */
