/*
 * libwebsockets - small server side websockets and web server implementation
 *
 * Copyright (C) 2010 - 2026 Andy Green <andy@warmcat.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

#include "private-lib-core.h"
#include "private-lib-tls.h"

#if defined(LWS_ROLE_QUIC) && defined(LWS_WITH_TLS) && defined(LWS_WITH_GNUTLS)

struct gnutls_quic_bio {
	const uint8_t *in;
	size_t in_len;
	size_t in_pos;

	uint8_t *out;
	size_t out_max;
	size_t out_len;
	int target_level;

	uint8_t *other_out[4];
	size_t other_out_len[4];

	gnutls_session_t session;
};

static ssize_t
gnutls_quic_bio_push(gnutls_transport_ptr_t ptr, const void *buf, size_t len)
{
	/* For QUIC, we drop the TLS Record Layer output generated by GnuTLS. 
	 * We only care about the raw handshake data intercepted by gnutls_quic_read_func. */
	return (ssize_t)len;
}

static ssize_t
gnutls_quic_bio_pull(gnutls_transport_ptr_t ptr, void *buf, size_t len)
{
	struct gnutls_quic_bio *b = (struct gnutls_quic_bio *)ptr;
	/* We don't pull from the Record Layer. QUIC data is pushed via gnutls_handshake_write. */
	gnutls_transport_set_errno(b->session, EAGAIN);
	return -1;
}

static int
gnutls_quic_secret_func(gnutls_session_t session,
			gnutls_record_encryption_level_t level,
			const void *secret_read, const void *secret_write,
			size_t secret_size)
{
	struct lws *wsi = (struct lws *)gnutls_session_get_ptr(session);
	int is_client;

	if (!wsi || !wsi->tls.quic_secret_cb || secret_size > 48)
		return 0;

	/*
	 * RFC 9001: report the negotiated AEAD so the QUIC role selects the
	 * matching packet-protection and header-protection ciphers instead of
	 * guessing from the secret length (AES-128-GCM and ChaCha20-Poly1305
	 * both yield a 32-byte SHA-256 secret).
	 */
	switch (gnutls_cipher_get(session)) {
	case GNUTLS_CIPHER_AES_128_GCM:
		wsi->tls.quic_aead = LWS_TLS_QUIC_AEAD_AES_128_GCM;
		break;
	case GNUTLS_CIPHER_AES_256_GCM:
		wsi->tls.quic_aead = LWS_TLS_QUIC_AEAD_AES_256_GCM;
		break;
	case GNUTLS_CIPHER_CHACHA20_POLY1305:
		wsi->tls.quic_aead = LWS_TLS_QUIC_AEAD_CHACHA20_POLY1305;
		break;
	default:
		/* leave any previously-reported suite in force */
		break;
	}

	if (wsi->a.vhost)
		is_client = lwsi_role_client(wsi) ? 1 : 0;
	else
		is_client = (wsi->tls.quic_tp_send_len == 3);

	if (secret_write) {
		enum lws_tls_quic_secret_type qtype;
		switch (level) {
		case GNUTLS_ENCRYPTION_LEVEL_EARLY:
			qtype = LWS_TLS_QUIC_SECRET_CLIENT_EARLY;
			break;
		case GNUTLS_ENCRYPTION_LEVEL_HANDSHAKE:
			qtype = is_client ? LWS_TLS_QUIC_SECRET_CLIENT_HANDSHAKE : LWS_TLS_QUIC_SECRET_SERVER_HANDSHAKE;
			break;
		case GNUTLS_ENCRYPTION_LEVEL_APPLICATION:
			qtype = is_client ? LWS_TLS_QUIC_SECRET_CLIENT_APPLICATION : LWS_TLS_QUIC_SECRET_SERVER_APPLICATION;
			break;
		default:
			return 0;
		}
		wsi->tls.quic_secret_cb(wsi, qtype, secret_write, secret_size);
	}

	if (secret_read) {
		enum lws_tls_quic_secret_type qtype;
		switch (level) {
		case GNUTLS_ENCRYPTION_LEVEL_EARLY:
			if (!is_client)
				qtype = LWS_TLS_QUIC_SECRET_CLIENT_EARLY;
			else
				return 0;
			break;
		case GNUTLS_ENCRYPTION_LEVEL_HANDSHAKE:
			qtype = !is_client ? LWS_TLS_QUIC_SECRET_CLIENT_HANDSHAKE : LWS_TLS_QUIC_SECRET_SERVER_HANDSHAKE;
			break;
		case GNUTLS_ENCRYPTION_LEVEL_APPLICATION:
			qtype = !is_client ? LWS_TLS_QUIC_SECRET_CLIENT_APPLICATION : LWS_TLS_QUIC_SECRET_SERVER_APPLICATION;
			break;
		default:
			return 0;
		}
		wsi->tls.quic_secret_cb(wsi, qtype, secret_read, secret_size);
	}

	return 0;
}



