/*
 * Copyright (c) 2002-2007 Endace Technology Ltd, Hamilton, New Zealand.
 * All rights reserved.
 *
 * This source code is proprietary to Endace Technology Limited and no part
 * of it may be redistributed, published or disclosed except as outlined in
 * the written contract supplied with this product.
 *
 * $Id: cat_tests.c 15597 2012-03-28 22:21:32Z jomi.gregory $
 */

/* Documentation notes: 
 * --------------------
 *
 * The test cases and the test plan is documented in the CAT and CSBM
 * test plan. 
 *
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "../../../include/dagcrc.h"
#include "../../../include/dagclarg.h"
#include "../../../include/dag_component.h"
#include "../../../include/dag_component_codes.h"
#include "../../../include/cat_interface.h"
#include "../../../include/dagapi.h"
#include "../../../include/dagerf.h"
#include "../../../include/dag_config.h"
#include "cat_tests.h"

#define IP_ADDR_MAKE(a,b,c,d) (((a&0xff)<<24)|((b&0xff)<<16)|((c&0xff)<<8)|(d&0xff))

#define MAX_COLOUR 4096
#define MAX_HASH 16
#define MAX_CAT_ENTRIES 16384

/* Command Line Arguments constants */
enum
{
	CLA_HASH_LIST,
	CLA_IFC_LIST,
	CLA_COLOUR_LIST,
	CLA_IFC_OVERRIDE,
	CLA_READ_CAT,
	CLA_CAP_STREAM
};

/* Structure to hold the information required by the tests */
typedef struct ipf_fields
{
	uint8_t  ifc;
	uint8_t  rectype;
	uint16_t rlen;
	uint16_t wlen;
	uint16_t colour;
	uint8_t  stream;
	uint8_t  hash;
	uint16_t etype;
	uint32_t src_ip;
	uint32_t dst_ip;
	uint16_t ip_prot;
	uint16_t src_port;
	uint16_t dst_port;
	uint8_t  tcp_flags;
	uint8_t  icmp_type;
	uint8_t  icmp_code;
} ipf_fields_t;

/* Internal Function prototypes */
int parse_rec(char *rec, int len, ipf_fields_t *flds);
void incr_counters(int res);
void load_cat_table(void);

/* Global variables for this test */
char rec_msg[MAX_STR_LEN];
int rec_msg_len = 0;

global_params_t settings;

/* Global counters for final reporting */
uint32_t cat_total = 0;
uint32_t cat_ignored = 0;
uint32_t cat_failed = 0;
uint32_t cat_passed = 0;
uint32_t cat_warning = 0;
uint32_t cat_unknown = 0;

int pkt_cap[MAX_IFC_CNT];

uint16_t cat_table[MAX_CAT_ENTRIES];

// Arrays indicate that the required colour is 
uint8_t expc_ifc_lst[MAX_IFC_CNT+1]; // We can have at most 4 interfaces
uint8_t expc_clr_lst[MAX_COLOUR+1]; // We can have at most 4K colours
uint8_t expc_hash_lst[MAX_HASH+1]; // We can have at most 16 hash values

// Global indicators
int ifc_override = 0;
int read_cat = 0; 
int check_erf_type_only = 1;

// Global values of interest
int cap_stream;

/* Main initialization of the test.
 * Valid parameters are: 
 * -v v1 (Expected hash values 0 to 15)
 *       May repeat more than once, values are kept in a list
 */
