/*
 * lws System Fault Injection
 *
 * Copyright (C) 2019 - 2021 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 <assert.h>

static lws_fi_priv_t *
lws_fi_lookup(const lws_fi_ctx_t *fic, const char *name)
{
	lws_start_foreach_dll(struct lws_dll2 *, p, fic->fi_owner.head) {
		lws_fi_priv_t *pv = lws_container_of(p, lws_fi_priv_t, list);

		if (!strcmp(pv->fi.name, name))
			return pv;

	} lws_end_foreach_dll(p);

	return NULL;
}

int
lws_fi(const lws_fi_ctx_t *fic, const char *name)
{
	lws_fi_priv_t *pv;
	int n;

	pv = lws_fi_lookup(fic, name);

	if (!pv)
		return 0;

	switch (pv->fi.type) {
	case LWSFI_ALWAYS:
		goto inject;

	case LWSFI_DETERMINISTIC:
		pv->fi.times++;
		if (pv->fi.times >= pv->fi.pre)
			if (pv->fi.times < pv->fi.pre + pv->fi.count)
				goto inject;
		return 0;

	case LWSFI_PROBABILISTIC:
		if (lws_xos_percent((lws_xos_t *)&fic->xos, (int)pv->fi.pre))
			goto inject;
		return 0;

	case LWSFI_PATTERN:
	case LWSFI_PATTERN_ALLOC:
		n = (int)((pv->fi.times++) % pv->fi.count);
		if (pv->fi.pattern[n >> 3] & (1 << (n & 7)))
			goto inject;

		return 0;

	default:
		return 0;
	}

	return 0;

inject:
	lwsl_warn("%s: Injecting fault %s->%s\n", __func__,
			fic->name ? fic->name : "unk", pv->fi.name);

	return 1;
}

int
_lws_fi_user_wsi_fi(struct lws *wsi, const char *name)
{
	return lws_fi(&wsi->fic, name);
}

int
_lws_fi_user_context_fi(struct lws_context *ctx, const char *name)
{
	return lws_fi(&ctx->fic, name);
}

#if defined(LWS_WITH_SECURE_STREAMS)
int
_lws_fi_user_ss_fi(struct lws_ss_handle *h, const char *name)
{
	return lws_fi(&h->fic, name);
}

#if defined(LWS_WITH_SECURE_STREAMS_PROXY_API)
int
_lws_fi_user_sspc_fi(struct lws_sspc_handle *h, const char *name)
{
	return lws_fi(&h->fic, name);
}
#endif
#endif

int
lws_fi_add(lws_fi_ctx_t *fic, const lws_fi_t *fi)
{
	lws_fi_priv_t *pv;
	size_t n = strlen(fi->name);

	pv = lws_malloc(sizeof(*pv) + n + 1, __func__);
	if (!pv)
		return 1;

	lws_dll2_clear(&pv->list);

	memcpy(&pv->fi, fi, sizeof(*fi));
	pv->fi.name = (const char *)&pv[1];
	memcpy(&pv[1], fi->name, n + 1);

	lws_dll2_add_tail(&pv->list, &fic->fi_owner);

	return 0;
}

void
lws_fi_remove(lws_fi_ctx_t *fic, const char *name)
{
	lws_fi_priv_t *pv = lws_fi_lookup(fic, name);

	if (!pv)
		return;

	lws_dll2_remove(&pv->list);
	lws_free(pv);
}

void
lws_fi_import(lws_fi_ctx_t *fic_dest, const lws_fi_ctx_t *fic_src)
{

	/* inherit the PRNG seed for our context from source guy too */
	lws_xos_init(&fic_dest->xos, lws_xos((lws_xos_t *)&fic_src->xos));

	lws_start_foreach_dll_safe(struct lws_dll2 *, p, p1,
				   fic_src->fi_owner.head) {
		lws_fi_priv_t *pv = lws_container_of(p, lws_fi_priv_t, list);

		lws_dll2_remove(&pv->list);
		lws_dll2_add_tail(&pv->list, &fic_dest->fi_owner);

	} lws_end_foreach_dll_safe(p, p1);
}

static void
do_inherit(lws_fi_ctx_t *fic_dest, lws_fi_t *pfi, size_t trim)
{
	lws_fi_t fi = *pfi;

	fi.name += trim;

	lwsl_info("%s: %s: %s inherited as %s\n", __func__, fic_dest->name,
		  pfi->name, fi.name);

	if (fi.type == LWSFI_PATTERN_ALLOC) {
		fi.pattern = lws_malloc((size_t)((fi.count >> 3) + 1), __func__);
		if (!fi.pattern)
			return;
		memcpy((uint8_t *)fi.pattern, pfi->pattern,
		       (size_t)((fi.count >> 3) + 1));
	}

	lws_fi_add(fic_dest, &fi);
}