static int
gnutls_quic_read_func(gnutls_session_t session,
		      gnutls_record_encryption_level_t level,
		      gnutls_handshake_description_t htype,
		      const void *data, size_t data_size)
{
	struct lws *wsi = (struct lws *)gnutls_session_get_ptr(session);
	struct gnutls_quic_bio *b;
	int qlevel;

	lwsl_debug("GNUTLS QUIC READ FUNC: level %d, htype %d, len %d\n",
		(int)level, (int)htype, (int)data_size);

	if (!wsi || !wsi->tls.client_bio)
		return 0;

	b = (struct gnutls_quic_bio *)wsi->tls.client_bio;

	switch (level) {
	case GNUTLS_ENCRYPTION_LEVEL_INITIAL:
		qlevel = 0; /* LWS_QUIC_LEVEL_INITIAL */
		break;
	case GNUTLS_ENCRYPTION_LEVEL_EARLY:
		qlevel = 1; /* LWS_QUIC_LEVEL_EARLY */
		break;
	case GNUTLS_ENCRYPTION_LEVEL_HANDSHAKE:
		qlevel = 2; /* LWS_QUIC_LEVEL_HANDSHAKE */
		break;
	case GNUTLS_ENCRYPTION_LEVEL_APPLICATION:
		qlevel = 3; /* LWS_QUIC_LEVEL_APP */
		break;
	default:
		return 0;
	}

	if (data_size == 5 && ((const uint8_t *)data)[0] == 24) {
		lwsl_wsi_info(wsi, "QUIC TLS TX: dropping 5-byte KeyUpdate message");
		return 0;
	}

	if (b->out && qlevel == b->target_level) {
		if (b->out_len + data_size > b->out_max)
			return GNUTLS_E_MEMORY_ERROR;
		memcpy(b->out + b->out_len, data, data_size);
		b->out_len += data_size;
	} else {
		uint8_t *p = lws_realloc(b->other_out[qlevel], b->other_out_len[qlevel] + data_size, "quic other out");
		if (!p)
			return GNUTLS_E_MEMORY_ERROR;
		b->other_out[qlevel] = p;
		memcpy(p + b->other_out_len[qlevel], data, data_size);
		b->other_out_len[qlevel] += data_size;
	}

	return 0;
}

static int
gnutls_quic_ext_recv_func(gnutls_session_t session, const unsigned char *data, size_t len)
{
	struct lws *wsi = (struct lws *)gnutls_session_get_ptr(session);
	uint8_t *p;

	if (!wsi)
		return 0;

	if (wsi->tls.quic_tp_recv)
		lws_free_set_NULL(wsi->tls.quic_tp_recv);

	p = lws_malloc(len, "quic tp recv");
	if (!p)
		return GNUTLS_E_MEMORY_ERROR;

	memcpy(p, data, len);
	wsi->tls.quic_tp_recv = p;
	wsi->tls.quic_tp_recv_len = len;

	return 0;
}

