/*
 * lws-api-test-lws_tokenize
 *
 * 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 the most minimal http server you can make with lws.
 *
 * To keep it simple, it serves stuff from the subdirectory 
 * "./mount-origin" of the directory it was started in.
 * You can change that by changing mount.origin below.
 */

#include <libwebsockets.h>

enum {
	LWS_SW_D,
	LWS_SW_F,
	LWS_SW_S,
	LWS_SW_HELP,
};

static const struct lws_switches switches[] = {
	[LWS_SW_D]	= { "-d",              "Debug logs (e.g. -d 15)" },
	[LWS_SW_F]	= { "-f",              "Enable -f feature" },
	[LWS_SW_S]	= { "-s",              "Use TLS / https" },
	[LWS_SW_HELP]	= { "--help",		"Show this help information" },
};

#include <string.h>
#include <stdio.h>

struct expected {
	lws_tokenize_elem e;
	const char *value;
	size_t len;
};

struct tests {
	const char *string;
	struct expected *exp;
	int count;
	int flags;
};

struct expected expected1[] = {
			{ LWS_TOKZE_TOKEN,		"protocol-1", 10 },
		{ LWS_TOKZE_DELIMITER, ",", 1},
			{ LWS_TOKZE_TOKEN,		"protocol_2", 10 },
		{ LWS_TOKZE_DELIMITER, ",", 1},
			{ LWS_TOKZE_TOKEN,		"protocol3", 9 },
		{ LWS_TOKZE_ENDED, NULL, 0 },
	},
	expected2[] = {
		{ LWS_TOKZE_TOKEN_NAME_COLON,		"Accept-Language", 15 },
			{ LWS_TOKZE_TOKEN,		"fr-CH", 5 },
		{ LWS_TOKZE_DELIMITER,			",", 1 },
			{ LWS_TOKZE_TOKEN,		"fr", 2 },
			{ LWS_TOKZE_DELIMITER,		";", 1},
			{ LWS_TOKZE_TOKEN_NAME_EQUALS,	"q", 1 },
			{ LWS_TOKZE_FLOAT,		"0.9", 3 },
		{ LWS_TOKZE_DELIMITER,			",", 1 },
			{ LWS_TOKZE_TOKEN,		"en", 2 },
			{ LWS_TOKZE_DELIMITER,		";", 1},
			{ LWS_TOKZE_TOKEN_NAME_EQUALS,	"q", 1 },
			{ LWS_TOKZE_FLOAT,		"0.8", 3 },
		{ LWS_TOKZE_DELIMITER,			",", 1 },
			{ LWS_TOKZE_TOKEN,		"de", 2 },
			{ LWS_TOKZE_DELIMITER,		";", 1},
			{ LWS_TOKZE_TOKEN_NAME_EQUALS,	"q", 1 },
			{ LWS_TOKZE_FLOAT,		"0.7", 3 },
		{ LWS_TOKZE_DELIMITER, ",", 1 },
			{ LWS_TOKZE_DELIMITER,		"*", 1 },
			{ LWS_TOKZE_DELIMITER,		";", 1 },
			{ LWS_TOKZE_TOKEN_NAME_EQUALS,	"q", 1 },
			{ LWS_TOKZE_FLOAT,		"0.5", 3 },
		{ LWS_TOKZE_ENDED, NULL, 0 },
	},
	expected3[] = {
			{ LWS_TOKZE_TOKEN_NAME_EQUALS,	"quoted", 6 },
			{ LWS_TOKZE_QUOTED_STRING,	"things:", 7 },
		{ LWS_TOKZE_DELIMITER,			",", 1 },
			{ LWS_TOKZE_INTEGER,		"1234", 4 },
		{ LWS_TOKZE_ENDED, NULL, 0 },
	},
	expected4[] = {
		{ LWS_TOKZE_ERR_COMMA_LIST,		",", 1 },
	},
	expected5[] = {
			{ LWS_TOKZE_TOKEN,		"brokenlist2", 11 },
		{ LWS_TOKZE_DELIMITER, ",", 1 },
		{ LWS_TOKZE_ERR_COMMA_LIST,		",", 1 },
	},
	expected6[] = {
			{ LWS_TOKZE_TOKEN,		"brokenlist3", 11 },
		{ LWS_TOKZE_DELIMITER, ",", 1 },
		{ LWS_TOKZE_ERR_COMMA_LIST,		",", 1 },

	},
	expected7[] = {
			{ LWS_TOKZE_TOKEN, "fr", 2 },
			{ LWS_TOKZE_DELIMITER, "-", 1 },
			{ LWS_TOKZE_TOKEN, "CH", 2 },
			{ LWS_TOKZE_DELIMITER, ",", 1 },
			{ LWS_TOKZE_TOKEN, "fr", 2 },
			{ LWS_TOKZE_DELIMITER, ";", 1 },
			{ LWS_TOKZE_TOKEN_NAME_EQUALS, "q", 1 },
			{ LWS_TOKZE_FLOAT, "0.9", 3 },
			{ LWS_TOKZE_DELIMITER, ",", 1 },
			{ LWS_TOKZE_TOKEN, "en", 2 },
			{ LWS_TOKZE_DELIMITER, ";", 1 },
			{ LWS_TOKZE_TOKEN_NAME_EQUALS, "q", 1 },
			{ LWS_TOKZE_FLOAT, "0.8", 3 },
			{ LWS_TOKZE_DELIMITER, ",", 1 },
			{ LWS_TOKZE_TOKEN, "de", 2 },
			{ LWS_TOKZE_DELIMITER, ";", 1 },
			{ LWS_TOKZE_TOKEN_NAME_EQUALS, "q", 1 },
			{ LWS_TOKZE_FLOAT, "0.7", 3 },
			{ LWS_TOKZE_DELIMITER, ",", 1 },
			{ LWS_TOKZE_TOKEN, "*", 1 },
			{ LWS_TOKZE_DELIMITER, ";", 1 },
			{ LWS_TOKZE_TOKEN_NAME_EQUALS, "q", 1 },
			{ LWS_TOKZE_FLOAT, "0.5", 3 },
			{ LWS_TOKZE_ENDED, "", 0 },
	},
	expected8[] = {
		{ LWS_TOKZE_TOKEN, "Οὐχὶ", 10 },
		{ LWS_TOKZE_TOKEN, "ταὐτὰ", 12 },
		{ LWS_TOKZE_TOKEN, "παρίσταταί", 22 },
		{ LWS_TOKZE_TOKEN, "μοι", 6 },
		{ LWS_TOKZE_TOKEN, "γιγνώσκειν", 21 },
		{ LWS_TOKZE_DELIMITER, ",", 1 },
		{ LWS_TOKZE_TOKEN, "ὦ", 3 },
		{ LWS_TOKZE_TOKEN, "ἄνδρες", 13 },
		{ LWS_TOKZE_TOKEN, "᾿Αθηναῖοι", 20 },
		{ LWS_TOKZE_DELIMITER, ",", 1 },
		{ LWS_TOKZE_TOKEN, "greek", 5 },
		{ LWS_TOKZE_ENDED, "", 0 },
	},
	expected9[] = {
		/*
		 *  because the tokenizer scans ahead for = aggregation,
		 * it finds the broken utf8 before reporting the token
		 */
		{ LWS_TOKZE_ERR_BROKEN_UTF8, "", 0 },
	},
	expected10[] = {
		{ LWS_TOKZE_TOKEN, "badutf8-2", 9 },
		{ LWS_TOKZE_TOKEN, "퟿", 3 },
		{ LWS_TOKZE_DELIMITER, ",", 1 },
		{ LWS_TOKZE_ERR_BROKEN_UTF8, "", 0 },
	},
	expected11[] = {
		{ LWS_TOKZE_TOKEN, "1.myserver", 10 },
		{ LWS_TOKZE_DELIMITER, ".", 1 },
		{ LWS_TOKZE_TOKEN, "com", 3 },
		{ LWS_TOKZE_ENDED, "", 0 },
	},
	expected12[] = {
		{ LWS_TOKZE_TOKEN, "1.myserver.com", 14 },
		{ LWS_TOKZE_ENDED, "", 0 },
	},
	expected13[] = {
		{ LWS_TOKZE_TOKEN, "1.myserver.com", 14 },
		{ LWS_TOKZE_ENDED, "", 0 },
	},
	expected14[] = {
		{ LWS_TOKZE_INTEGER, "1", 1 },
		{ LWS_TOKZE_DELIMITER, ".", 1 },
		{ LWS_TOKZE_TOKEN, "myserver", 8 },
		{ LWS_TOKZE_DELIMITER, ".", 1 },
		{ LWS_TOKZE_TOKEN, "com", 3 },
		{ LWS_TOKZE_ENDED, "", 0 },
	},
	expected15[] = {
		{ LWS_TOKZE_TOKEN, "close", 5 },
		{ LWS_TOKZE_DELIMITER, ",", 1 },
		{ LWS_TOKZE_TOKEN, "Upgrade", 7 },
		{ LWS_TOKZE_ENDED, "", 0 },
	},
	expected16[] = {
		{ LWS_TOKZE_TOKEN_NAME_EQUALS, "a", 1 },
		{ LWS_TOKZE_TOKEN, "5", 1 },
		{ LWS_TOKZE_ENDED, "", 0 },
	},
	expected17[] = {
		{ LWS_TOKZE_TOKEN, "hello", 5 },
		{ LWS_TOKZE_ENDED, "", 0 },
	},
	expected18[] = {
		{ LWS_TOKZE_TOKEN, "x=y", 3 },
		{ LWS_TOKZE_ENDED, "", 0 },
	}
