/*
 * 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: hlbv2_tests.c 15597 2012-03-28 22:21:32Z jomi.gregory $
 */

/* Documentation notes: 
 * --------------------
 *
 * The test cases and the test plan is documented int he IPF and HAT 
 * test plan. Each test case and test doesn't have an in code documentation.
 *
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "dagcrc.h"
#include "dagclarg.h"
#include "dagapi.h"
#include "dagerf.h"
#include "hlbv2_tests.h"

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

/* Command Line Arguments constants */
enum
{
	CLA_HASH_LIST
};

/* 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 ipf_parse_rec(char *rec, int len, ipf_fields_t *flds);
void incr_counters(int res);

/* 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 hlb_total = 0;
uint32_t hlb_ignored = 0;
uint32_t hlb_failed = 0;
uint32_t hlb_passed = 0;
uint32_t hlb_warning = 0;
uint32_t hlb_unknown = 0;

int pkt_cap[MAX_IFC_CNT];

int hlb_vals[256];

/* 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 hlbv2_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 cnt = 0;
	int h_val;

	/* Initialize the expected HLB values */
	for (cnt=0; cnt<256; cnt++)
	{
		hlb_vals[cnt] = -1;
	}
	cnt = 0;

	/* Set up the command line options. */
	clarg = dagclarg_init(param_cnt, (const char* const *) params);
	
	/* General options. */
	dagclarg_add_int(clarg, "Valid hash values", "--value", 'v', "n", &h_val, CLA_HASH_LIST);

	/* Parse the command line options. */
	clarg_result = dagclarg_parse(clarg, errorfile, &argindex, &code);
	while (1 == clarg_result)
	{
		switch (code)
		{
		case CLA_HASH_LIST:
			// printf("hlbv2_tests: -v %d\n", h_val);
			hlb_vals[cnt++] = h_val;
			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;
	}

	/* Initialize local variables */
	for (cnt=0; cnt<MAX_IFC_CNT; cnt++)
	{
		pkt_cap[cnt] = 0;
	}
	
 #if 0
	cnt = 0;
	printf("hlbv2_tests: Setting these hash values (%d):", hlb_vals[0]);
	while (hlb_vals[cnt] >= 0)
	{
		if (hlb_vals[cnt] >= 0)
			printf("%d ", hlb_vals[cnt]);
		cnt++;
	}
	printf("\n");
#endif

	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 hlbv2_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 cnt = 0;
	int found = 0;
        int len_incr;

	rec_msg_len = 0;

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

	if ((hlb_vals[0] < 0) && ((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))
	{
		while (hlb_vals[cnt] >= 0)
		{
			if (hlb_vals[cnt] == flds.hash)
				found++;
			cnt++;
		}
		if (found == 0)
		{
			len_incr = snprintf(&rec_msg[rec_msg_len], MAX_STR_LEN-rec_msg_len, "Didn't find expected HASH Value (%d).\n", flds.hash);
			rec_msg_len += len_incr;
			ret_val = TEST_FAIL;
		}
		else if (found > 1)
		{
			len_incr = snprintf(&rec_msg[rec_msg_len], MAX_STR_LEN-rec_msg_len, "HASH Value found more than once (%d).\n", flds.hash);
			rec_msg_len += len_incr;
			ret_val = TEST_WARNING;
		}
	}
	else if (hlb_vals[0] >= 0)
	{
		len_incr = snprintf(&rec_msg[rec_msg_len], MAX_STR_LEN-rec_msg_len, "HASH ERF Type expected (Received %d).\n", flds.rectype);
		rec_msg_len += len_incr;
		ret_val = TEST_IGNORE;
	}

	incr_counters(ret_val);

	return ret_val;
}

int hlbv2_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 hlbv2_tests_final_msg(char *buf, int size)
{
	char msg[MAX_STR_LEN];

	snprintf(msg, MAX_STR_LEN, 
		 "hlbv2_tests: pass %u\nhlbv2_tests: fail %u\nhlbv2_tests: warning %u\nhlbv2_tests: ignore %u\n",
		 hlb_passed, 
		 hlb_failed, 
		 hlb_warning, 
		 hlb_ignored);

    if (size > 0)
    {
	    strncpy(buf, msg, size);
	    buf[size-1] = '\0';
    }

	/* 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 ((hlb_failed > 0) || (hlb_total == 0))
		return TEST_FAIL;
	else if ((hlb_warning > 0) || (hlb_ignored > 0))
		return TEST_WARNING;
	else
		return TEST_PASS;
}

int hlbv2_tests_cleanup()
{
	return TEST_PASS;
}

int hlbv2_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:
		hlb_passed++;
		break;

	case TEST_IGNORE:
		hlb_ignored++;
		break;

	case TEST_FAIL:
		hlb_failed++;
		break;
		
	case TEST_WARNING:
		hlb_warning++;
		break;

	default:
		hlb_unknown++;
		break;
	}
}

int  ipf_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))
	{
		hlb_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:
		hlb_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;
}