static int
gnutls_quic_ext_send_func(gnutls_session_t session, gnutls_buffer_t extdata)
{
	struct lws *wsi = (struct lws *)gnutls_session_get_ptr(session);

	if (!wsi) {
		lwsl_err("GNUTLS EXT SEND: wsi is NULL!\n");
		return 0;
	}

	if (!wsi->tls.quic_tp_send || !wsi->tls.quic_tp_send_len) {
		lwsl_err("GNUTLS EXT SEND: no tp_send (%p, len %d)!\n", wsi->tls.quic_tp_send, (int)wsi->tls.quic_tp_send_len);
		return 0;
	}

	lwsl_info("GNUTLS EXT SEND: appending %d bytes of TP!\n", (int)wsi->tls.quic_tp_send_len);

	if (gnutls_buffer_append_data(extdata, wsi->tls.quic_tp_send, wsi->tls.quic_tp_send_len) < 0)
		return GNUTLS_E_MEMORY_ERROR;

	return (int)wsi->tls.quic_tp_send_len;
}

void
gnutls_quic_bio_free(struct lws *wsi)
{
	if (!wsi)
		return;

	if (wsi->tls.client_bio) {
		struct gnutls_quic_bio *b = (struct gnutls_quic_bio *)wsi->tls.client_bio;
		int i;
		for (i = 0; i < 4; i++) {
			if (b->other_out[i])
				lws_free(b->other_out[i]);
		}
		lws_free(wsi->tls.client_bio);
		wsi->tls.client_bio = NULL;
	}

	if (wsi->tls.quic_tp_recv) {
		lws_free((void *)wsi->tls.quic_tp_recv);
		wsi->tls.quic_tp_recv = NULL;
	}

	if (wsi->tls.quic_tp_send) {
		lws_free((void *)wsi->tls.quic_tp_send);
		wsi->tls.quic_tp_send = NULL;
	}
}

int
lws_tls_quic_vhost_init(lws_tls_ctx *ctx)
{
	return 0; /* Nothing to do context-wide for GnuTLS currently. */
}