;

struct tests tests[] = {
	{
		" protocol-1, protocol_2\t,\tprotocol3\n",
		expected1, LWS_ARRAY_SIZE(expected1),
		LWS_TOKENIZE_F_MINUS_NONTERM | LWS_TOKENIZE_F_AGG_COLON
	}, {
		"Accept-Language: fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5",
		expected2, LWS_ARRAY_SIZE(expected2),
		LWS_TOKENIZE_F_MINUS_NONTERM | LWS_TOKENIZE_F_AGG_COLON
	}, {
		"quoted = \"things:\", 1234",
		expected3, LWS_ARRAY_SIZE(expected3),
		LWS_TOKENIZE_F_MINUS_NONTERM | LWS_TOKENIZE_F_AGG_COLON
	}, {
		", brokenlist1",
		expected4, LWS_ARRAY_SIZE(expected4),
		LWS_TOKENIZE_F_COMMA_SEP_LIST
	}, {
		"brokenlist2,,",
		expected5, LWS_ARRAY_SIZE(expected5),
		LWS_TOKENIZE_F_COMMA_SEP_LIST
	}, {
		"brokenlist3,",
		expected6, LWS_ARRAY_SIZE(expected6),
		LWS_TOKENIZE_F_COMMA_SEP_LIST
	}, {
		"fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5",
		expected7, LWS_ARRAY_SIZE(expected7),
		LWS_TOKENIZE_F_ASTERISK_NONTERM | LWS_TOKENIZE_F_RFC7230_DELIMS
	},
	{
		" Οὐχὶ ταὐτὰ παρίσταταί μοι γιγνώσκειν, ὦ ἄνδρες ᾿Αθηναῖοι, greek",
		expected8, LWS_ARRAY_SIZE(expected8),
		LWS_TOKENIZE_F_RFC7230_DELIMS
	},
	{
		"badutf8-1 \x80...",
		expected9, LWS_ARRAY_SIZE(expected9),
		LWS_TOKENIZE_F_MINUS_NONTERM | LWS_TOKENIZE_F_RFC7230_DELIMS
	},
	{
		"badutf8-2 \xed\x9f\xbf,\x80...",
		expected10, LWS_ARRAY_SIZE(expected10),
		LWS_TOKENIZE_F_MINUS_NONTERM | LWS_TOKENIZE_F_RFC7230_DELIMS
	},
	{
		"1.myserver.com",
		expected11, LWS_ARRAY_SIZE(expected11),
		0
	},
	{
		"1.myserver.com",
		expected12, LWS_ARRAY_SIZE(expected12),
		LWS_TOKENIZE_F_DOT_NONTERM
	},
	{
		"1.myserver.com",
		expected13, LWS_ARRAY_SIZE(expected13),
		LWS_TOKENIZE_F_DOT_NONTERM | LWS_TOKENIZE_F_NO_FLOATS
	},
	{
		"1.myserver.com",
		expected14, LWS_ARRAY_SIZE(expected14),
		LWS_TOKENIZE_F_NO_FLOATS
	},
	{
		"close,  Upgrade",
		expected15, LWS_ARRAY_SIZE(expected15),
		LWS_TOKENIZE_F_COMMA_SEP_LIST
	},
	{
		"a=5", expected16, LWS_ARRAY_SIZE(expected16),
		LWS_TOKENIZE_F_NO_INTEGERS
	},
	{
		"# comment1\r\nhello #comment2\r\n#comment3", expected17,
		LWS_ARRAY_SIZE(expected17), LWS_TOKENIZE_F_HASH_COMMENT
	},
	{
		"x=y", expected18,
		LWS_ARRAY_SIZE(expected18), LWS_TOKENIZE_F_EQUALS_NONTERM
	}
};

