/***
 * Copyright 2020 HAProxy Technologies
 *
 * This file is part of the HAProxy OpenTracing filter.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
#include "include.h"


#define FLT_OT_GROUP_DEF(a,b,c)   { a, b, c },
const struct flt_ot_group_data flt_ot_group_data[] = { FLT_OT_GROUP_DEFINES };
#undef FLT_OT_GROUP_DEF


/***
 * NAME
 *   flt_ot_group_action -
 *
 * ARGUMENTS
 *   rule -
 *   px   -
 *   sess -
 *   s    -
 *   opts -
 *
 * DESCRIPTION
 *   This is the action_ptr callback of a rule associated to the
 *   FLT_OT_ACTION_GROUP action.
 *
 * RETURN VALUE
 *   The function returns ACT_RET_CONT if processing is finished (with error or
 *   not), otherwise, it returns ACT_RET_YIELD if the action is in progress.
 */
static enum act_return flt_ot_group_action(struct act_rule *rule, struct proxy *px, struct session *sess, struct stream *s, int opts)
{
	const struct filter                 *filter;
	const struct flt_conf               *fconf;
	const struct flt_ot_conf            *conf;
	const struct flt_ot_conf_group      *conf_group;
	const struct flt_ot_runtime_context *rt_ctx = NULL;
	const struct flt_ot_conf_ph         *ph_scope;
	char                                *err = NULL;
	int                                  i, rc;

	FLT_OT_FUNC("%p, %p, %p, %p, %d", rule, px, sess, s, opts);

	FLT_OT_DBG(3, "from: %d, arg.act %p:{ %p %p %p %p }", rule->from, &(rule->arg.act), rule->arg.act.p[0], rule->arg.act.p[1], rule->arg.act.p[2], rule->arg.act.p[3]);

	fconf      = rule->arg.act.p[FLT_OT_ARG_FLT_CONF];
	conf       = rule->arg.act.p[FLT_OT_ARG_CONF];
	conf_group = ((const struct flt_ot_conf_ph *)(rule->arg.act.p[FLT_OT_ARG_GROUP]))->ptr;

	if ((fconf == NULL) || (conf == NULL) || (conf_group == NULL)) {
		FLT_OT_LOG(LOG_ERR, FLT_OT_ACTION_GROUP ": internal error, invalid group action");

		FLT_OT_RETURN_EX(ACT_RET_CONT, enum act_return, "%d");
	}

	if (conf->tracer->flag_disabled) {
		FLT_OT_DBG(1, "filter '%s' disabled, group action '%s' ignored", conf->id, conf_group->id);

		FLT_OT_RETURN_EX(ACT_RET_CONT, enum act_return, "%d");
	}

	/* Find the OpenTracing filter instance from the current stream. */
	list_for_each_entry(filter, &(s->strm_flt.filters), list)
		if (filter->config == fconf) {
			rt_ctx = filter->ctx;

			break;
		}

	if (rt_ctx == NULL) {
		FLT_OT_DBG(1, "cannot find filter, probably not attached to the stream");

		FLT_OT_RETURN_EX(ACT_RET_CONT, enum act_return, "%d");
	}
	else if (flt_ot_is_disabled(filter FLT_OT_DBG_ARGS(, -1))) {
		FLT_OT_RETURN_EX(ACT_RET_CONT, enum act_return, "%d");
	}
	else {
		FLT_OT_DBG(3, "run group '%s'", conf_group->id);
		FLT_OT_DBG_CONF_GROUP("run group ", conf_group);
	}

	/*
	 * Check the value of rule->from; in case it is incorrect,
	 * report an error.
	 */
	for (i = 0; i < FLT_OT_TABLESIZE(flt_ot_group_data); i++)
		if (flt_ot_group_data[i].act_from == rule->from)
			break;

	if (i >= FLT_OT_TABLESIZE(flt_ot_group_data)) {
		FLT_OT_LOG(LOG_ERR, FLT_OT_ACTION_GROUP ": internal error, invalid rule->from=%d", rule->from);

		FLT_OT_RETURN_EX(ACT_RET_CONT, enum act_return, "%d");
	}

	list_for_each_entry(ph_scope, &(conf_group->ph_scopes), list) {
		rc = flt_ot_scope_run(s, rt_ctx->filter, &(s->res), ph_scope->ptr, NULL, SMP_OPT_DIR_RES, &err);
		if ((rc == FLT_OT_RET_ERROR) && (opts & ACT_OPT_FINAL)) {
			/* XXX */
		}
	}