int
lws_tls_quic_init(struct lws *wsi, lws_tls_quic_secret_cb cb)
{
	struct gnutls_quic_bio *b;
	gnutls_session_t session;

	if (!wsi->tls.ssl) {
		lwsl_err("lws_tls_quic_init: wsi->tls.ssl is NULL\n");
		return -1;
	}

	int ret;
	session = (gnutls_session_t)wsi->tls.ssl;
	if (!session) {
		lwsl_err("lws_tls_quic_init: session is NULL\n");
		return -1;
	}

	const char *user_ciphers = NULL;
	char priority[256];

#if defined(LWS_WITH_CLIENT)
        if (wsi->quic.qn && !wsi->quic.qn->is_server) {
                user_ciphers = wsi->a.vhost ? wsi->a.vhost->tls.cfg_tls_client_cipher_list : NULL;
        } else
#endif
	{
		user_ciphers = wsi->a.vhost ? wsi->a.vhost->tls.cfg_ssl_cipher_list : NULL;
	}

	if (user_ciphers) {
		lws_snprintf(priority, sizeof(priority), "%s:%%DISABLE_TLS13_COMPAT_MODE", user_ciphers);
	} else {
		lws_snprintf(priority, sizeof(priority), "NORMAL:-VERS-ALL:+VERS-TLS1.3:%%DISABLE_TLS13_COMPAT_MODE");
	}


	/* Enforce QUIC requirements: TLS 1.3 only, NO compatibility mode (empty legacy_session_id) */
	ret = gnutls_priority_set_direct(session, priority, NULL);
	if (ret < 0)
		lwsl_notice("gnutls_priority_set_direct failed: %s\n", gnutls_strerror(ret));

	if (!wsi->tls.quic_tp_send) {
		/* Construct dynamically to include loc_cid */
		struct lws_quic_netconn *qn = wsi->quic.qn;
		uint8_t dynamic_tp[128];
		uint8_t *p = dynamic_tp;

		/* initial_max_stream_data_bidi_local (0x05), len 4, val 65535 */
		*p++ = 0x05; *p++ = 0x04; *p++ = 0x80; *p++ = 0x00; *p++ = 0xff; *p++ = 0xff;
		/* initial_max_stream_data_bidi_remote (0x06), len 4, val 65535 */
		*p++ = 0x06; *p++ = 0x04; *p++ = 0x80; *p++ = 0x00; *p++ = 0xff; *p++ = 0xff;
		/* initial_max_stream_data_uni (0x07), len 4, val 65535 */
		*p++ = 0x07; *p++ = 0x04; *p++ = 0x80; *p++ = 0x00; *p++ = 0xff; *p++ = 0xff;
		/* initial_max_data (0x04), len 4, val 65535 */
		*p++ = 0x04; *p++ = 0x04; *p++ = 0x80; *p++ = 0x00; *p++ = 0xff; *p++ = 0xff;
		/* initial_max_streams_bidi (0x08), len 2, val 100 (2-byte varint: 0x40 0x64) */
		*p++ = 0x08; *p++ = 0x02; *p++ = 0x40; *p++ = 0x64;
		/* initial_max_streams_uni (0x09), len 2, val 100 (2-byte varint: 0x40 0x64) */
		*p++ = 0x09; *p++ = 0x02; *p++ = 0x40; *p++ = 0x64;
		/* max_idle_timeout (0x01), len 4, val 30000 */
		*p++ = 0x01; *p++ = 0x04; *p++ = 0x80; *p++ = 0x00; *p++ = 0x75; *p++ = 0x30;
		/* max_datagram_frame_size (0x20), len 4, val 65535 */
		*p++ = 0x20; *p++ = 0x04; *p++ = 0x80; *p++ = 0x00; *p++ = 0xff; *p++ = 0xff;
		/* initial_source_connection_id (0x0f) */
		if (qn && qn->loc_cid.len > 0) {
			if ((size_t)(p - dynamic_tp) + 2 + qn->loc_cid.len > sizeof(dynamic_tp)) {
				lwsl_err("loc_cid too large for TP buffer\n");
				return -1;
			}
			*p++ = 0x0f;
			*p++ = qn->loc_cid.len;
			memcpy(p, qn->loc_cid.id, qn->loc_cid.len);
			p += qn->loc_cid.len;
		}

		wsi->tls.quic_tp_send = lws_malloc((size_t)(p - dynamic_tp), "quic tp");
		if (wsi->tls.quic_tp_send) {
			memcpy((void *)wsi->tls.quic_tp_send, dynamic_tp, (size_t)(p - dynamic_tp));
			wsi->tls.quic_tp_send_len = (size_t)(p - dynamic_tp);
		}
	}

	if (wsi->alpn[0]) {
		gnutls_datum_t alpn[4];
		unsigned int i = 0;
		char *p = wsi->alpn;
		char *end = p + strlen(p);
		
		while (p < end && i < 4) {
			char *comma = strchr(p, ',');
			alpn[i].data = (uint8_t *)p;
			if (comma) {
				alpn[i].size = (unsigned int)lws_ptr_diff_size_t(comma, p);
				p = comma + 1;
			} else {
				alpn[i].size = (unsigned int)lws_ptr_diff_size_t(end, p);
				p = end;
			}
			/* Replace comma with NUL for cleaner logging, though gnutls only uses size */
			if (comma) *comma = '\0';
			i++;
		}
		gnutls_alpn_set_protocols(session, alpn, i, GNUTLS_ALPN_MANDATORY);
	} else if (wsi->a.vhost && wsi->a.vhost->tls.alpn_ctx.len) {
		gnutls_datum_t alpn[4];
		unsigned int i = 0, p = 0;
		while (p < wsi->a.vhost->tls.alpn_ctx.len && i < 4) {
			alpn[i].data = &wsi->a.vhost->tls.alpn_ctx.data[p + 1];
			alpn[i].size = wsi->a.vhost->tls.alpn_ctx.data[p];
			p += alpn[i].size + 1;
			i++;
		}
		gnutls_alpn_set_protocols(session, alpn, i, GNUTLS_ALPN_MANDATORY);
	}

	wsi->tls.quic_secret_cb = cb;

	b = lws_zalloc(sizeof(*b), "quic bio");
	if (!b) {
		lwsl_err("lws_tls_quic_init: failed to allocate bio\n");
		return -1;
	}

	b->session = session;

	wsi->tls.client_bio = (lws_tls_bio *)b;

	gnutls_transport_set_ptr(session, b);
	gnutls_transport_set_push_function(session, gnutls_quic_bio_push);
	gnutls_transport_set_pull_function(session, gnutls_quic_bio_pull);

	gnutls_session_set_ptr(session, wsi);
	gnutls_handshake_set_secret_function(session, gnutls_quic_secret_func);



#if defined(LWS_WITH_SERVER) && defined(LWS_WITH_TLS_SESSIONS)
	if (!lwsi_role_client(wsi)) {
#if GNUTLS_VERSION_NUMBER >= 0x030603
		if (wsi->a.vhost && wsi->a.vhost->tls.ssl_ctx) {
			if (wsi->a.vhost->tls.ssl_ctx->ticket_key_valid) {
				// lwsl_notice("%s: enabling server session tickets\n", __func__);
				gnutls_session_ticket_enable_server(session, &wsi->a.vhost->tls.ssl_ctx->ticket_key);
			} else
				lwsl_notice("%s: ticket_key_valid is 0\n", __func__);
		}
#endif
	}
#endif
	gnutls_handshake_set_read_function(session, gnutls_quic_read_func);

	gnutls_session_ext_register(session, "quic_transport_parameters",
				    57, GNUTLS_EXT_TLS,
				    gnutls_quic_ext_recv_func,
				    gnutls_quic_ext_send_func,
				    NULL, NULL, NULL,
				    GNUTLS_EXT_FLAG_CLIENT_HELLO | GNUTLS_EXT_FLAG_EE);

#if defined(LWS_WITH_CLIENT) && defined(LWS_WITH_TLS_SESSIONS)
	if (lwsi_role_client(wsi))
		lws_tls_reuse_session(wsi);
#endif

	return 0;
}