/*
 * add LWS_TOKZE_ERRS to the element index (which may be negative by that
 * amount) to index this array
 */

static const char *element_names[] = {
	"LWS_TOKZE_ERR_BROKEN_TOO_LONG",
	"LWS_TOKZE_ERR_BROKEN_WANT_READ",
	"LWS_TOKZE_ERR_BROKEN_UTF8",
	"LWS_TOKZE_ERR_UNTERM_STRING",
	"LWS_TOKZE_ERR_MALFORMED_FLOAT",
	"LWS_TOKZE_ERR_NUM_ON_LHS",
	"LWS_TOKZE_ERR_COMMA_LIST",
	"LWS_TOKZE_ENDED",
	"LWS_TOKZE_DELIMITER",
	"LWS_TOKZE_TOKEN",
	"LWS_TOKZE_INTEGER",
	"LWS_TOKZE_FLOAT",
	"LWS_TOKZE_TOKEN_NAME_EQUALS",
	"LWS_TOKZE_TOKEN_NAME_COLON",
	"LWS_TOKZE_QUOTED_STRING",
};


int
exp_cb1(void *priv, const char *name, char *out, size_t *pos, size_t olen,
	size_t *exp_ofs)
{
	const char *replace = NULL;
	size_t total, budget;

	if (!strcmp(name, "test")) {
		replace = "replacement_string";
		total = strlen(replace);
		goto expand;
	}

	return LSTRX_FATAL_NAME_UNKNOWN;

expand:
	budget = olen - *pos;
	total -= *exp_ofs;
	if (total < budget)
		budget = total;

	if (out)
		memcpy(out + *pos, replace + (*exp_ofs), budget);
	*exp_ofs += budget;
	*pos += budget;

	if (budget == total)
		return LSTRX_DONE;

	return LSTRX_FILLED_OUT;
}

static const char *exp_inp1 = "this-is-a-${test}-for-strexp";