int cat_tests_init(char *params[], int param_cnt, global_params_t *global_params, test_printf f)
{

	ClArgPtr clarg = NULL;
	FILE* errorfile = NULL;
	int argindex = 0;
	int clarg_result = 0;
	int code = 0;
	int ifc_val;
	int clr_val;
	int hash_val;	

	memset(pkt_cap, 0, sizeof(pkt_cap));
	memset(cat_table, 0, sizeof(cat_table));
	memset(expc_ifc_lst, 0, sizeof(expc_ifc_lst));
	memset(expc_clr_lst, 0, sizeof(expc_clr_lst));
	memset(expc_hash_lst, 0, sizeof(expc_hash_lst));

	cap_stream = global_params->cap_stream;

	/* Set up the command line options. */
	clarg = dagclarg_init(param_cnt, (const char* const *) params);
	
	/* General options. */
	dagclarg_add_int(clarg, "Define an expected interface, use -1 to ignore field", "--ifc", 'i', "n", &ifc_val, CLA_IFC_LIST);
	dagclarg_add_int(clarg, "Define an expected colour, use -1 to ignore field", "--colour", 'c', "n", &clr_val, CLA_COLOUR_LIST);
	dagclarg_add_int(clarg, "Define an expected hash, use -1 to ignore field", "--hash", 'v', "n", &hash_val, CLA_HASH_LIST);
	dagclarg_add(clarg, "Indicate we do use the interface field", "--useifc", 'u', CLA_IFC_OVERRIDE);
	dagclarg_add(clarg, "Indicate we read the CAT table from the card", "--readcat", 'r', CLA_READ_CAT);
	dagclarg_add_int(clarg, "Define which capture stream is plugin is running on", "--stream", 's', "n", &cap_stream, CLA_CAP_STREAM);

	/* Parse the command line options. */
	clarg_result = dagclarg_parse(clarg, errorfile, &argindex, &code);
	while (1 == clarg_result)
	{
		switch (code)
		{
		case CLA_HASH_LIST:
			check_erf_type_only = 0;
			if ((hash_val >= 0) && (hash_val < MAX_HASH))
				expc_hash_lst[hash_val] = 1;
			else if (hash_val == -1)
				memset(expc_hash_lst, 1, sizeof(expc_hash_lst));
			break;
			
		case CLA_IFC_LIST:
			check_erf_type_only = 0;
			if ((ifc_val >= 0) && (ifc_val < MAX_IFC_CNT))
				expc_ifc_lst[ifc_val] = 1;
			else if (ifc_val == -1)
				memset(expc_ifc_lst, 1, sizeof(expc_ifc_lst));
			break;

		case CLA_COLOUR_LIST:
			check_erf_type_only = 0;
			if ((clr_val >= 0) && (clr_val < MAX_COLOUR))
				expc_clr_lst[clr_val] = 1;
			else if (clr_val == -1)
				memset(expc_clr_lst, 1, sizeof(expc_clr_lst));
			break;

		case CLA_IFC_OVERRIDE:
			ifc_override = 1;
			break;

		case CLA_READ_CAT:
			check_erf_type_only = 0;
			read_cat = 1;
			break;

		case CLA_CAP_STREAM:
			break;

		default:
			if (params[argindex][0] == '-')
			{
				/* Unknown option. */
				dagutil_error("unknown option %s\n", params[argindex]);
				/* print_usage(clarg);*/
				return TEST_FAIL;
			}
			break;
		}
		clarg_result = dagclarg_parse(clarg, errorfile, &argindex, &code);
	}

	/* Check for errors in the arguments */
	if (-1 == clarg_result)
	{
		if (argindex < param_cnt)
		{
			dagutil_error("while processing option %s\n", params[argindex]);
		}
		dagclarg_display_usage(clarg, stderr);
		return TEST_FAIL;
	}

	memcpy(&settings, global_params, sizeof(settings));

	load_cat_table();

	return TEST_PASS;
}

/* This is the main test function.
 *
 * This function basically calls the correct function for testing
 * and increments the correct counters depending on the test result.
 *
 */
int cat_tests_run(char *rec, int len, char* lastpkt, struct_protocol_t prot, uint64_t rec_cnt)
{

	ipf_fields_t flds;
	int ret_val = TEST_PASS;
        int len_incr;
	uint16_t cmp_val;

	rec_msg_len = 0;

	/* Parse the record and verify that we have a correct record. */
	if ((ret_val = parse_rec(rec, len, &flds)) != TEST_PASS)
	{
		incr_counters(ret_val);
		return ret_val;
	}

	if (check_erf_type_only && ((flds.rectype == 19) || (flds.rectype == 20)))
	{
		len_incr = snprintf(&rec_msg[rec_msg_len], MAX_STR_LEN-rec_msg_len, "Recieived unexpected ERF Type (%d).\n", flds.rectype);
		rec_msg_len += len_incr;
		ret_val = TEST_FAIL;
	}
	else if ((flds.rectype == 19) || (flds.rectype == 20))
	{
		if (ifc_override)
			cmp_val = ((uint16_t) flds.ifc << 14) || ((uint16_t) flds.colour << 4) | ((uint16_t) flds.hash);
		else
			cmp_val = ((uint16_t) flds.colour << 4) | ((uint16_t) flds.hash);
		cmp_val = cmp_val & 0x03FFF;
		if ((cat_table[cmp_val] & (1 << (cap_stream/2))) != (1 << (cap_stream/2)))
		{
			len_incr = snprintf(&rec_msg[rec_msg_len], MAX_STR_LEN-rec_msg_len, 
					    "Packet recieved in unexpected stream (entry %d, streams 0x%x, cur. stream %d).\n", 
					    cmp_val, cat_table[cmp_val], cap_stream);
			rec_msg_len += len_incr;
			ret_val = TEST_FAIL;
		}
		
	}
	else if (check_erf_type_only && (flds.rectype != 19) && (flds.rectype != 20))
	{
		len_incr = snprintf(&rec_msg[rec_msg_len], MAX_STR_LEN-rec_msg_len, "Test not supported for ERF Type (%d).\n", flds.rectype);
		rec_msg_len += len_incr;
		ret_val = TEST_IGNORE;
	}		

	incr_counters(ret_val);

	return ret_val;
}