int
lws_tls_quic_advance_handshake(struct lws *wsi, int level,
			       const uint8_t *in, size_t in_len,
			       uint8_t *out, size_t *out_len)
{
	gnutls_session_t session = (gnutls_session_t)wsi->tls.ssl;
	struct gnutls_quic_bio *b = (struct gnutls_quic_bio *)wsi->tls.client_bio;
	int n, l;

	if (!b || !session)
		return -1;

	b->out = out;
	b->out_max = out ? *out_len : 0;
	if (out_len)
		*out_len = 0;
	b->out_len = 0;
	b->target_level = level;

	if (in && in_len > 0) {
		gnutls_record_encryption_level_t glevel;
		switch (level) {
		case 0: /* LWS_QUIC_LEVEL_INITIAL */
			glevel = GNUTLS_ENCRYPTION_LEVEL_INITIAL;
			break;
		case 1: /* LWS_QUIC_LEVEL_EARLY */
			glevel = GNUTLS_ENCRYPTION_LEVEL_EARLY;
			break;
		case 2: /* LWS_QUIC_LEVEL_HANDSHAKE */
			glevel = GNUTLS_ENCRYPTION_LEVEL_HANDSHAKE;
			break;
		case 3: /* LWS_QUIC_LEVEL_APP */
			glevel = GNUTLS_ENCRYPTION_LEVEL_APPLICATION;
			break;
		default:
			if (out_len)
				*out_len = 0;
			return -1;
		}
		int hw = gnutls_handshake_write(session, glevel, in, in_len);
		if (hw < 0) {
			lwsl_err("gnutls_handshake_write failed: %d\n", hw);
			return hw;
		}
	}

#if defined(LWS_WITH_CLIENT) && defined(LWS_WITH_TLS_SESSIONS)
	/* 
	 * Try to cache any newly available session data (like NewSessionTicket)
	 * BEFORE calling gnutls_handshake, because gnutls_handshake sets
	 * handshake_in_progress=1 and causes gnutls_session_get_data2 to fail with -408.
	 */
	if (lwsi_role_client(wsi) && wsi->quic.qn && wsi->quic.qn->handshake_done)
		lws_tls_session_new_gnutls(wsi);
#endif

	n = gnutls_handshake(session);
	// lwsl_notice("gnutls_handshake returned %d\n", n);

	struct lws *active_wsi = (struct lws *)gnutls_session_get_ptr(session);
	if (!active_wsi)
		active_wsi = wsi;

#if defined(LWS_WITH_SERVER) && defined(LWS_WITH_TLS_SESSIONS)
	if (n == GNUTLS_E_SUCCESS && !lwsi_role_client(active_wsi) && active_wsi->quic.qn && !active_wsi->quic.qn->handshake_done) {
#if GNUTLS_VERSION_NUMBER >= 0x030603
		int r = gnutls_session_ticket_send(session, 1, 0);
		(void)r;
		// lwsl_notice("%s: gnutls_session_ticket_send returned %d\n", __func__, r);
#endif
	}
#endif

	if (out_len)
		*out_len = b->out_len;

	for (l = 0; l < 4; l++) {
		if (b->other_out_len[l]) {
			if (active_wsi->quic.qn) {
				lws_tls_quic_tx_crypto_cb(active_wsi, l, b->other_out[l], b->other_out_len[l]);
			}
			lws_free_set_NULL(b->other_out[l]);
			b->other_out_len[l] = 0;
		}
	}

	if (n == GNUTLS_E_SUCCESS) {
#if defined(LWS_WITH_CLIENT) && defined(LWS_WITH_TLS_SESSIONS)
		if (lwsi_role_client(active_wsi))
			lws_tls_session_new_gnutls(active_wsi);
#endif
		return 0;
	}

	if (n == GNUTLS_E_AGAIN || n == GNUTLS_E_INTERRUPTED) {
		return 1;
	}

	lwsl_wsi_warn(active_wsi, "gnutls_handshake failed: %d\n", n);
	return n < 0 ? n : -1;
}