int main(int argc, const char **argv)
{
	struct lws_context_creation_info info;
	struct lws_context *cx;
	struct lws_tokenize ts;
	lws_tokenize_elem e;
	const char *p;
	char b1[22];
	int n;
	int fail = 0, ok = 0, flags = 0;
	char dotstar[512];
	(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;
	}



	lwsl_user("LWS API selftest: lws_tokenize\n");

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


	lws_context_info_defaults(&info, NULL);
	lws_cmdline_option_handle_builtin(argc, argv, &info);
	info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT |
		       LWS_SERVER_OPTION_H2_JUST_FIX_WINDOW_UPDATE_OVERFLOW;

	/*
	 * since we know this lws context is only ever going to be used with
	 * one client wsis / fds / sockets at a time, let lws know it doesn't
	 * have to use the default allocations for fd tables up to ulimit -n.
	 * It will just allocate for 1 internal and 1 (+ 1 http2 nwsi) that we
	 * will use.
	 */
	info.fd_limit_per_thread = 1 + 1 + 1;

#if 0
#if defined(LWS_WITH_MBEDTLS) || defined(USE_WOLFSSL)
	/*
	 * OpenSSL uses the system trust store.  mbedTLS has to be told which
	 * CA to trust explicitly.
	 */
	info.client_ssl_ca_filepath = "./warmcat.com.cer";
#endif
#endif
#if 0
	n = open("./warmcat.com.cer", O_RDONLY);
	if (n >= 0) {
		info.client_ssl_ca_mem_len = read(n, memcert, sizeof(memcert));
		info.client_ssl_ca_mem = memcert;
		close(n);
		n = 0;
		memcert[info.client_ssl_ca_mem_len++] = '\0';
	}
#endif

	{
		const char * match, *argv1[] = { "arg0", "--arg1", "arg2", "-arg3", "arg4", NULL };

		match = lws_cmdline_options(LWS_ARRAY_SIZE(argv1) - 1, argv1, NULL, NULL);
		if (!match || strcmp(match, "arg2")) {
			lwsl_warn("%s: test1 result unexpected: '%s'\n", __func__, match);
			fail++;
		}
	}

	{
		/* lws_fx_t */

		lws_fx_t r, a, b;

		a.whole = 1;
		a.frac = 0;

		b.whole = 5;
		b.frac = 50000000;

		lws_fx_add(&r, &a, &b);

		if (r.whole != 6 || r.frac != 50000000) {
			lwsl_err("%s: fixed3232: test1 fail\n", __func__);
			return 1;
		}


		lws_fx_sub(&r, &b, &a);

		if (r.whole != 4 || r.frac != 50000000) {
			lwsl_err("%s: fixed3232: test2 fail\n", __func__);
			return 1;
		}


		a.whole = -1;
		a.frac = 0;

		b.whole = 5;
		b.frac = 50000000;

		lws_fx_add(&r, &a, &b);

		if (r.whole != 4 || r.frac != 50000000) {
			lwsl_err("%s: fixed3232: test3 fail\n", __func__);
			return 1;
		}


		a.whole = -1;
		a.frac = 0;

		b.whole = -1;
		b.frac = 50000000;

		lws_fx_add(&r, &a, &b);

		if (r.whole != -2 || r.frac != 50000000) {
			lwsl_err("%s: fixed3232: test4 fail\n", __func__);
			return 1;
		}

		lws_fx_set(a, -1, 70000000);
		lws_fx_set(b, -1, 50000000);

		lws_fx_add(&r, &a, &b);

		if (r.whole != -3 || r.frac != -20000000) {
			lwsl_err("%s: fixed3232: test4a fail: %s\n", __func__,
					lws_fx_string(&r, b1, sizeof(b1)));
			return 1;
		}

		lws_fx_set(a, 1, 70000000);
		lws_fx_set(b, -1, 50000000);

		lws_fx_add(&r, &a, &b);

		if (r.whole != 0 || r.frac != 20000000) {
			lwsl_err("%s: fixed3232: test4b fail: %s\n", __func__,
					lws_fx_string(&r, b1, sizeof(b1)));
			return 1;
		}

		lws_fx_set(a, -1, 70000000);
		lws_fx_set(b, 1, 50000000);

		lws_fx_add(&r, &a, &b);

		if (r.whole != 0 || r.frac != -20000000) {
			lwsl_err("%s: fixed3232: test4b1 fail: %s\n", __func__,
					lws_fx_string(&r, b1, sizeof(b1)));
			return 1;
		}

		lws_fx_set(a, 3, 0);
		lws_fx_set(b, 5, 10000000);

		lws_fx_mul(&r, &a, &b);

		if (r.whole != 15 || r.frac != 30000000) {
			lwsl_err("%s: fixed3232: test5 fail: %d.%08u\n", __func__, r.whole, r.frac);
			return 1;
		}

		lws_fx_set(a, -3, 0);
		lws_fx_set(b, 5, 10000000);

		lws_fx_mul(&r, &a, &b);

		if (r.whole != -15 || r.frac != -30000000) {
			lwsl_err("%s: fixed3232: test6 fail: %d.%08u\n", __func__, r.whole, r.frac);
			return 1;
		}

		lws_fx_set(a, 3, 0);
		lws_fx_set(b, -5, 10000000);

		lws_fx_mul(&r, &a, &b);

		if (r.whole != -15 || r.frac != -30000000) {
			lwsl_err("%s: fixed3232: test7 fail: %d.%08d\n", __func__, r.whole, r.frac);
			return 1;
		}


		lws_fx_set(a, -3, 0);
		lws_fx_set(b, -5, 10000000);

		lws_fx_mul(&r, &a, &b);

		if (r.whole != 15 || r.frac != 30000000) {
			lwsl_err("%s: fixed3232: test8 fail: %d.%08u\n", __func__, r.whole, r.frac);
			return 1;
		}

		lws_fx_set(a, 3, 50000000);
		lws_fx_set(b, 5, 10000000);

		lws_fx_mul(&r, &a, &b);

		if (r.whole != 17 || r.frac != 85000000) {
			lwsl_err("%s: fixed3232: test9 fail: %d.%08u\n", __func__, r.whole, r.frac);
			return 1;
		}

		lws_fx_set(a, -3, 50000000);
		lws_fx_set(b, 5, 10000000);

		lws_fx_mul(&r, &a, &b);

		if (r.whole != -17 || r.frac != -85000000) {
			lwsl_err("%s: fixed3232: test10 fail: %d.%08u\n", __func__, r.whole, r.frac);
			return 1;
		}

		lws_fx_set(a, 1, 50000000);
		lws_fx_set(b, 0, 10000000);

		lws_fx_mul(&r, &a, &b);

		if (r.whole != 0 || r.frac != 15000000) {
			lwsl_err("%s: fixed3232: test10a fail: %d.%08u\n", __func__, r.whole, r.frac);
			return 1;
		}

		lws_fx_set(a, -1, 50000000);
		lws_fx_set(b, 0, 10000000);

		lws_fx_mul(&r, &a, &b);

		if (r.whole != 0 || r.frac != -15000000) {
			lwsl_err("%s: fixed3232: test10b fail: %d.%08u\n", __func__, r.whole, r.frac);
			return 1;
		}

		lws_fx_set(a, 1, 50000000);
		lws_fx_set(b, 0, -10000000);

		lws_fx_mul(&r, &a, &b);

		if (r.whole != 0 || r.frac != -15000000) {
			lwsl_err("%s: fixed3232: test10c fail: %d.%08u\n", __func__, r.whole, r.frac);
			return 1;
		}

		lws_fx_set(a, 3, 50000000);
		lws_fx_set(b, -5, 10000000);

		lws_fx_mul(&r, &a, &b);

		if (r.whole != -17 || r.frac != -85000000) {
			lwsl_err("%s: fixed3232: test11 fail: %d.%08u\n", __func__, r.whole, r.frac);
			return 1;
		}

		lws_fx_set(a, -3, 50000000);
		lws_fx_set(b, -5, 10000000);

		lws_fx_mul(&r, &a, &b);

		if (r.whole != 17 || r.frac != 85000000) {
			lwsl_err("%s: fixed3232: test12 fail: %d.%08u\n", __func__, r.whole, r.frac);
			return 1;
		}

		lws_fx_set(a, 27, 0);
		lws_fx_set(b, 9, 0);

		lws_fx_div(&r, &a, &b);

		if (r.whole != 3 || r.frac != 0) {
			lwsl_err("%s: fixed3232: test13 fail: %d.%08u\n", __func__, r.whole, r.frac);
			return 1;
		}

		lws_fx_set(a, 33, 33333333);
		lws_fx_set(b, 11, 0);

		lws_fx_div(&r, &a, &b);

		if (r.whole != 3 || r.frac != 3030302) {
			lwsl_err("%s: fixed3232: test14 fail: %d.%08u\n", __func__, r.whole, r.frac);
			return 1;
		}

		lws_fx_set(a, 33, 33333333);
		lws_fx_set(b, 1, 10000000);

		lws_fx_div(&r, &a, &b);

		if (r.whole != 30 || r.frac != 30303030) {
			lwsl_err("%s: fixed3232: test15 fail: %d.%08u\n", __func__, r.whole, r.frac);
			return 1;
		}

		lws_fx_set(a, -33, 33333333);
		lws_fx_set(b, 1, 10000000);

		lws_fx_div(&r, &a, &b);

		if (r.whole != -30 || r.frac != -30303030) {
			lwsl_err("%s: fixed3232: test16 fail: %d.%08u\n", __func__, r.whole, r.frac);
			return 1;
		}

		a.whole = 33;
		a.frac = 33333333;

		b.whole = -1;
		b.frac = 10000000;

		lws_fx_div(&r, &a, &b);

		if (r.whole != -30 || r.frac != -30303030) {
			lwsl_err("%s: fixed3232: test17 fail: %d.%08u\n", __func__, r.whole, r.frac);
			return 1;
		}

		a.whole = -33;
		a.frac = 33333333;

		b.whole = -1;
		b.frac = 10000000;

		lws_fx_div(&r, &a, &b);

		if (r.whole != 30 || r.frac != 30303030) {
			lwsl_err("%s: fixed3232: test18 fail: %d.%08u\n", __func__, r.whole, r.frac);
			return 1;
		}


		a.whole = 64;
		a.frac = 0;

		lws_fx_sqrt(&r, &a);

		if (r.whole != 8 || r.frac != 0) {
			lwsl_err("%s: fixed3232: test19 fail: %d.%08u\n", __func__, r.whole, r.frac);
			return 1;
		}

		a.whole = 16512;
		a.frac = 25000000;

		lws_fx_sqrt(&r, &a);

		if (r.whole != 128 || r.frac != 50000000) {
			lwsl_err("%s: fixed3232: test20 fail: %d.%08u\n", __func__, r.whole, r.frac);
			return 1;
		}

		a.whole = 22;
		a.frac = 80000000;

		b.whole = 18;
		b.frac = 0;

		lws_fx_sub(&r, &a, &b);

		if (r.whole != 4 || r.frac != 80000000) {
			lwsl_err("%s: fixed3232: test21 fail: %d.%08u\n", __func__, r.whole, r.frac);
			return 1;
		}

		a.whole = 2;
		a.frac = 80000000;

		b.whole = 18;
		b.frac = 0;

		lws_fx_sub(&r, &a, &b);

		if (r.whole != -15 || r.frac != 20000000) {
			lwsl_err("%s: fixed3232: test22 fail: %d.%08u\n", __func__, r.whole, r.frac);
			return 1;
		}

		a.whole = 24;
		a.frac = 20000000;

		b.whole = 24;
		b.frac = 0;

		lws_fx_sub(&r, &a, &b);

		if (r.whole != 0 || r.frac != 20000000) {
			lwsl_err("%s: fixed3232: test23 fail: %d.%08u\n", __func__, r.whole, r.frac);
			return 1;
		}
	}


	cx = lws_create_context(&info);

	/* lws_strexp */

	{
		size_t in_len, used_in, used_out;
		lws_strexp_t exp;
		char obuf[128];
		const char *p;

		obuf[0] = '\0';
		lws_strexp_init(&exp, NULL, exp_cb1, obuf, sizeof(obuf));
		n = lws_strexp_expand(&exp, exp_inp1, 28, &used_in, &used_out);
		if (n != LSTRX_DONE || used_in != 28 ||
		    strcmp(obuf, "this-is-a-replacement_string-for-strexp")) {
			lwsl_notice("%s: obuf %s\n", __func__, obuf);
			lwsl_err("%s: lws_strexp test 1 failed: %d\n", __func__, n);

			return 1;
		}

		/* as above, but don't generate output, just find the length */

		lws_strexp_init(&exp, NULL, exp_cb1, NULL, (size_t)-1);
		n = lws_strexp_expand(&exp, exp_inp1, 28, &used_in, &used_out);
		if (n != LSTRX_DONE || used_in != 28 || used_out != 39) {
			lwsl_err("%s: lws_strexp test 2 failed: %d, used_out: %d\n",
					__func__, n, (int)used_out);

			return 1;
		}

		p = exp_inp1;
		in_len = strlen(p);
		memset(obuf, 0, sizeof(obuf));
		lws_strexp_init(&exp, NULL, exp_cb1, obuf, 16);
		n = lws_strexp_expand(&exp, p, in_len, &used_in, &used_out);
		if (n != LSTRX_FILLED_OUT || used_in != 16 || used_out != 16) {
			lwsl_err("a\n");
			return 1;
		}

		p += used_in;
		in_len -= used_in;

		memset(obuf, 0, sizeof(obuf));
		lws_strexp_reset_out(&exp, obuf, 16);

		n = lws_strexp_expand(&exp, p, in_len, &used_in, &used_out);
		if (n != LSTRX_FILLED_OUT || used_in != 5 || used_out != 16) {
			lwsl_err("b: n %d, used_in %d, used_out %d\n", n,
					(int)used_in, (int)used_out);
			return 2;
		}

		p += used_in;
		in_len -= used_in;

		memset(obuf, 0, sizeof(obuf));
		lws_strexp_reset_out(&exp, obuf, 16);

		n = lws_strexp_expand(&exp, p, in_len, &used_in, &used_out);
		if (n != LSTRX_DONE || used_in != 7 || used_out != 7) {
			lwsl_err("c: n %d, used_in %d, used_out %d\n", n, (int)used_in, (int)used_out);
			return 2;
		}
	}

	/* sanity check base64 decode */

	{
		struct lws_b64state b64;
		uint8_t result[16];
		size_t in_len, out_len;

		lws_b64_decode_state_init(&b64);
		in_len = 8;
		out_len = sizeof(result);
		if (lws_b64_decode_stateful(&b64, "YWJjZA==", &in_len, result, &out_len, 1)) {
			lwsl_err("%s: b64 test 1 decode failed\n", __func__);
			return 1;
		}
		if (out_len != 4) {
			lwsl_err("%s: b64 test 1 decode len not 4 (%d)\n", __func__, (int)out_len);
			return 1;
		}
		if (memcmp(result, "abcd", out_len)) {
			lwsl_err("%s: b64 test 1 decode value not 'abcde' (%.*s)\n", __func__, (int)out_len, result);
			return 1;
		}

		lws_b64_decode_state_init(&b64);
		in_len = 8;
		out_len = sizeof(result);
		if (lws_b64_decode_stateful(&b64, "YWJjZGU=", &in_len, result, &out_len, 1)) {
			lwsl_err("%s: b64 test 2 decode failed\n", __func__);
			return 1;
		}
		if (out_len != 5) {
			lwsl_err("%s: b64 test 2 decode len not 5 (%d)\n", __func__, (int)out_len);
			return 1;
		}
		if (memcmp(result, "abcde", out_len)) {
			lwsl_err("%s: b64 test 2 decode value not 'abcd' (%.*s)\n", __func__, (int)out_len, result);
			return 1;
		}

		lws_b64_decode_state_init(&b64);
		in_len = 8;
		out_len = sizeof(result);
		if (lws_b64_decode_stateful(&b64, "YWJjZGVm", &in_len, result, &out_len, 1)) {
			lwsl_err("%s: b64 test 3 decode failed\n", __func__);
			return 1;
		}
		if (out_len != 6) {
			lwsl_err("%s: b64 test 3 decode len not 6 (%d)\n", __func__, (int)out_len);
			return 1;
		}
		if (memcmp(result, "abcdef", out_len)) {
			lwsl_err("%s: b64 test 3 decode value not 'abcde' (%.*s)\n", __func__, (int)out_len, result);
			return 1;
		}
	}

	/* sanity check lws_strnncpy() */

	lws_strnncpy(dotstar, "12345678", 4, sizeof(dotstar));
	if (strcmp(dotstar, "1234")) {
		lwsl_err("%s: lws_strnncpy check failed\n", __func__);

		return 1;
	}
	lws_strnncpy(dotstar, "12345678", 8, 6);
	if (strcmp(dotstar, "12345")) {
		lwsl_err("%s: lws_strnncpy check failed\n", __func__);

		return 1;
	}

	/* sanity check lws_nstrstr() */

	{
		static const char *t1 = "abc123456";
		const char *mcp;

		mcp = lws_nstrstr(t1, strlen(t1), "abc", 3);
		if (mcp != t1) {
			lwsl_err("%s: lws_nstrstr 1 failed\n", __func__);
			return 1;
		}
		mcp = lws_nstrstr(t1, strlen(t1), "def", 3);
		if (mcp != NULL) {
			lwsl_err("%s: lws_nstrstr 2 failed\n", __func__);
			return 1;
		}
		mcp = lws_nstrstr(t1, strlen(t1), "456", 3);
		if (mcp != t1 + 6) {
			lwsl_err("%s: lws_nstrstr 3 failed: %p\n", __func__, mcp);
			return 1;
		}
		mcp = lws_nstrstr(t1, strlen(t1), "1", 1);
		if (mcp != t1 + 3) {
			lwsl_err("%s: lws_nstrstr 4 failed\n", __func__);
			return 1;
		}
		mcp = lws_nstrstr(t1, strlen(t1), "abc1234567", 10);
		if (mcp != NULL) {
			lwsl_err("%s: lws_nstrstr 5 failed\n", __func__);
			return 1;
		}
	}

	/* sanity check lws_json_simple_find() */

	{
		static const char *t1 = "{\"myname1\":true,"
					 "\"myname2\":\"string\", "
					 "\"myname3\": 123}";
		size_t alen;
		const char *mcp;

		mcp = lws_json_simple_find(t1, strlen(t1), "\"myname1\":", &alen);
		if (mcp != t1 + 11 || alen != 4) {
			lwsl_err("%s: lws_json_simple_find 1 failed: (%d) %s\n",
				 __func__, (int)alen, mcp);
			return 1;
		}

		mcp = lws_json_simple_find(t1, strlen(t1), "\"myname2\":", &alen);
		if (mcp != t1 + 27 || alen != 6) {
			lwsl_err("%s: lws_json_simple_find 2 failed\n", __func__);
			return 1;
		}

		mcp = lws_json_simple_find(t1, strlen(t1), "\"myname3\":", &alen);
		if (mcp != t1 + 47 || alen != 3) {
			lwsl_err("%s: lws_json_simple_find 3 failed\n", __func__);
			return 1;
		}

		mcp = lws_json_simple_find(t1, strlen(t1), "\"nope\":", &alen);
		if (mcp != NULL) {
			lwsl_err("%s: lws_json_simple_find 4 failed\n", __func__);
			return 1;
		}
	}

	/* sanity check lws_parse_uri_create() */

	{
		struct {
			const char *uri;
			const char *scheme;
			const char *host;
			const char *path;
			uint16_t port;
			char unix_skt;
		} parse_tests[] = {
			{ "wss://host.com?key=val", "wss", "host.com", "?key=val", 443, 0 },
			{ "wss://[::1]:8080/path/to?query=1", "wss", "::1", "path/to?query=1", 8080, 0 },
			{ "host.com?key=val", "", "host.com", "?key=val", 0, 0 },
			{ "http://+/var/run/mysocket:/my/path", "http", "+/var/run/mysocket", "my/path", 0, 1 },
			{ "http://host.com", "http", "host.com", "/", 80, 0 },
		};
		int t;

		for (t = 0; t < (int)LWS_ARRAY_SIZE(parse_tests); t++) {
			lws_parse_uri_t *u = lws_parse_uri_create(parse_tests[t].uri);
			if (!u) {
				lwsl_err("%s: lws_parse_uri_create test %d failed to alloc\n", __func__, t);
				fail++;
				continue;
			}
			if (strcmp(u->scheme, parse_tests[t].scheme)) {
				lwsl_err("%s: test %d: scheme mismatch exp '%s' got '%s'\n", __func__, t, parse_tests[t].scheme, u->scheme);
				fail++;
			}
			if (strcmp(u->host, parse_tests[t].host)) {
				lwsl_err("%s: test %d: host mismatch exp '%s' got '%s'\n", __func__, t, parse_tests[t].host, u->host);
				fail++;
			}
			if (strcmp(u->path, parse_tests[t].path)) {
				lwsl_err("%s: test %d: path mismatch exp '%s' got '%s'\n", __func__, t, parse_tests[t].path, u->path);
				fail++;
			}
			if (u->port != parse_tests[t].port) {
				lwsl_err("%s: test %d: port mismatch exp '%d' got '%d'\n", __func__, t, parse_tests[t].port, u->port);
				fail++;
			}
			if (u->unix_skt != parse_tests[t].unix_skt) {
				lwsl_err("%s: test %d: unix_skt mismatch exp '%d' got '%d'\n", __func__, t, parse_tests[t].unix_skt, u->unix_skt);
				fail++;
			}
			lws_parse_uri_destroy(&u);
            if (u != NULL) {
                lwsl_err("%s: lws_parse_uri_destroy didn't set to NULL\n", __func__);
                fail++;
            }
		}
	}

	p = lws_cmdline_option(argc, argv, switches[LWS_SW_S].sw);

	for (n = 0; n < (int)LWS_ARRAY_SIZE(tests); n++) {
		int m = 0, in_fail = fail;
		struct expected *exp = tests[n].exp;

		memset(&ts, 0, sizeof(ts));
		ts.start = tests[n].string;
		ts.len = strlen(ts.start);
		ts.flags = (uint16_t)tests[n].flags;

		do {
			e = lws_tokenize(&ts);

			lws_strnncpy(dotstar, ts.token, ts.token_len,
				     sizeof(dotstar));
			lwsl_info("{ %s, \"%s\", %d }\n",
				  element_names[e + LWS_TOKZE_ERRS], dotstar,
				  (int)ts.token_len);

			if (m == (int)tests[n].count) {
				lwsl_notice("fail: expected end earlier\n");
				fail++;
				break;
			}

			if (e != exp->e) {
				lwsl_notice("fail... tok %s vs expected %s\n",
					element_names[e + LWS_TOKZE_ERRS],
					element_names[exp->e + LWS_TOKZE_ERRS]);
				fail++;
				break;
			}

			if (e > 0 &&
			    (ts.token_len != exp->len ||
			     memcmp(exp->value, ts.token, exp->len))) {
				lws_strnncpy(dotstar, ts.token, ts.token_len,
					     sizeof(dotstar));
				lwsl_notice("fail token mismatch %d %d %s\n",
					    (int)ts.token_len, (int)exp->len,
					    dotstar);
				fail++;
				break;
			}

			m++;
			exp++;

		} while (e > 0);

		if (fail == in_fail)
			ok++;
	}

	if (p) {
		ts.start = p;
		ts.len = strlen(p);
		ts.flags = (uint16_t)flags;

		printf("\t{\n\t\t\"%s\",\n"
		       "\t\texpected%d, LWS_ARRAY_SIZE(expected%d),\n\t\t",
		       p, (int)LWS_ARRAY_SIZE(tests) + 1,
		       (int)LWS_ARRAY_SIZE(tests) + 1);

		if (!flags)
			printf("0\n\t},\n");
		else {
			if (flags & LWS_TOKENIZE_F_MINUS_NONTERM)
				printf("LWS_TOKENIZE_F_MINUS_NONTERM");
			if (flags & LWS_TOKENIZE_F_AGG_COLON) {
				if (flags & 1)
					printf(" | ");
				printf("LWS_TOKENIZE_F_AGG_COLON");
			}
			if (flags & LWS_TOKENIZE_F_COMMA_SEP_LIST) {
				if (flags & 3)
					printf(" | ");
				printf("LWS_TOKENIZE_F_COMMA_SEP_LIST");
			}
			if (flags & LWS_TOKENIZE_F_RFC7230_DELIMS) {
				if (flags & 7)
					printf(" | ");
				printf("LWS_TOKENIZE_F_RFC7230_DELIMS");
			}
			if (flags & LWS_TOKENIZE_F_DOT_NONTERM) {
				if (flags & 15)
					printf(" | ");
				printf("LWS_TOKENIZE_F_DOT_NONTERM");
			}
			if (flags & LWS_TOKENIZE_F_NO_FLOATS) {
				if (flags & 31)
					printf(" | ");
				printf("LWS_TOKENIZE_F_NO_FLOATS");
			}
			printf("\n\t},\n");
		}

		printf("\texpected%d[] = {\n", (int)LWS_ARRAY_SIZE(tests) + 1);

		do {
			e = lws_tokenize(&ts);

			lws_strnncpy(dotstar, ts.token, ts.token_len,
				     sizeof(dotstar));

			printf("\t\t{ %s, \"%s\", %d },\n",
				  element_names[e + LWS_TOKZE_ERRS],
				  dotstar, (int)ts.token_len);

		} while (e > 0);

		printf("\t}\n");
	}

#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
	{
		time_t t;

		if (lws_http_date_parse_unix("Tue, 15 Nov 1994 08:12:31 GMT", 29, &t)) {
			lwsl_err("%s: date parse failed\n", __func__);
			fail++;
		} else {
			/* lwsl_notice("%s: %llu\n", __func__, (unsigned long long)t); */
			if (t != (time_t)784887151) {
				lwsl_err("%s: date parse wrong\n", __func__);
				fail++;
			} else {
				char s[30];

				if (lws_http_date_render_from_unix(s, sizeof(s), &t)) {
					lwsl_err("%s: failed date render\n", __func__);
					fail++;
				} else {
					if (!strcmp(s, "Tue, 15 Nov 1994 08:12:31 GMT")) {
						lwsl_err("%s: date render wrong\n", __func__);
						fail++;
					}
				}
			}
		}
	}
#endif

	{
		char buf[24];
		int m;

		m = lws_humanize(buf, sizeof(buf), 0, humanize_schema_si);
		if (m != 1 || strcmp(buf, "0")) {
			lwsl_user("%s: humanize 1 fail '%s' (%d)\n", __func__, buf, m);
			fail++;
		}
		m = lws_humanize(buf, sizeof(buf), 2, humanize_schema_si);
		if (m != 1 || strcmp(buf, "2")) {
			lwsl_user("%s: humanize 2 fail '%s' (%d)\n", __func__, buf, m);
			fail++;
		}
		m = lws_humanize(buf, sizeof(buf), 999, humanize_schema_si);
		if (m != 3 || strcmp(buf, "999")) {
			lwsl_user("%s: humanize 3 fail '%s' (%d)\n", __func__, buf, m);
			fail++;
		}
		m = lws_humanize(buf, sizeof(buf), 1000, humanize_schema_si);
		if (m != 4 || strcmp(buf, "1000")) {
			lwsl_user("%s: humanize 4 fail '%s' (%d)\n", __func__, buf, m);
			fail++;
		}
		m = lws_humanize(buf, sizeof(buf), 1024, humanize_schema_si);
		if (m != 3 || strcmp(buf, "1Ki")) {
			lwsl_user("%s: humanize 5 fail '%s' (%d)\n", __func__, buf, m);
			fail++;
		}
	}

	if (lws_strcmp_wildcard("allied", 6, "allied", 6)) {
		lwsl_user("%s: wc 1 fail\n", __func__);
		fail++;
	}
	if (lws_strcmp_wildcard("a*", 2, "allied", 6)) {
		lwsl_user("%s: wc 2 fail\n", __func__);
		fail++;
	}
	if (lws_strcmp_wildcard("all*", 4, "allied", 6)) {
		lwsl_user("%s: wc 3 fail\n", __func__);
		fail++;
	}
	if (lws_strcmp_wildcard("all*d", 5, "allied", 6)) {
		lwsl_user("%s: wc 4 fail\n", __func__);
		fail++;
	}
	if (!lws_strcmp_wildcard("b*", 2, "allied", 6)) {
		lwsl_user("%s: wc 5 fail\n", __func__);
		fail++;
	}
	if (!lws_strcmp_wildcard("b*ed", 4, "allied", 6)) {
		lwsl_user("%s: wc 6 fail\n", __func__);
		fail++;
	}
	if (!lws_strcmp_wildcard("allie", 5, "allied", 6)) {
		lwsl_user("%s: wc 7 fail\n", __func__);
		fail++;
	}
	if (lws_strcmp_wildcard("allie*", 6, "allied", 6)) {
		lwsl_user("%s: wc 8 fail\n", __func__);
		fail++;
	}
	if (lws_strcmp_wildcard("*llie*", 6, "allied", 6)) {
		lwsl_user("%s: wc 9 fail\n", __func__);
		fail++;
	}
	if (lws_strcmp_wildcard("*llied", 6, "allied", 6)) {
		lwsl_user("%s: wc 10 fail\n", __func__);
		fail++;
	}
	if (!lws_strcmp_wildcard("*llie", 5, "allied", 6)) {
		lwsl_user("%s: wc 11 fail\n", __func__);
		fail++;
	}
	if (!lws_strcmp_wildcard("*nope", 5, "allied", 6)) {
		lwsl_user("%s: wc 12 fail\n", __func__);
		fail++;
	}
	if (lws_strcmp_wildcard("*li*", 4, "allied", 6)) {
		lwsl_user("%s: wc 13 fail\n", __func__);
		fail++;
	}
	if (lws_strcmp_wildcard("*", 1, "allied", 6)) {
		lwsl_user("%s: wc 14 fail\n", __func__);
		fail++;
	}
	if (lws_strcmp_wildcard("*abc*d", 6, "xxabyyabcdd", 11)) {
		lwsl_user("%s: wc 15 fail\n", __func__);
		fail++;
	}
	if (lws_strcmp_wildcard("ssproxy.n.cn.*", 14,
				"ssproxy.n.cn.failures", 21)) {
		lwsl_user("%s: wc 16 fail\n", __func__);
		fail++;
	}

	lwsl_user("Completed: PASS: %d, FAIL: %d\n", ok, fail);

	lws_context_destroy(cx);

	return !(ok && !fail);
}
