/*
 * lws-minimal-secure-streams-tx
 *
 * Written in 2010-2021 by Andy Green <andy@warmcat.com>
 *
 * This file is made available under the Creative Commons CC0 1.0
 * Universal Public Domain Dedication.
 *
 *
 * This demonstrates proxied mass tx from secure streams, this example is a
 * client that has no policy of its own, but gets stuff done via the ss proxy.
 *
 * It opens a websocket stream and fires 100 x small 80-byte payloads on it
 * at 20Hz (50ms)
 */

#define LWS_SS_USE_SSPC

#include <libwebsockets.h>

enum {
	LWS_SW_A,
	LWS_SW_C,
	LWS_SW_D,
	LWS_SW_I,
	LWS_SW_P,
	LWS_SW_HELP,
};

static const struct lws_switches switches[] = {
	[LWS_SW_A]	= { "-a",              "Enable -a feature" },
	[LWS_SW_C]	= { "-c",              "Client connections" },
	[LWS_SW_D]	= { "-d",              "Debug logs (e.g. -d 15)" },
	[LWS_SW_I]	= { "-i",              "Interface to bind to" },
	[LWS_SW_P]	= { "-p",              "Port number to listen or connect on" },
	[LWS_SW_HELP]	= { "--help",		"Show this help information" },
};

#include <string.h>
#include <signal.h>

#define PKT_SIZE 80
#define RATE_US 50000

static int interrupted, bad = 1, reads = 100;

typedef struct myss {
	struct lws_ss_handle 	*ss;
	void			*opaque_data;
	/* ... application specific state ... */
	lws_sorted_usec_list_t	sul;

	int			count;
	char			due;
} myss_t;

/* secure streams payload interface */

static lws_ss_state_return_t
myss_rx(void *userobj, const uint8_t *buf, size_t len, int flags)
{
	/* this example isn't interested in rx */
	return LWSSSSRET_OK;
}

static void
txcb(struct lws_sorted_usec_list *sul)
{
	myss_t *m = lws_container_of(sul, myss_t, sul);

	/*
	 * We want to do 100 of these ws messages, and then exit, so we can run
	 * this as a pass / fail test.
	 */

	if (m->count == reads) {
		interrupted = 1;
		bad = 0;
	} else {
		m->due = 1;
		lws_ss_request_tx(m->ss);
	}

	lws_sul_schedule(lws_ss_get_context(m->ss), 0, &m->sul, txcb, RATE_US);
}

static lws_ss_state_return_t
myss_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, size_t *len,
	int *flags)
{
	myss_t *m = (myss_t *)userobj;

	if (!m->due)
		return LWSSSSRET_TX_DONT_SEND;

	m->due = 0;

	if (lws_get_random(lws_ss_get_context(m->ss), buf, PKT_SIZE) != PKT_SIZE)
		return LWSSSSRET_TX_DONT_SEND;

	*len = PKT_SIZE;
	*flags = LWSSS_FLAG_SOM | LWSSS_FLAG_EOM;

	m->count++;

	lws_sul_schedule(lws_ss_get_context(m->ss), 0, &m->sul, txcb, RATE_US);

	lwsl_user("%s: sending pkt %d\n", __func__, m->count);

	return LWSSSSRET_OK;
}

static lws_ss_state_return_t
myss_state(void *userobj, void *sh, lws_ss_constate_t state,
		lws_ss_tx_ordinal_t ack)
{
	myss_t *m = (myss_t *)userobj;
	struct lws_context *context = lws_ss_get_context(m->ss);

	lwsl_user("%s: %s, ord 0x%x\n", __func__, lws_ss_state_name(state),
		  (unsigned int)ack);

	switch (state) {
	case LWSSSCS_CREATING:
		return lws_ss_client_connect(m->ss);

	case LWSSSCS_CONNECTED:
		lws_sul_schedule(context, 0, &m->sul, txcb, RATE_US);
		break;
	case LWSSSCS_DISCONNECTED:
		lws_sul_cancel(&m->sul);
		break;
	case LWSSSCS_ALL_RETRIES_FAILED:
		/* if we're out of retries, we want to close the app and FAIL */
		interrupted = 1;
		break;
	default:
		break;
	}

	return 0;
}

static void
sigint_handler(int sig)
{
	interrupted = 1;
}

static const lws_ss_info_t ssi = {
	.handle_offset			= offsetof(myss_t, ss),
	.opaque_user_data_offset	= offsetof(myss_t, opaque_data),
	.rx				= myss_rx,
	.tx				= myss_tx,
	.state				= myss_state,
	.user_alloc			= sizeof(myss_t),
	.streamtype			= "spam"
};

int main(int argc, const char **argv)
{
	int n = 0;
	struct lws_context_creation_info info;
	struct lws_context *context;
	const char *p;
	(void)switches;

	if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) {
		lws_switches_print_help(argv[0], switches, LWS_ARRAY_SIZE(switches));
		return 0;
	}


	signal(SIGINT, sigint_handler);


	if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_C].sw)))
		reads = atoi(p);

	lwsl_user("LWS secure streams client TX [-d<verb>]\n");

	lws_context_info_defaults(&info, NULL);
	lws_cmdline_option_handle_builtin(argc, argv, &info);
	info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
	info.fd_limit_per_thread = 1 + 6 + 1;
	info.port = CONTEXT_PORT_NO_LISTEN;
	info.protocols = lws_sspc_protocols;
	{
		const char *p;

		/* connect to ssproxy via UDS by default, else via
		 * tcp connection to this port */
		if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_P].sw)))
			{
				int __pt = atoi(p);
				if (__pt < 0 || __pt > 65535) {
					lwsl_err("Port %d is outside valid 16-bit range\n", __pt);
					return 1;
				}
				info.ss_proxy_port = (uint16_t)__pt;
			}

		/* UDS "proxy.ss.lws" in abstract namespace, else this socket
		 * path; when -p given this can specify the network interface
		 * to bind to */
		if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_I].sw)))
			info.ss_proxy_bind = p;

		/* if -p given, -a specifies the proxy address to connect to */
		if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_A].sw)))
			info.ss_proxy_address = p;
	}

	context = lws_create_context(&info);
	if (!context) {
		lwsl_err("lws init failed\n");
		goto bail1;
	}

	if (lws_ss_create(context, 0, &ssi, NULL, NULL, NULL, NULL)) {
		lwsl_err("%s: create secure stream failed\n", __func__);
		goto bail;
	}

	/* the event loop */

	while (n >= 0 && !interrupted)
		n = lws_service(context, 0);

bail:
	lws_context_destroy(context);

bail1:
	lwsl_user("Completed: %s\n", bad ? "failed" : "OK");

	return bad;
}