int
lws_tls_quic_set_transport_parameters(struct lws *wsi, const uint8_t *tp, size_t tp_len)
{
	uint8_t *p;

	if (wsi->tls.quic_tp_send)
		lws_free((void *)wsi->tls.quic_tp_send);

	p = lws_malloc(tp_len, "quic tp send");
	if (!p)
		return -1;

	memcpy(p, tp, tp_len);
	wsi->tls.quic_tp_send = p;
	wsi->tls.quic_tp_send_len = tp_len;
	return 0;
}

int
lws_tls_quic_get_transport_parameters(struct lws *wsi, const uint8_t **tp, size_t *tp_len)
{
	if (!wsi->tls.quic_tp_recv) {
		if (tp_len)
			*tp_len = 0;
		return -1;
	}

	*tp = wsi->tls.quic_tp_recv;
	if (tp_len)
		*tp_len = wsi->tls.quic_tp_recv_len;

	return 0;
}

static int test_secrets_extracted = 0;

static int
test_secret_cb(struct lws *wsi, enum lws_tls_quic_secret_type type,
	       const uint8_t *secret, size_t secret_len)
{
	lwsl_notice("%s: extracted type %d, len %d\n", __func__, type, (int)secret_len);
	test_secrets_extracted++;
	return 0;
}