void
lws_fi_inherit_copy(lws_fi_ctx_t *fic_dest, const lws_fi_ctx_t *fic_src,
		    const char *scope, const char *value)
{
	size_t sl = 0, vl = 0;

	if (scope)
		sl = strlen(scope);

	if (value)
		vl = strlen(value);

	lws_start_foreach_dll_safe(struct lws_dll2 *, p, p1,
				   fic_src->fi_owner.head) {
		lws_fi_priv_t *pv = lws_container_of(p, lws_fi_priv_t, list);
		size_t nl = strlen(pv->fi.name);

		if (!scope)
			do_inherit(fic_dest, &pv->fi, 0);
		else
			if (nl > sl + 2 &&
			    !strncmp(pv->fi.name, scope, sl) &&
			    pv->fi.name[sl] == '/')
				do_inherit(fic_dest, &pv->fi, sl + 1);
			else {
				if (value && nl > sl + vl + 2 &&
				    pv->fi.name[sl] == '=' &&
				    !strncmp(pv->fi.name + sl + 1, value, vl) &&
				    pv->fi.name[sl + 1 + vl] == '/')
					do_inherit(fic_dest, &pv->fi, sl + vl + 2);
			}

	} lws_end_foreach_dll_safe(p, p1);
}

void
lws_fi_destroy(const lws_fi_ctx_t *fic)
{
	lws_start_foreach_dll_safe(struct lws_dll2 *, p, p1,
				   fic->fi_owner.head) {
		lws_fi_priv_t *pv = lws_container_of(p, lws_fi_priv_t, list);

		if (pv->fi.type == LWSFI_PATTERN_ALLOC && pv->fi.pattern) {
			lws_free((void *)pv->fi.pattern);
			pv->fi.pattern = NULL;
		}

		lws_dll2_remove(&pv->list);
		lws_free(pv);

	} lws_end_foreach_dll_safe(p, p1);
}

enum {
	PARSE_NAME,
	PARSE_WHEN,
	PARSE_PC,
	PARSE_ENDBR
};

void
lws_fi_deserialize(lws_fi_ctx_t *fic, const char *sers)
{
	struct lws_tokenize ts;
	lws_fi_t fi;
	char nm[64];
	int state = PARSE_NAME;

	/*
	 * Go through the comma-separated list of faults
	 * creating them and adding to the lws_context info
	 */

	lws_tokenize_init(&ts, sers, LWS_TOKENIZE_F_DOT_NONTERM |
				     LWS_TOKENIZE_F_NO_INTEGERS |
				     LWS_TOKENIZE_F_NO_FLOATS |
				     LWS_TOKENIZE_F_EQUALS_NONTERM |
				     LWS_TOKENIZE_F_SLASH_NONTERM |
				     LWS_TOKENIZE_F_MINUS_NONTERM);
	ts.len = (unsigned int)strlen(sers);
	if (ts.len < 1 || ts.len > 10240)
		return;

	do {
		ts.e = (int8_t)lws_tokenize(&ts);
		switch (ts.e) {
		case LWS_TOKZE_TOKEN:

			if (state == PARSE_NAME) {
				/*
				 * One fault to inject looks like, eg,
				 *
				 *   vh=xxx/listenskt
				 */

				memset(&fi, 0, sizeof(fi));

				lws_strnncpy(nm, ts.token, ts.token_len, sizeof(nm));
				fi.name = nm;
				fi.type = LWSFI_ALWAYS;

				lwsl_notice("%s: name %.*s\n", __func__, (int)ts.token_len, ts.token);

				/* added later, potentially after (when) */
				break;
			}
			if (state == PARSE_WHEN) {
				/* it's either numeric or a pattern */

				lwsl_notice("%s: when\n", __func__);

				if (*ts.token == '.' || *ts.token == 'X') {
					uint8_t *pat;
					size_t n;

					/*
					 * pattern... we need to allocate it
					 */
					fi.type = LWSFI_PATTERN_ALLOC;
					pat = lws_zalloc((ts.token_len >> 3) + 1, __func__);
					if (!pat)
						return;
					fi.pattern = pat;
					fi.count = (uint64_t)ts.token_len;

					for (n = 0; n < ts.token_len; n++)
						if (ts.token[n] == 'X')
							pat[n >> 3] = (uint8_t)(
								pat[n >> 3] | (1 << (n & 7)));

					lwsl_hexdump_notice(pat, (ts.token_len >> 3) + 1);

					state = PARSE_ENDBR;
					break;
				}

				fi.pre = (uint64_t)atoi(ts.token);
				lwsl_notice("%s: prob %d%%\n", __func__, (int)fi.pre);
				fi.type = LWSFI_PROBABILISTIC;
				state = PARSE_PC;
				break;
			}
			break;

		case LWS_TOKZE_DELIMITER:
			if (*ts.token == ',') {
				lws_fi_add(fic, &fi);
				state = PARSE_NAME;
				break;
			}
			if (*ts.token == '(') {
				lwsl_notice("%s: (\n", __func__);
				if (state != PARSE_NAME) {
					lwsl_err("%s: misplaced (\n", __func__);
					return;
				}
				state = PARSE_WHEN;
				break;
			}
			if (*ts.token == ')') {
				if (state != PARSE_ENDBR) {
					lwsl_err("%s: misplaced )\n", __func__);
					return;
				}
				state = PARSE_NAME;
				break;
			}
			if (*ts.token == '%') {
				if (state != PARSE_PC) {
					lwsl_err("%s: misplaced %%\n", __func__);
					return;
				}
				state = PARSE_ENDBR;
				break;
			}
			break;

		case LWS_TOKZE_ENDED:
			lws_fi_add(fic, &fi);
			return;

		default:
			return;
		}
	} while (ts.e > 0);
}
