/*-
 * Copyright (c) 2006 Verdens Gang AS
 * Copyright (c) 2006-2015 Varnish Software AS
 * All rights reserved.
 *
 * Author: Poul-Henning Kamp <phk@phk.freebsd.dk>
 * Author: Martin Blix Grydeland <martin@varnish-software.com>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "config.h"

#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>

#include "vdef.h"
#include "vas.h"
#include "miniobj.h"

#include "vbm.h"
#include "vqueue.h"
#include "vre.h"
#include "vsb.h"

#include "vapi/vsl.h"

#include "vsl_api.h"

/*--------------------------------------------------------------------*/

const char			vsl_file_id[] = {'V', 'S', 'L', '2'};

const char * const VSL_tags[SLT__MAX] = {
#  define SLTM(foo,flags,sdesc,ldesc)       [SLT_##foo] = #foo,
#  include "tbl/vsl_tags.h"
};

const unsigned VSL_tagflags[SLT__MAX] = {
#  define SLTM(foo, flags, sdesc, ldesc)	[SLT_##foo] = flags,
#  include "tbl/vsl_tags.h"
#  undef SLTM
};

int
vsl_diag(struct VSL_data *vsl, const char *fmt, ...)
{
	va_list ap;

	CHECK_OBJ_NOTNULL(vsl, VSL_MAGIC);
	AN(fmt);

	if (vsl->diag == NULL)
		vsl->diag = VSB_new_auto();
	AN(vsl->diag);
	VSB_clear(vsl->diag);
	va_start(ap, fmt);
	VSB_vprintf(vsl->diag, fmt, ap);
	va_end(ap);
	AZ(VSB_finish(vsl->diag));
	return (-1);
}

struct VSL_data *
VSL_New(void)
{
	struct VSL_data *vsl;

	ALLOC_OBJ(vsl, VSL_MAGIC);
	if (vsl == NULL)
		return (NULL);

	vsl->L_opt = 1000;
	vsl->T_opt = 120.;
	vsl->vbm_select = vbit_new(SLT__MAX);
	vsl->vbm_suppress = vbit_new(SLT__MAX);
	VTAILQ_INIT(&vsl->vslf_select);
	VTAILQ_INIT(&vsl->vslf_suppress);

	return (vsl);
}

static void
vsl_IX_free(vslf_list *filters)
{
	struct vslf *vslf;

	while (!VTAILQ_EMPTY(filters)) {
		vslf = VTAILQ_FIRST(filters);
		CHECK_OBJ_NOTNULL(vslf, VSLF_MAGIC);
		VTAILQ_REMOVE(filters, vslf, list);
		if (vslf->tags)
			vbit_destroy(vslf->tags);
		AN(vslf->vre);
		VRE_free(&vslf->vre);
		AZ(vslf->vre);
		FREE_OBJ(vslf);
	}
}

void
VSL_Delete(struct VSL_data *vsl)
{

	CHECK_OBJ_NOTNULL(vsl, VSL_MAGIC);

	vbit_destroy(vsl->vbm_select);
	vbit_destroy(vsl->vbm_suppress);
	vsl_IX_free(&vsl->vslf_select);
	vsl_IX_free(&vsl->vslf_suppress);
	VSL_ResetError(vsl);
	FREE_OBJ(vsl);
}

const char *
VSL_Error(const struct VSL_data *vsl)
{

	CHECK_OBJ_NOTNULL(vsl, VSL_MAGIC);

	if (vsl->diag == NULL)
		return (NULL);
	else
		return (VSB_data(vsl->diag));
}

void
VSL_ResetError(struct VSL_data *vsl)
{

	CHECK_OBJ_NOTNULL(vsl, VSL_MAGIC);

	if (vsl->diag == NULL)
		return;
	VSB_destroy(&vsl->diag);
}

static int
vsl_match_IX(struct VSL_data *vsl, const vslf_list *list,
    const struct VSL_cursor *c)
{
	enum VSL_tag_e tag;
	const char *cdata;
	int len;
	const struct vslf *vslf;

	(void)vsl;
	tag = VSL_TAG(c->rec.ptr);
	cdata = VSL_CDATA(c->rec.ptr);
	len = VSL_LEN(c->rec.ptr);

	VTAILQ_FOREACH(vslf, list, list) {
		CHECK_OBJ_NOTNULL(vslf, VSLF_MAGIC);
		if (vslf->tags != NULL && !vbit_test(vslf->tags, tag))
			continue;
		if (VRE_match(vslf->vre, cdata, len, 0, NULL) >= 0)
			return (1);
	}
	return (0);
}