int
lws_tls_quic_api_test(void)
{
	struct lws wsi_client = {0}, wsi_server = {0};
	gnutls_session_t csession = NULL, ssession = NULL;
	gnutls_certificate_credentials_t s_cred = NULL;
	gnutls_certificate_credentials_t c_cred = NULL;
	uint8_t c2s[4096], s2c[4096];
	size_t c2s_len = 0, s2c_len = 0;
	int iter = 0;

	const uint8_t ctp[] = { 0x01, 0x02, 0x03 };
	const uint8_t stp[] = { 0x04, 0x05, 0x06, 0x07 };
	const uint8_t *rtp = NULL;
	size_t rtp_len = 0;



	gnutls_certificate_allocate_credentials(&s_cred);
	gnutls_certificate_set_x509_key_file(s_cred, "../dummy.pem", "../dummy.pem", GNUTLS_X509_FMT_PEM);
	gnutls_certificate_allocate_credentials(&c_cred);

	gnutls_init(&csession, GNUTLS_CLIENT);
	gnutls_init(&ssession, GNUTLS_SERVER);

	gnutls_priority_set_direct(csession, "NORMAL:-VERS-ALL:+VERS-TLS1.3", NULL);
	gnutls_priority_set_direct(ssession, "NORMAL:-VERS-ALL:+VERS-TLS1.3", NULL);

	gnutls_credentials_set(ssession, GNUTLS_CRD_CERTIFICATE, s_cred);
	gnutls_credentials_set(csession, GNUTLS_CRD_CERTIFICATE, c_cred);

	wsi_client.tls.ssl = (lws_tls_conn *)csession;
	wsi_server.tls.ssl = (lws_tls_conn *)ssession;

	if (lws_tls_quic_init(&wsi_client, test_secret_cb))
		goto fail;
	if (lws_tls_quic_init(&wsi_server, test_secret_cb))
		goto fail;

	lws_tls_quic_set_transport_parameters(&wsi_client, ctp, sizeof(ctp));
	lws_tls_quic_set_transport_parameters(&wsi_server, stp, sizeof(stp));

	c2s_len = sizeof(c2s);
	lws_tls_quic_advance_handshake(&wsi_client, 0, NULL, 0, c2s, &c2s_len);

	while (iter++ < 10) {
		if (c2s_len) {
			lwsl_notice("C -> S: %d bytes\n", (int)c2s_len);
			s2c_len = sizeof(s2c);
			(void)lws_tls_quic_advance_handshake(&wsi_server, 0, c2s, c2s_len, s2c, &s2c_len);
			c2s_len = 0;
		}

		if (s2c_len) {
			lwsl_notice("S -> C: %d bytes\n", (int)s2c_len);
			c2s_len = sizeof(c2s);
			(void)lws_tls_quic_advance_handshake(&wsi_client, 0, s2c, s2c_len, c2s, &c2s_len);
			s2c_len = 0;
		}

		if (gnutls_session_is_resumed(csession) ||
		    (gnutls_handshake_get_last_in(csession) == GNUTLS_HANDSHAKE_FINISHED &&
		     gnutls_handshake_get_last_in(ssession) == GNUTLS_HANDSHAKE_FINISHED))
			break; /* Not very robust condition, but good enough if it passes */
	}

	lwsl_notice("Handshake finished, secrets extracted: %d\n", test_secrets_extracted);

	if (lws_tls_quic_get_transport_parameters(&wsi_server, &rtp, &rtp_len) == 0) {
		if (rtp_len != sizeof(ctp) || memcmp(rtp, ctp, rtp_len))
			lwsl_err("Server failed to receive Client TP properly\n");
	} else
		lwsl_err("Server failed to receive Client TP\n");

	if (lws_tls_quic_get_transport_parameters(&wsi_client, &rtp, &rtp_len) == 0) {
		if (rtp_len != sizeof(stp) || memcmp(rtp, stp, rtp_len))
			lwsl_err("Client failed to receive Server TP properly\n");
	} else
		lwsl_err("Client failed to receive Server TP\n");

	if (!lws_tls_quic_get_transport_parameters(&wsi_server, &rtp, &rtp_len) &&
	    !lws_tls_quic_get_transport_parameters(&wsi_client, &rtp, &rtp_len))
		lwsl_notice("Transport parameters successfully exchanged\n");

fail:
	if (csession) gnutls_deinit(csession);
	if (ssession) gnutls_deinit(ssession);
	if (s_cred) gnutls_certificate_free_credentials(s_cred);
	if (c_cred) gnutls_certificate_free_credentials(c_cred);

	gnutls_quic_bio_free(&wsi_client);
	gnutls_quic_bio_free(&wsi_server);

	return 0;
}

int
lws_tls_quic_migrate_wsi(struct lws *old_wsi, struct lws *new_wsi)
{
	gnutls_session_t session;

	if (!new_wsi || !new_wsi->tls.ssl)
		return -1;

	session = (gnutls_session_t)new_wsi->tls.ssl;
	gnutls_session_set_ptr(session, new_wsi);

	return 0;
}

#endif