	FLT_OT_RETURN_EX(ACT_RET_CONT, enum act_return, "%d");
}


/***
 * NAME
 *   flt_ot_group_check -
 *
 * ARGUMENTS
 *   rule -
 *   px   -
 *   err  -
 *
 * DESCRIPTION
 *   This is the check_ptr callback of a rule associated to the
 *   FLT_OT_ACTION_GROUP action.
 *
 * RETURN VALUE
 *   The function returns 1 in success case, otherwise,
 *   it returns 0 and err is filled.
 */
static int flt_ot_group_check(struct act_rule *rule, struct proxy *px, char **err)
{
	struct flt_conf       *fconf_tmp, *fconf = NULL;
	struct flt_ot_conf    *conf;
	struct flt_ot_conf_ph *ph_group;
	const char            *filter_id;
	const char            *group_id;
	bool                   flag_found = 0;
	int                    i;

	FLT_OT_FUNC("%p, %p, %p:%p", rule, px, FLT_OT_DPTR_ARGS(err));

	filter_id = rule->arg.act.p[FLT_OT_ARG_FILTER_ID];
	group_id  = rule->arg.act.p[FLT_OT_ARG_GROUP_ID];

	FLT_OT_DBG(2, "checking filter_id='%s', group_id='%s'", filter_id, group_id);

	/*
	 * Check the value of rule->from; in case it is incorrect,
	 * report an error.
	 */
	for (i = 0; i < FLT_OT_TABLESIZE(flt_ot_group_data); i++)
		if (flt_ot_group_data[i].act_from == rule->from)
			break;

	if (i >= FLT_OT_TABLESIZE(flt_ot_group_data)) {
		FLT_OT_ERR("internal error, unexpected rule->from=%d, please report this bug!", rule->from);

		FLT_OT_RETURN_INT(0);
	}

	/*
	 * Try to find the OpenTracing filter by checking all filters
	 * for the proxy <px>.
	 */
	list_for_each_entry(fconf_tmp, &(px->filter_configs), list) {
		conf = fconf_tmp->conf;

		if (fconf_tmp->id != ot_flt_id) {
			/* This is not an OpenTracing filter. */
			continue;
		}
		else if (strcmp(conf->id, filter_id) == 0) {
			/* This is the good filter ID. */
			fconf = fconf_tmp;

			break;
		}
	}

	if (fconf == NULL) {
		FLT_OT_ERR("unable to find the OpenTracing filter '%s' used by the " FLT_OT_ACTION_GROUP " '%s'", filter_id, group_id);

		FLT_OT_RETURN_INT(0);
	}

	/*
	 * Attempt to find if the group is defined in the OpenTracing filter
	 * configuration.
	 */
	list_for_each_entry(ph_group, &(conf->tracer->ph_groups), list)
		if (strcmp(ph_group->id, group_id) == 0) {
			flag_found = 1;

			break;
		}

	if (!flag_found) {
		FLT_OT_ERR("unable to find group '%s' in the OpenTracing filter '%s' configuration", group_id, filter_id);

		FLT_OT_RETURN_INT(0);
	}

	FLT_OT_FREE_CLEAR(rule->arg.act.p[FLT_OT_ARG_FILTER_ID]);
	FLT_OT_FREE_CLEAR(rule->arg.act.p[FLT_OT_ARG_GROUP_ID]);

	rule->arg.act.p[FLT_OT_ARG_FLT_CONF] = fconf;
	rule->arg.act.p[FLT_OT_ARG_CONF]     = conf;
	rule->arg.act.p[FLT_OT_ARG_GROUP]    = ph_group;

	FLT_OT_RETURN_INT(1);
}


/***
 * NAME
 *   flt_ot_group_release -
 *
 * ARGUMENTS
 *   rule -
 *
 * DESCRIPTION
 *   This is the release_ptr callback of a rule associated to the
 *   FLT_OT_ACTION_GROUP action.
 *
 * RETURN VALUE
 *   This function does not return a value.
 */
static void flt_ot_group_release(struct act_rule *rule)
{
	FLT_OT_FUNC("%p", rule);

	FLT_OT_RETURN();
}