int
VSL_Match(struct VSL_data *vsl, const struct VSL_cursor *c)
{
	enum VSL_tag_e tag;

	CHECK_OBJ_NOTNULL(vsl, VSL_MAGIC);
	if (c == NULL || c->rec.ptr == NULL)
		return (0);
	tag = VSL_TAG(c->rec.ptr);
	if (tag <= SLT__Bogus || tag >= SLT__Reserved)
		return (0);
	if (!vsl->c_opt || !vsl->b_opt) {
		if (vsl->c_opt && !VSL_CLIENT(c->rec.ptr))
			return (0);
		if (vsl->b_opt && !VSL_BACKEND(c->rec.ptr))
			return (0);
	}
	if (!VTAILQ_EMPTY(&vsl->vslf_select) &&
	    vsl_match_IX(vsl, &vsl->vslf_select, c))
		return (1);
	else if (vbit_test(vsl->vbm_select, tag))
		return (1);
	else if (!VTAILQ_EMPTY(&vsl->vslf_suppress) &&
	    vsl_match_IX(vsl, &vsl->vslf_suppress, c))
		return (0);
	else if (vbit_test(vsl->vbm_suppress, tag))
		return (0);

	/* Default show */
	return (1);
}

static const char * const VSL_transactions[VSL_t__MAX] = {
	/*                 12345678901234 */
	[VSL_t_unknown] = "<< Unknown  >>",
	[VSL_t_sess]	= "<< Session  >>",
	[VSL_t_req]	= "<< Request  >>",
	[VSL_t_bereq]	= "<< BeReq    >>",
	[VSL_t_raw]	= "<< Record   >>",
};

#define VSL_PRINT(...)					\
	do {						\
		if (0 > fprintf(__VA_ARGS__))		\
			return (-5);			\
	} while (0)

static int
vsl_print_unsafe(FILE *fo, unsigned len, const char *data)
{

	VSL_PRINT(fo, "\"");
	while (len-- > 0) {
		if (*data >= ' ' && *data <= '~')
			VSL_PRINT(fo, "%c", *data);
		else
			VSL_PRINT(fo, "%%%02x", (unsigned char)*data);
		data++;
	}
	VSL_PRINT(fo, "\"\n");
	return (0);
}


static int
vsl_print_binary(FILE *fo, unsigned len, const char *data)
{

	VSL_PRINT(fo, "[");
	while (len-- > 0) {
		VSL_PRINT(fo, "%02x", (unsigned char)*data);
		data++;
	}
	VSL_PRINT(fo, "]\n");
	return (0);
}

static int
vsl_print(const struct VSL_data *vsl, const struct VSL_cursor *c, void *fo,
    int terse)
{
	enum VSL_tag_e tag;
	uint64_t vxid;
	unsigned len;
	const char *data;
	int type;

	CHECK_OBJ_NOTNULL(vsl, VSL_MAGIC);
	if (c == NULL || c->rec.ptr == NULL)
		return (0);
	if (fo == NULL)
		fo = stdout;
	tag = VSL_TAG(c->rec.ptr);
	vxid = VSL_ID(c->rec.ptr);
	len = VSL_LEN(c->rec.ptr);
	type = VSL_CLIENT(c->rec.ptr) ? 'c' : VSL_BACKEND(c->rec.ptr) ?
	    'b' : '-';
	data = VSL_CDATA(c->rec.ptr);

	if (!terse)
		VSL_PRINT(fo, "%10ju ", (uintmax_t)vxid);
	VSL_PRINT(fo, "%-14s ", VSL_tags[tag]);
	if (!terse)
		VSL_PRINT(fo, "%c ", type);

	if (VSL_tagflags[tag] & SLT_F_UNSAFE)
		(void)vsl_print_unsafe(fo, len, data);
	else if (VSL_tagflags[tag] & SLT_F_BINARY)
		(void)vsl_print_binary(fo, len, data);
	else
		VSL_PRINT(fo, "%.*s\n", (int)len, data);

	return (0);
}

int
VSL_Print(const struct VSL_data *vsl, const struct VSL_cursor *c, void *fo)
{

	return (vsl_print(vsl, c, fo, 0));
}

int
VSL_PrintTerse(const struct VSL_data *vsl, const struct VSL_cursor *c, void *fo)
{

	return (vsl_print(vsl, c, fo, 1));
}

int
VSL_PrintAll(struct VSL_data *vsl, const struct VSL_cursor *c, void *fo)
{
	int i;

	if (c == NULL)
		return (0);
	while (1) {
		i = VSL_Next(c);
		if (i <= 0)
			return (i);
		if (!VSL_Match(vsl, c))
			continue;
		i = VSL_Print(vsl, c, fo);
		if (i != 0)
			return (i);
	}
}