int cat_tests_err_msg(char *buf, int size)
{
    if (size > 0)
    {
	    strncpy(buf, rec_msg, size);
	    buf[size-1] = '\0'; /* Just in case the buffer was too short...*/
    }
	return TEST_PASS;
}

int cat_tests_final_msg(char *buf, int size)
{
	char msg[MAX_STR_LEN];

	snprintf(msg, MAX_STR_LEN, 
		 "cat_tests: pass %u\ncat_tests: fail %u\ncat_tests: warning %u\ncat_tests: ignore %u\n",
		 cat_passed, 
		 cat_failed, 
		 cat_warning, 
		 cat_ignored);

    if (size > 0)
    {
	    strncpy(buf, msg, size);
	    buf[size-1] = '\0'; /* Just in case the buffer was too short...*/
    }

	/* Test will fail if we had failuirs captures or we didn't capture any packets */
	/* Test will have wanting if we idn't fail, but we encountered wanting during the test, or we ignored packets */
	/* Otherwise, we pass the test */
	if ((cat_failed > 0) || (cat_total == 0))
		return TEST_FAIL;
	else if ((cat_warning > 0) || (cat_ignored > 0))
		return TEST_WARNING;
	else
		return TEST_PASS;
}

int cat_tests_cleanup()
{
	return TEST_PASS;
}

int cat_tests_printf (char *format, ...)
{

	printf ("%s: %s - Test printf\n", __FILE__, __FUNCTION__);

	return TEST_PASS;
}

void incr_counters(int res)
{
	switch (res)
	{
	case TEST_PASS:
		cat_passed++;
		break;

	case TEST_IGNORE:
		cat_ignored++;
		break;

	case TEST_FAIL:
		cat_failed++;
		break;
		
	case TEST_WARNING:
		cat_warning++;
		break;

	default:
		cat_unknown++;
		break;
	}
}