/***
 * NAME
 *   flt_ot_group_parse -
 *
 * ARGUMENTS
 *   args    -
 *   cur_arg -
 *   px      -
 *   rule    -
 *   err     -
 *
 * DESCRIPTION
 *   -
 *
 * RETURN VALUE
 *   Returns ACT_RET_PRS_ERR if an error occurs, ACT_RET_PRS_OK otherwise.
 */
static enum act_parse_ret flt_ot_group_parse(const char **args, int *cur_arg, struct proxy *px, struct act_rule *rule, char **err)
{
	FLT_OT_FUNC("%p, %p, %p, %p, %p:%p", args, cur_arg, px, rule, FLT_OT_DPTR_ARGS(err));

	if (!FLT_OT_ARG_ISVALID(*cur_arg) ||
	    !FLT_OT_ARG_ISVALID(*cur_arg + 1) ||
	    (FLT_OT_ARG_ISVALID(*cur_arg + 2) &&
	     (strcmp(args[*cur_arg + 2], FLT_OT_CONDITION_IF) != 0) &&
	     (strcmp(args[*cur_arg + 2], FLT_OT_CONDITION_UNLESS) != 0))) {
		FLT_OT_ERR("expects: <filter-id> <group-id> [{ if | unless } ...]");

		FLT_OT_RETURN_EX(ACT_RET_PRS_ERR, enum act_parse_ret, "%d");
	}

	/* Copy the OpenTracing filter id. */
	rule->arg.act.p[FLT_OT_ARG_FILTER_ID] = FLT_OT_STRDUP(args[*cur_arg]);
	if (rule->arg.act.p[FLT_OT_ARG_FILTER_ID] == NULL) {
		FLT_OT_ERR("%s : out of memory", args[*cur_arg]);

		FLT_OT_RETURN_EX(ACT_RET_PRS_ERR, enum act_parse_ret, "%d");
	}

	/* Copy the OpenTracing group id. */
	rule->arg.act.p[FLT_OT_ARG_GROUP_ID] = FLT_OT_STRDUP(args[*cur_arg + 1]);
	if (rule->arg.act.p[FLT_OT_ARG_GROUP_ID] == NULL) {
		FLT_OT_ERR("%s : out of memory", args[*cur_arg + 1]);

		FLT_OT_FREE_CLEAR(rule->arg.act.p[FLT_OT_ARG_FILTER_ID]);

		FLT_OT_RETURN_EX(ACT_RET_PRS_ERR, enum act_parse_ret, "%d");
	}

	rule->action      = ACT_CUSTOM;
	rule->action_ptr  = flt_ot_group_action;
	rule->check_ptr   = flt_ot_group_check;
	rule->release_ptr = flt_ot_group_release;

	*cur_arg += 2;

	FLT_OT_RETURN_EX(ACT_RET_PRS_OK, enum act_parse_ret, "%d");
}


static struct action_kw_list tcp_req_action_kws = { ILH, {
		{ FLT_OT_ACTION_GROUP, flt_ot_group_parse },
		{ /* END */ },
	}
};

INITCALL1(STG_REGISTER, tcp_req_cont_keywords_register, &tcp_req_action_kws);

static struct action_kw_list tcp_res_action_kws = { ILH, {
		{ FLT_OT_ACTION_GROUP, flt_ot_group_parse },
		{ /* END */ },
	}
};

INITCALL1(STG_REGISTER, tcp_res_cont_keywords_register, &tcp_res_action_kws);

static struct action_kw_list http_req_action_kws = { ILH, {
		{ FLT_OT_ACTION_GROUP, flt_ot_group_parse },
		{ /* END */ },
	}
};

INITCALL1(STG_REGISTER, http_req_keywords_register, &http_req_action_kws);

static struct action_kw_list http_res_action_kws = { ILH, {
		{ FLT_OT_ACTION_GROUP, flt_ot_group_parse },
		{ /* END */ },
	}
};

INITCALL1(STG_REGISTER, http_res_keywords_register, &http_res_action_kws);

static struct action_kw_list http_after_res_actions_kws = { ILH, {
		{ FLT_OT_ACTION_GROUP, flt_ot_group_parse },
		{ /* END */ },
	}
};

INITCALL1(STG_REGISTER, http_after_res_keywords_register, &http_after_res_actions_kws);

/*
 * Local variables:
 *  c-indent-level: 8
 *  c-basic-offset: 8
 * End:
 *
 * vi: noexpandtab shiftwidth=8 tabstop=8
 */