int v_matchproto_(VSLQ_dispatch_f)
VSL_PrintTransactions(struct VSL_data *vsl, struct VSL_transaction * const pt[],
    void *fo)
{
	struct VSL_transaction *t;
	int i;
	int delim = 0;
	int verbose;

	CHECK_OBJ_NOTNULL(vsl, VSL_MAGIC);
	if (fo == NULL)
		fo = stdout;
	if (pt[0] == NULL)
		return (0);

	for (t = pt[0]; t != NULL; t = *++pt) {
		if (vsl->c_opt || vsl->b_opt) {
			switch (t->type) {
			case VSL_t_req:
				if (!vsl->c_opt)
					continue;
				if (t->reason == VSL_r_esi && !vsl->E_opt)
					continue;
				break;
			case VSL_t_bereq:
				if (!vsl->b_opt)
					continue;
				break;
			case VSL_t_raw:
				break;
			default:
				continue;
			}
		}

		verbose = 0;
		if (t->level == 0 || vsl->v_opt)
			verbose = 1;

		if (t->level) {
			/* Print header */
			if (t->level > 3)
				VSL_PRINT(fo, "*%1.1d* ", t->level);
			else
				VSL_PRINT(fo, "%-3.*s ",
				    (int)(t->level), "***");
			VSL_PRINT(fo, "%*.s%-14s %*.s%-10ju\n",
			    verbose ? 10 + 1 : 0, " ",
			    VSL_transactions[t->type],
			    verbose ? 1 + 1 : 0, " ",
			    (uintmax_t)t->vxid);
			delim = 1;
		}

		while (1) {
			/* Print records */
			i = VSL_Next(t->c);
			if (i < 0)
				return (i);
			if (i == 0)
				break;
			if (!VSL_Match(vsl, t->c))
				continue;
			if (t->level > 3)
				VSL_PRINT(fo, "-%1.1d- ", t->level);
			else if (t->level)
				VSL_PRINT(fo, "%-3.*s ",
				    (int)(t->level), "---");
			if (verbose)
				i = VSL_Print(vsl, t->c, fo);
			else
				i = VSL_PrintTerse(vsl, t->c, fo);
			if (i != 0)
				return (i);
		}
	}

	if (delim)
		VSL_PRINT(fo, "\n");;

	return (0);
}

FILE*
VSL_WriteOpen(struct VSL_data *vsl, const char *name, int append, int unbuf)
{
	FILE* f;

	if (!strcmp(name, "-"))
		f = stdout;
	else
		f = fopen(name, append ? "a" : "w");
	if (f == NULL) {
		vsl_diag(vsl, "%s", strerror(errno));
		return (NULL);
	}
	if (unbuf)
		setbuf(f, NULL);
	if (ftell(f) == 0 || f == stdout) {
		if (fwrite(VSL_FILE_ID, 1, sizeof VSL_FILE_ID, f) !=
		    sizeof VSL_FILE_ID) {
			vsl_diag(vsl, "%s", strerror(errno));
			(void)fclose(f);
			return (NULL);
		}
	}
	return (f);
}

int
VSL_Write(const struct VSL_data *vsl, const struct VSL_cursor *c, void *fo)
{
	size_t r;

	CHECK_OBJ_NOTNULL(vsl, VSL_MAGIC);
	if (c == NULL || c->rec.ptr == NULL)
		return (0);
	if (fo == NULL)
		fo = stdout;
	r = fwrite(c->rec.ptr, sizeof *c->rec.ptr,
	    VSL_NEXT(c->rec.ptr) - c->rec.ptr, fo);
	if (r == 0)
		return (-5);
	return (0);
}

int
VSL_WriteAll(struct VSL_data *vsl, const struct VSL_cursor *c, void *fo)
{
	int i;

	if (c == NULL)
		return (0);
	while (1) {
		i = VSL_Next(c);
		if (i <= 0)
			return (i);
		if (!VSL_Match(vsl, c))
			continue;
		i = VSL_Write(vsl, c, fo);
		if (i != 0)
			return (i);
	}
}

int v_matchproto_(VSLQ_dispatch_f)
VSL_WriteTransactions(struct VSL_data *vsl, struct VSL_transaction * const pt[],
    void *fo)
{
	struct VSL_transaction *t;
	int i;

	if (pt == NULL)
		return (0);
	for (i = 0, t = pt[0]; i == 0 && t != NULL; t = *++pt)
		i = VSL_WriteAll(vsl, t->c, fo);
	return (i);
}