int  parse_rec(char *rec, int len, ipf_fields_t *flds)
{
	dag_record_t* drec = (dag_record_t*) rec;
	uint32_t tmp;
	uint16_t color;
	uint16_t stream = 0;
	uint16_t hash = 0;
	uint16_t etype;
	uint8_t  ihl;
	uint8_t *pload;
	uint8_t *tp;
	
	/* We don't handle legacy records, so we ignore them and report that... */
	if (dagerf_is_legacy_type((uint8_t*) rec))
	{
		cat_ignored++;
		return TEST_IGNORE;
	} 
	
	/* If it's not legacy record than it's modern and we continue... */
	/* We also need at least 20 Bytes to extract the ether-type */
	if (len < 20)
		return TEST_IGNORE;
	else
		len -= 20;

	switch (drec->type) {
	case ERF_TYPE_HDLC_POS:
	case ERF_TYPE_COLOR_HDLC_POS:
	case ERF_TYPE_COLOR_HASH_POS:
		if (drec->type == ERF_TYPE_COLOR_HDLC_POS)
		{
			color = ntohs(drec->lctr);
			stream = color & 0x03;
			color = color >> 2;
			tmp = ntohl(drec->rec.pos.hdlc);
			etype = (uint16_t) (tmp & 0xffff);
		} 
		else if (drec->type == ERF_TYPE_COLOR_HASH_POS)
		{
			color = ntohs(drec->lctr);
			hash = color & 0x0f;
			color = color >> 4;
			tmp = ntohl(drec->rec.pos.hdlc);
			etype = (uint16_t) (tmp & 0xffff);
		} 
		else 
		{
			tmp = ntohl(drec->rec.pos.hdlc);
			color = (uint16_t) ((tmp>>16) & 0xffff);
			stream = color & 0x03;
			color = color >> 2;
			etype = (uint16_t) (tmp & 0xffff);
		}
		flds->ifc = drec->flags.iface;
		flds->rectype = drec->type;
		flds->rlen = ntohs(drec->rlen);
		flds->wlen = ntohs(drec->wlen);
		flds->colour = color;
		flds->stream = stream;
		flds->hash = hash;
		flds->etype = etype;
		
		/* Make sure the payload points to the IP header */
		pload = drec->rec.pos.pload;

		break;
		
	case ERF_TYPE_ETH:
	case ERF_TYPE_COLOR_ETH:
	case ERF_TYPE_COLOR_HASH_ETH:
		if (drec->type == ERF_TYPE_COLOR_ETH)
		{
			color = ntohs(drec->lctr);
			stream = color & 0x03;
			color = color >> 2;
			etype = ntohs(drec->rec.eth.etype);
		} 
		else if (drec->type == ERF_TYPE_COLOR_HASH_ETH)
		{
			color = ntohs(drec->lctr);
			hash = color & 0x0f;
			color = color >> 4;
			etype = ntohs(drec->rec.eth.etype);
		} 
		else 
		{
			tp = &(drec->rec.eth.offset);
			color = ntohs(*((uint16_t*) tp));
			stream = color & 0x03;
			color = color >> 2;
			etype = ntohs(drec->rec.eth.etype);
		}
		pload = drec->rec.eth.pload - 2; /* We want to point to the VLAN label in case we're Q-in-Q */
		while (etype == 0x8100) /* Skip VLAN Tags */
		{
			pload += 4; /* Skip this VLAN */
			etype = ntohs(*pload);
		}
		pload += 2; /* Now we're 2 bytes behind, so we need to atch up */

		flds->ifc = drec->flags.iface;
		flds->rectype = drec->type;
		flds->rlen = ntohs(drec->rlen);
		flds->wlen = ntohs(drec->wlen);
		flds->colour = color;
		flds->stream = stream;
		flds->hash = hash;
		flds->etype = etype;
		break;
		
	default:
		cat_ignored++;
		return TEST_IGNORE;

	}

	/* We need at least 20 bytes for IP header */
	if (len < 20)
		return TEST_IGNORE;

	if ((etype == 0x0800) || (etype == 0x0021)) /* IP v4 */
	{
		ihl = ((uint8_t) pload[0]) & 0x0f;
		flds->ip_prot = pload[9];
		flds->src_ip = IP_ADDR_MAKE(pload[12], pload[13], pload[14], pload[15]);
		flds->dst_ip = IP_ADDR_MAKE(pload[16], pload[17], pload[18], pload[19]);
		
		len -= 4*ihl;

		pload = pload + 4*ihl;
		if (flds->ip_prot == 1) /* ICMP */
		{
			if (len < 2)
				return TEST_IGNORE;
			flds->icmp_type = pload[0];
			flds->icmp_code = pload[1];
		}
		else if (flds->ip_prot == 6)  /* TCP */
		{
			if (len < 13)
				return TEST_IGNORE;
			flds->src_port = (uint16_t) (pload[0] << 8 | pload[1]);
			flds->dst_port = (uint16_t) (pload[2] << 8 | pload[3]);
			flds->tcp_flags = pload[13];
		}
		else if (flds->ip_prot == 17)  /* UDP */
		{
			if (len < 4)
				return TEST_IGNORE;
			flds->src_port = (uint16_t) (pload[0] << 8 | pload[1]);
			flds->dst_port = (uint16_t) (pload[2] << 8 | pload[3]);
		}
	}
	
	return TEST_PASS;
}

void load_cat_table(void)
{
	int cnt;
	dag_component_t root;
	dag_component_t cat = NULL;
	dag_card_ref_t dag_ref = NULL;
	uint8_t cat_bank = 0;

	if (read_cat)
	{
		if (NULL == (dag_ref = dag_config_init(settings.dagname)))
		{
			dagutil_panic("Cannot find a the card %s. This test cannot run when checking a file.\n", settings.dagname);
		}
		if (!(root = dag_config_get_root_component(dag_ref)))
		{
			dagutil_panic("Cannot get root component.\n");
		}
		if (!(cat = dag_component_get_subcomponent(root, kComponentCAT, 0)))
		{
			dagutil_panic("Cannot find a CAT component.\n");
		}
		cat_bank = cat_get_current_bank(cat);
		// printf("Active CAT Bank is %d\n", cat_bank);
	}
	for (cnt = 0; cnt < MAX_CAT_ENTRIES; cnt++)
	{
		if (read_cat == 0)
		{
			if (ifc_override)
			{
				if (expc_ifc_lst[(cnt >> 14) & 0x03] && expc_hash_lst[(cnt & 0x0f)] && expc_clr_lst[((cnt >> 4) & 0x03ff)])
					cat_table[cnt] |= (1 << cap_stream);
			}
			else
			{
				if (expc_hash_lst[(cnt & 0x0f)] && expc_clr_lst[((cnt >> 4) & 0x0fff)])
					cat_table[cnt] |= (1 << cap_stream);
			}
		}
		else
		{
			// Now we have all we need to read the CAT
			cat_table[cnt] = cat_get_raw_entry(cat, cat_bank, cnt);

			if (cat_table[cnt] != cat_get_raw_entry(cat, cat_bank, cnt))
			{
				printf("Second read was inconsistant..\n");
			}
			// dagutil_nanosleep(100);
			// printf("CAT Entry[%d] = %d\n", cnt, cat_table[cnt]);
		}
	}
}
