/*
 * Copyright (c) 2004-2005 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: dag_filter.c 7478 2007-10-16 03:58:07Z jomi $
 */

/* File headers. */
#include "dag_filter.h"

/* Project headers. */
#include "dagipf.h"

/* Endace headers. */
#include "dag_platform.h"
#include "dagapi.h"
#include "dagutil.h"


/* CVS Header. */
static const char* const kCvsHeader __attribute__ ((unused)) = "$Id: dag_filter.c 7478 2007-10-16 03:58:07Z jomi $";

/* Standard ERF header is 4 words. */
#define ERF_HEADER_WORDS 4

/* A standard Ethernet header is 14 bytes, but it is padded to 16 bytes (4 words). */
#define ETHERNET_HEADER_WORDS 4

/* HDLC header is 1 word. */
#define HDLC_HEADER_WORDS 1

/* ETHERTYPE_IP (0800) is defined in net/ethernet.h, but there's no definition of the IPv4 equivalent in PPP. */
#define PPP_IPv4_ID 0x0021

typedef struct FilterPrivateHeader
{
    mapping_t uResultsMapping;
    FilterStatePtr uDagipf;
    uint8_t uInputLayer2Words;   /* Size of the Layer 2 header (in 32-bit words). */
    uint8_t uOutputLayer2Words;  /* Size of the ERF and Layer 2 header (in 32-bit words). */
    linktype_t uLinkType;
} FilterPrivateHeader, *FilterPrivateHeaderPtr;

/* Internal routines. */
static int write_pbm_csr(int dagfd, uint32_t invalue, uint32_t mask); /* Return 1 on success, 0 on failure. */
static int write_source_select(int dagfd, uint8_t invalue); /* Return 1 on success, 0 on failure. */
static int write_drop_enable(int dagfd, int invalue); /* Return 1 on success, 0 on failure. */

static unsigned int dag_filter_load_cam_entries2(FilterPrivatePtr private, uint8_t port,
                                                 uint8_t ruleset, cam_entry_t** cam_array,
                                                 uint32_t count, bool verify);
static dagipf_error_t enable_search_registers(FilterPrivatePtr private, coprocessor_t cptype);
static dagipf_error_t init_result_mapping(FilterPrivatePtr private, linktype_t datalink);
static dagipf_error_t init_search_templates(FilterPrivatePtr private,linktype_t datalink);

/* Implementation of internal routines. */
static int
write_pbm_csr(int dagfd, uint32_t invalue, uint32_t mask)
{
	dag_reg_t result[DAG_REG_MAX_ENTRIES];
	volatile uint8_t* iom = dag_iom(dagfd);
	volatile uint32_t* global_csr = NULL;
	uint32_t value;

	/* Make sure there are no extraneous bits set. */
	assert(0 == (invalue & ~mask));
	invalue = invalue & mask;

	/* Can we find the PBM registers? */
	if (-1 == dag_reg_find((char*) iom, DAG_REG_PBM, result))
	{
		dagutil_error("no PCI Burst Manager found.\n");
		return 0;
	}
	
	assert(0 != result[0].addr);
	
	/* Which version of the burst manager are we using? */
	if (0 == result[0].version)
	{
		/* PBM is too old. */
		return 0;
	}
	
	global_csr = (uint32_t*) (iom + result[0].addr + 0x00); /* Global control/status register is at offset 0x00 for the MkII Burst Manager. */

	value = *global_csr;  /* read. */
	value &= ~mask;       /* zero the masked bits. */
	value |= invalue;     /* add the desired source select to the value to write. */
	*global_csr = value;  /* write back. */

	return 1;
}


static int
write_source_select(int dagfd, uint8_t invalue)
{
	uint32_t mask = BIT13 | BIT12; /* Route source select is bits 12 and 13. */
	uint32_t value;

	assert(0 == (invalue & 0xfc)); /* Only lowest two bits should be non-zero. */
	value = invalue & 0x03; /* Make sure there are no extraneous bits set. */
	value <<= 12;

	return write_pbm_csr(dagfd, value, mask);
}


static int
write_drop_enable(int dagfd, int invalue)
{
	uint32_t mask = BIT15;
	uint32_t value;

	assert(0 == (invalue & 0xfe)); /* Only lowest bit should be non-zero. */
	value = invalue & 0x01; /* Make sure there are no extraneous bits set. */
	value <<= 15;

	return write_pbm_csr(dagfd, value, mask);
}

static dagipf_error_t 
enable_search_registers(FilterPrivatePtr private, coprocessor_t cptype) {
    FilterPrivateHeaderPtr header = (FilterPrivateHeaderPtr) private;
	dagipf_error_t result;

	if (cptype == kCoprocessorSC128)
	{
		/* Set IP length filtering. */
		result = dagipf_set_ip_length_range(header->uDagipf, 0, UINT16_MAX);
		if (result != ERR_NOERR)
		{
			fprintf(stderr, "dagipf_set_ip_length_range() failed: %s\n", dagipf_strerror(result));
			return result;
		}
	
		/* Set TTL filtering. */
		result = dagipf_set_ttl_range(header->uDagipf, 0, UINT8_MAX);
		if (result != ERR_NOERR)
		{
			fprintf(stderr, "dagipf_set_ttl_range() failed: %s\n", dagipf_strerror(result));
			return result;
		}
	
		/* Enable 144 bit search register. */
		result = dagipf_enable_search_register(header->uDagipf, SEARCH_REGISTER_A);
		if (result != ERR_NOERR)
		{
			fprintf(stderr, "dagipf_enable_search_register() failed: %s\n", dagipf_strerror(result));
			return result;
		}
	
		/* Disable other search registers. */
		result = dagipf_disable_search_register(header->uDagipf, SEARCH_REGISTER_B);
		if (result != ERR_NOERR)
		{
			fprintf(stderr, "dagipf_disable_search_register() failed: %s\n", dagipf_strerror(result));
			return result;
		}
	
		result = dagipf_disable_search_register(header->uDagipf, SEARCH_REGISTER_C);
		if (result != ERR_NOERR)
		{
			fprintf(stderr, "dagipf_disable_search_register() failed: %s\n", dagipf_strerror(result));
			return result;
		}
	
		result = dagipf_disable_search_register(header->uDagipf, SEARCH_REGISTER_D);
		if (result != ERR_NOERR)
		{
			fprintf(stderr, "dagipf_disable_search_register() failed: %s\n", dagipf_strerror(result));
			return result;
		}
	
		result = dagipf_disable_search_register(header->uDagipf, SEARCH_REGISTER_E);
		if (result != ERR_NOERR)
		{
			fprintf(stderr, "dagipf_disable_search_register() failed: %s\n", dagipf_strerror(result));
			return result;
		}
	}

    return ERR_NOERR;
}


static dagipf_error_t 
init_result_mapping(FilterPrivatePtr private, linktype_t datalink) {
    FilterPrivateHeaderPtr header = (FilterPrivateHeaderPtr) private;
    result_mapping_t result_mapping;
	dagipf_error_t result;

    /******************** IPFv1 only *******************/
	/* Initialise results (output) mapping. */
	memset(&result_mapping, 0, sizeof(result_mapping));

	if (DF_MAPPING_RXERROR == header->uResultsMapping)
	{
		/* The least significant bit of the colour (bit 0 in the Results Register) 
		 * is mapped to the ERF header RX error bit (bit 4 of the second byte in the third word).
		 */
		result_mapping.bit_mapping[0].flags = RESULT_FLAG_MAPPED;
		result_mapping.bit_mapping[0].output_word = 2;
		result_mapping.bit_mapping[0].word_bit = 4 + 16;
	}
	else if ((DF_MAPPING_PADOFFSET == header->uResultsMapping) || (DF_MAPPING_PADOFFSET_STREAM0 == header->uResultsMapping))
	{
		/* The colour (bits 0 to 15 in the Results Register) 
		 * is mapped to the ERF ETH header pad and offset bytes (bytes 16 and 17 in the ERF header).
		 */
		int bit;

		assert(DF_LINK_ETHERNET == datalink);
		if (DF_LINK_ETHERNET != datalink)
		{
			fprintf(stderr, "error: kResultsMappingPadOffset can only be used with Ethernet links.\n");
			return ERR_NOT_IMPLEMENTED; /* whatever */
		}

		for (bit = 0; bit < 16; bit++)
		{
			result_mapping.bit_mapping[bit].flags = RESULT_FLAG_MAPPED;
			result_mapping.bit_mapping[bit].output_word = 4;
			result_mapping.bit_mapping[bit].word_bit = 16 + bit;
		}
	}
	else if ((DF_MAPPING_HDLC_HEADER == header->uResultsMapping) || (DF_MAPPING_HDLC_HEADER_STREAM0 == header->uResultsMapping))
	{
		/* The colour (bits 0 to 15 in the Results Register) 
		 * is mapped to the ERF HDLC header.
		 */
		int bit;

		assert((DF_LINK_POS4_CHDLC == datalink) || (DF_LINK_POS4_PPP == datalink));
		if ((DF_LINK_POS4_CHDLC != datalink) && (DF_LINK_POS4_PPP != datalink))
		{
			fprintf(stderr, "error: kResultsMappingHdlcHeader can only be used with 4-byte PoS (CHDLC or PPP) links.\n");
			return ERR_NOT_IMPLEMENTED; /* whatever */
		}

		for (bit = 0; bit < 16; bit++)
		{
			result_mapping.bit_mapping[bit].flags = RESULT_FLAG_MAPPED;
			result_mapping.bit_mapping[bit].output_word = 4;
			result_mapping.bit_mapping[bit].word_bit = 16 + bit;
		}
	}
	else if ((DF_MAPPING_COLOURED_ERF == header->uResultsMapping) || (DF_MAPPING_COLOURED_ERF_STREAM0 == header->uResultsMapping))
	{
		/* The colour (bits 0 to 15 in the Results Register) 
		 * is mapped to the ERF header loss counter/colour bytes (bytes 12 and 13).
		 */
		int bit;

		for (bit = 0; bit < 16; bit++)
		{
			result_mapping.bit_mapping[bit].flags = RESULT_FLAG_MAPPED;
			result_mapping.bit_mapping[bit].output_word = 3;
			result_mapping.bit_mapping[bit].word_bit = 16 + bit;
		}
	}
	else if (DF_MAPPING_ENTIRE_REGISTER == header->uResultsMapping)
	{
		/* The entire Results Register is mapped into the payload beginning
		 * at the 6th word (one word beyond the pad and offset bytes for ERF ETH,
		 * one word beyond the HDLC header for ERF HDLC).
		 */
		int bit;

		for (bit = 0; bit < 32; bit++)
		{
			result_mapping.bit_mapping[bit].flags = RESULT_FLAG_MAPPED;
			result_mapping.bit_mapping[bit].output_word = 5;
			result_mapping.bit_mapping[bit].word_bit = bit;
		}

		for (bit = 32; bit < 64; bit++)
		{
			result_mapping.bit_mapping[bit].flags = RESULT_FLAG_MAPPED;
			result_mapping.bit_mapping[bit].output_word = 6;
			result_mapping.bit_mapping[bit].word_bit = bit - 32;
		}

		for (bit = 64; bit < 96; bit++)
		{
			result_mapping.bit_mapping[bit].flags = RESULT_FLAG_MAPPED;
			result_mapping.bit_mapping[bit].output_word = 7;
			result_mapping.bit_mapping[bit].word_bit = bit - 64;
		}

		for (bit = 96; bit < 128; bit++)
		{
			result_mapping.bit_mapping[bit].flags = RESULT_FLAG_MAPPED;
			result_mapping.bit_mapping[bit].output_word = 8;
			result_mapping.bit_mapping[bit].word_bit = bit - 96;
		}
	}
	else if (DF_MAPPING_NONE == header->uResultsMapping)
	{
		/* No results mapping. */
	}
	else
	{
		assert(0); /* Programming error. */
	}

	result = dagipf_add_results_mapping(header->uDagipf, &result_mapping);
	if (result != ERR_NOERR)
	{
		fprintf(stderr, "dagipf_add_results_mapping() failed: %s\n", dagipf_strerror(result));
		return result;
	}

    return ERR_NOERR;
}

static dagipf_error_t 
init_search_templates(FilterPrivatePtr private, linktype_t datalink) {
    FilterPrivateHeaderPtr header = (FilterPrivateHeaderPtr) private;
	search_record144_t* search144 = NULL;
	dagipf_error_t result;
	unsigned int index;

	/* Allocate new 144 bit search. */
	search144 = dagipf_new_144bit_search(header->uDagipf, 0);
	if (!search144)
	{
		fprintf(stderr, "dagipf_new_144bit_search() returned NULL\n");
		return ERR_NOT_IMPLEMENTED; /* whatever */
	}

	/* Create template for 144 bit search.
	 *
	 * It contains (some 32-bit quantities are aligned on 36 bit boundaries for convenience):
	 *
     * 143 - 140: port & ruleset (nibble 35) [NOTE: this is set by DAG IPF when searches are added]
     * 139 - 132: TCP flags      (nibbles 33-34)
	 * 131 - 116: ethertype      (nibbles 29-32)
	 * 115 - 108: protocol       (nibbles 27-28)
	 * 103 -  72: source IP      (nibbles 18-25)
	 *  67 -  36: dest IP        (nibbles 9-16)
	 *  31 -  16: source port    (nibbles 4-7)
	 *  15 -   0: dest port      (nibbles 0-3)
	 *
	 * Note: for convenience while programming we work with Layer 3 (IP) header words.
	 * Afterwards we add the appropriate Layer 3 word offset calculated by dag_filter_configure_copro().
	 */

	/* Note: These have been thoroughly changed - Original HMA mapping was incorrect.
	 * Note that these mappings are byte-swapped.  This is because htons and htonl have been
	 * used on ip addresses and ports, and hence this is an effort to compensate for this
	 * while localising changes.
	 */

	/* Configure protocol. */
	search144->register_mapping[35-27].word = 2;
	search144->register_mapping[35-27].nibble = 3;
	search144->register_mapping[35-28].word = 2;
	search144->register_mapping[35-28].nibble = 2;

	/* Configure TCP flags byte. */
	search144->register_mapping[35-33].word = 8;
	search144->register_mapping[35-33].nibble = 3;
	search144->register_mapping[35-34].word = 8;
	search144->register_mapping[35-34].nibble = 2;

	/* Configure source IP address. */
	search144->register_mapping[35-18].word = 3;
	search144->register_mapping[35-18].nibble = 1; 
	search144->register_mapping[35-19].word = 3;
	search144->register_mapping[35-19].nibble = 0; 
	search144->register_mapping[35-20].word = 3;
	search144->register_mapping[35-20].nibble = 3;
	search144->register_mapping[35-21].word = 3;
	search144->register_mapping[35-21].nibble = 2;
	search144->register_mapping[35-22].word = 3;
	search144->register_mapping[35-22].nibble = 5;
	search144->register_mapping[35-23].word = 3;
	search144->register_mapping[35-23].nibble = 4;
	search144->register_mapping[35-24].word = 3;
	search144->register_mapping[35-24].nibble = 7;
	search144->register_mapping[35-25].word = 3;
	search144->register_mapping[35-25].nibble = 6;

	/* Configure destination IP address. */
	search144->register_mapping[35-9].word = 4;
	search144->register_mapping[35-9].nibble = 1;	
	search144->register_mapping[35-10].word = 4;
	search144->register_mapping[35-10].nibble = 0;	
	search144->register_mapping[35-11].word = 4;
	search144->register_mapping[35-11].nibble = 3;
	search144->register_mapping[35-12].word = 4;
	search144->register_mapping[35-12].nibble = 2;
	search144->register_mapping[35-13].word = 4;
	search144->register_mapping[35-13].nibble = 5;
	search144->register_mapping[35-14].word = 4;
	search144->register_mapping[35-14].nibble = 4;
	search144->register_mapping[35-15].word = 4;
	search144->register_mapping[35-15].nibble = 7;
	search144->register_mapping[35-16].word = 4;
	search144->register_mapping[35-16].nibble = 6;

	/* Configure source port. */
	search144->register_mapping[35-4].word = 5;
	search144->register_mapping[35-4].nibble = 1;
	search144->register_mapping[35-5].word = 5;
	search144->register_mapping[35-5].nibble = 0;
	search144->register_mapping[35-6].word = 5;
	search144->register_mapping[35-6].nibble = 3;
	search144->register_mapping[35-7].word = 5;
	search144->register_mapping[35-7].nibble = 2;

	/* Configure destination port. */
	search144->register_mapping[35-0].word = 5;
	search144->register_mapping[35-0].nibble = 5;
	search144->register_mapping[35-1].word = 5;
	search144->register_mapping[35-1].nibble = 4;
	search144->register_mapping[35-2].word = 5;
	search144->register_mapping[35-2].nibble = 7;
	search144->register_mapping[35-3].word = 5;
	search144->register_mapping[35-3].nibble = 6;

	/* Add in the layer 2 word offset. */
	for (index = 0; index < 36; index++)
	{
		if (search144->register_mapping[index].word != -1)
		{
			search144->register_mapping[index].word += header->uInputLayer2Words;
		}
	}

	if (DF_LINK_ETHERNET == datalink)
	{
		/* Configure ethertype (13th and 14th octets, i.e. first and second bytes in the third word). */
		search144->register_mapping[35-29].word = 3;
		search144->register_mapping[35-29].nibble = 7;
		search144->register_mapping[35-30].word = 3;
		search144->register_mapping[35-30].nibble = 6;
		search144->register_mapping[35-31].word = 3;
		search144->register_mapping[35-31].nibble = 5;
		search144->register_mapping[35-32].word = 3;
		search144->register_mapping[35-32].nibble = 4;
	}
	else if ((DF_LINK_POS4_CHDLC == datalink) || (DF_LINK_POS4_PPP == datalink))
	{
		/* Configure ethertype (3rd and 4th octets, i.e. third and fourth bytes in the first word. */
		search144->register_mapping[35-29].word = 0;
		search144->register_mapping[35-29].nibble = 7;
		search144->register_mapping[35-30].word = 0;
		search144->register_mapping[35-30].nibble = 6;
		search144->register_mapping[35-31].word = 0;
		search144->register_mapping[35-31].nibble = 5;
		search144->register_mapping[35-32].word = 0;
		search144->register_mapping[35-32].nibble = 4;
	}
	else
	{
		assert(0);
	}

	/* Add 144 bit search to copro. */
	result = dagipf_add_144bit_header_search(header->uDagipf, search144);
	if (result != ERR_NOERR)
	{
		fprintf(stderr, "dagipf_add_144bit_header_search() failed: %s\n", dagipf_strerror(result));
		return result;
	}
	/* search144 is now owned by the DAG_IPF module. */
    return result;
}

unsigned int
dag_filter_configure_copro(FilterPrivatePtr private,
                           int dagfd,
                           linktype_t datalink,
                           mapping_t mapping,
                           uint8_t ports,
                           uint8_t rulesets,
                           coprocessor_t cptype)
{
    FilterPrivateHeaderPtr header = (FilterPrivateHeaderPtr) private;
	uint32_t flags = 0;
	dagipf_error_t result;
	uint8_t source_select = 0;
    int version;

	assert(header->uDagipf == NULL);

	/* Set header sizes depending on link type. */
	if (DF_LINK_ETHERNET == datalink)
	{ 
		header->uInputLayer2Words = ETHERNET_HEADER_WORDS;
		header->uOutputLayer2Words = ERF_HEADER_WORDS + ETHERNET_HEADER_WORDS;
	}
	else if ((DF_LINK_POS4_CHDLC == datalink) || (DF_LINK_POS4_PPP == datalink))
	{
		header->uInputLayer2Words = HDLC_HEADER_WORDS;
		header->uOutputLayer2Words = ERF_HEADER_WORDS + HDLC_HEADER_WORDS;
	}
	else
	{
		fprintf(stderr, "Ethernet and 4-byte PoS (CHDLC or PPP) are the only supported datalink types.\n");
		assert(0); /* Caller didn't check the datalink to see it was suitable. */
		return 0;
	}
	
	/* Save linktype for later. */
	header->uLinkType = datalink;
	
	/* Sanity-check the user-supplied mapping. 
	 * Once the mapping has passed this check we assume it's trustworthy.
	 */
	switch (mapping)
	{
		case DF_MAPPING_RXERROR:
			break;
	
		case DF_MAPPING_PADOFFSET:
		case DF_MAPPING_PADOFFSET_STREAM0:
			assert(DF_LINK_ETHERNET == header->uLinkType);
			break;
	
		case DF_MAPPING_COLOURED_ERF:
		case DF_MAPPING_COLOURED_ERF_STREAM0:
			break;
	
		case DF_MAPPING_ENTIRE_REGISTER:
			break;
	
		case DF_MAPPING_HDLC_HEADER:
		case DF_MAPPING_HDLC_HEADER_STREAM0:
			assert((DF_LINK_POS4_CHDLC == datalink) || (DF_LINK_POS4_PPP == datalink));
			break;
	
		default:
			fprintf(stderr, "Unknown results mapping type.\n");
			assert(0); /* Caller didn't check the results mapping to see it was suitable. */
			return 0;
	}
	header->uResultsMapping = mapping;

	/* Set up the coprocessor for Snort usage. */
	/* flags = DAGIPF_FLAG_NOHARDWARE; */
	header->uDagipf = dagipf_init(dagfd, flags, cptype);
	if (!header->uDagipf)
	{
		fprintf(stderr, "dagipf_init() returned NULL\n");
		return 0;
	}

    version = dagipf_get_version(header->uDagipf);
    if (0 == version) {
        /* initialization steps for IPFv1  */
        result = enable_search_registers(private, cptype);
        if (result != ERR_NOERR)
        {
            goto fail;
        }
        result = init_result_mapping(private, datalink);
        if (result != ERR_NOERR)
        {
            goto fail;
        }
        result = init_search_templates(private, datalink);
        if (result != ERR_NOERR)
        {
            goto fail;
        }
   } else if (2 == version) {
        /* initialization steps for IPFv2*/
        /* IPFv2 has hardwired search/mapping, only a few things needs
         * to be set */

        dagipf_reset_control(header->uDagipf);
        dagipf_enable_drop(header->uDagipf, 1);
        
        if (DF_LINK_ETHERNET == datalink) {
            dagipf_set_linktype_eth(header->uDagipf, 1);
        }

        switch (mapping) {
        case DF_MAPPING_COLOURED_ERF:
        case DF_MAPPING_COLOURED_ERF_STREAM0: /* fall thru */
            dagipf_set_color_lctr(header->uDagipf, 1);
            break;
        case DF_MAPPING_PADOFFSET:
        case DF_MAPPING_PADOFFSET_STREAM0: /* fall thru */
            dagipf_set_color_lctr(header->uDagipf, 0);
            break;
        case DF_MAPPING_RXERROR:
            dagipf_set_rx_error(header->uDagipf, 1);
            break;
        default:
            assert(0);
        }
    }
    
    /* Set up the ports and rulesets. */
    result = dagipf_configure_rulesets(header->uDagipf, ports, rulesets);
    if (result != ERR_NOERR)
    {
        fprintf(stderr, "dagipf_configure_rulesets() failed: %s\n", dagipf_strerror(result));
        goto fail;
    }

	/* Set the PCI Burst Manager route source select. */
    switch(header->uResultsMapping) {
	case DF_MAPPING_RXERROR: /* fall thru */ 
	case DF_MAPPING_PADOFFSET_STREAM0: /* fall thru */
	case DF_MAPPING_HDLC_HEADER_STREAM0: /* fall thru */
	case DF_MAPPING_COLOURED_ERF_STREAM0: /* fall thru */
	case DF_MAPPING_ENTIRE_REGISTER: /* fall thru */
    case DF_MAPPING_NONE: 
		source_select = 0x00;
        break;
	case DF_MAPPING_PADOFFSET: /* fall thru */
	case DF_MAPPING_HDLC_HEADER:
		source_select = 0x02;
        break;
	case DF_MAPPING_COLOURED_ERF:
		source_select = 0x01;
        break;
    default:
        assert(0);
	}

 	/* Write the source select to the PBM. */
	if (0 == write_source_select(dagfd, source_select))
	{
		dagutil_panic("failed to write source select bit\n");
	}
	
	/* Disable the PBM drop by default. */
	if (0 == write_drop_enable(dagfd, 0))
	{
		dagutil_panic("failed to write drop enable bit\n");
	}

	/* Load state into the coprocessor and start processing. */
	if (cptype == kCoprocessorSC128)
	{
		result = dagipf_activate(header->uDagipf);
		if (result != ERR_NOERR)
		{
			fprintf(stderr, "dagipf_activate() failed: %s\n", dagipf_strerror(result));
			goto fail;
		}
	}
	else if ((cptype == kCoprocessorSC256) ||
             (cptype == kCoprocessorSC256C) ||
             (cptype == kCoprocessorBuiltin))
	{
		result = dagipf_activate256(header->uDagipf);
		if (result != ERR_NOERR)
		{
			fprintf(stderr, "dagipf_activate256() failed: %s\n", dagipf_strerror(result));
			goto fail;
		}
        if (cptype == kCoprocessorBuiltin) {
            dagipf_copro_enable(header->uDagipf);
        }
	}
	return 1;

fail:
	assert(0); /* FIXME: Leave this here as an attention-getting device until sure that failures aren't due to programming errors. */
	dagipf_dispose(header->uDagipf);
	header->uDagipf = NULL;
	return 0;
}


unsigned int
dag_filter_connect_to_copro(FilterPrivatePtr private, int dagfd, linktype_t datalink, coprocessor_t cptype)
{
    FilterPrivateHeaderPtr header = (FilterPrivateHeaderPtr) private;

	/* State should be uninitialised at this stage. */
	assert(header->uDagipf == NULL);
	assert(header->uInputLayer2Words == 0);

	if (header->uDagipf == NULL)
	{
		uint32_t flags = 0;

		/* Must assume that the copro is already configured. 
		 * This should "connect" without disrupting the coprocessor's operation.
		 */
		/* flags = DAGIPF_FLAG_NOHARDWARE; */

		header->uDagipf = dagipf_init(dagfd, flags, cptype);
		if (!header->uDagipf)
		{
			fprintf(stderr, "dagipf_init() returned NULL\n");
			return 0;
		}
	}

	/* Set header sizes depending on link type. */
	if (DF_LINK_ETHERNET == datalink)
	{ 
		header->uInputLayer2Words = ETHERNET_HEADER_WORDS;
		header->uOutputLayer2Words = ERF_HEADER_WORDS + ETHERNET_HEADER_WORDS;
	}
	else if ((DF_LINK_POS4_CHDLC == datalink) || (DF_LINK_POS4_PPP == datalink))
	{
		header->uInputLayer2Words = HDLC_HEADER_WORDS;
		header->uOutputLayer2Words = ERF_HEADER_WORDS + HDLC_HEADER_WORDS;
	}
	else
	{
		fprintf(stderr, "Ethernet and 4-byte PoS (CHDLC or PPP) are the only supported datalink types.\n");
		assert(0); /* Caller didn't check the datalink to see it was suitable. */
		return 0;
	}
	
	/* Save linktype for later. */
	header->uLinkType = datalink;

	return 1;
}


unsigned int
dag_filter_load_cam_entries(FilterPrivatePtr private,
                            uint8_t port,
                            uint8_t ruleset,
                            cam_entry_t** cam_array,
                            uint32_t count,
                            bool verify)
{
	pattern144_t* searches = NULL;
	uint32_t ethertype = 0;
	dagipf_error_t result;
	unsigned int index;
    FilterPrivateHeaderPtr header = (FilterPrivateHeaderPtr) private;

	assert(header->uDagipf != NULL);
	assert(header->uInputLayer2Words != 0);
	assert(header->uLinkType != DF_LINK_UNKNOWN);

    if (0 != dagipf_get_version(header->uDagipf)) {
        /* use the new CAM format for IPFv2 */
        return dag_filter_load_cam_entries2(private, port, ruleset, cam_array, count, verify);
    }

	searches = (pattern144_t*) malloc(count * sizeof(pattern144_t));
	if (searches == NULL)
	{
		return 0;
	}

	memset(searches, 0, count * sizeof(pattern144_t));

	/* Add search values to 144 bit search. */
	if ((DF_LINK_ETHERNET == header->uLinkType) || (DF_LINK_POS4_CHDLC == header->uLinkType))
	{
		ethertype = (((uint32_t) ETHERTYPE_IP) << 8) & 0x00ffff00;
	}
	else if (DF_LINK_POS4_PPP == header->uLinkType)
	{
		ethertype = (((uint32_t) PPP_IPv4_ID) << 8) & 0x00ffff00;
	}
	else
	{
		assert(0); /* Programming error: linktype wasn't correctly set. */
		return 0;
	}

	for (index = 0; index < count; index++)
	{
		cam_entry_t* cam_entry = cam_array[index];
		uint32_t value = (((uint32_t) cam_entry->src_port.port) << 16) | cam_entry->dest_port.port;
		uint32_t mask = (((uint32_t) cam_entry->src_port.mask) << 16) | cam_entry->dest_port.mask;

		/* Add ports. */
		searches[index].value[3] = value;
		searches[index].mask[3] = mask;

		/* Note: As part of the HMA fix, the source and destination address placement
		 * within the CAM entry have been modified to be consistent.
		 */
		
		/* Add destination IP address. */
		searches[index].value[2] = (uint32_t) cam_entry->dest.ip4_addr;
		searches[index].mask[2] = cam_entry->dest.mask;

		/* Add source IP address. */
		searches[index].value[1] = (uint32_t) cam_entry->source.ip4_addr;
		searches[index].mask[1] = cam_entry->source.mask;

		/* Add TCP flags byte, protocol, ethertype. */
		value = (((uint32_t) cam_entry->tcp_flags.flags) << 24) & 0xff000000;
		searches[index].value[0] = value | ethertype | (cam_entry->protocol.protocol & 0x000000ff);

		/* If the protocol is a default value (marked with IPPROTO_RAW), then turn the
		 * ethertype and protocol fields into "don't care"s.  This is to allow default
		 * rules to apply to non-ip packets. 
		 * If the protocol is IP, then turn the protocol field into a "don't care".
		 */
		if (0 == cam_entry->protocol.mask)
		{
			/* Match every frame. */
			searches[index].mask[0] = 0x000000;
		}
		else if (cam_entry->protocol.protocol == IPPROTO_IP)
		{
			/* Require ETHERTYPE_IP but allow all IP protocols. */
			searches[index].mask[0] = 0xffff00;
		}
		else
		{
			/* Require ETHERTYPE_IP and IP protocol. */
			searches[index].mask[0] = 0xffffff;
		}
		searches[index].mask[0] |= (((uint32_t) cam_entry->tcp_flags.mask) << 24);

		/* Add colour. */
		searches[index].colour = cam_entry->colour;

		/* Add snap length. */
		if (cam_entry->snaplength)
		{
			searches[index].snaplength = cam_entry->snaplength;
		}
		else
		{
			searches[index].snaplength = 0xffff;
		}

		/* Add action. */
		searches[index].action = cam_entry->action;
	}

	/* Load searches into the coprocessor. */
	result = dagipf_add_144bit_searches(header->uDagipf, port, ruleset, count, searches, verify);
	if (result != ERR_NOERR)
	{
		fprintf(stderr, "dagipf_add_144bit_searches() failed: %s\n", dagipf_strerror(result));
		free(searches);
		return 0;
	}

    free(searches);
	return 1;
}

/**
 * Convert the cam_entry_t data into pattern144_t, using the new CAM format
 */
static unsigned int
dag_filter_load_cam_entries2(FilterPrivatePtr private,
                             uint8_t port,
                             uint8_t ruleset,
                             cam_entry_t** cam_array,
                             uint32_t count,
                             bool verify)
{
    FilterPrivateHeaderPtr header = (FilterPrivateHeaderPtr) private;
	pattern144_t* searches = NULL;
	dagipf_error_t result;
	unsigned int index;

	assert(header->uDagipf != NULL);
	assert(header->uInputLayer2Words != 0);
	assert(header->uLinkType != DF_LINK_UNKNOWN);

	searches = (pattern144_t*) malloc(count * sizeof(pattern144_t));
	if (searches == NULL)
	{
		return 0;
	}

	memset(searches, 0, count * sizeof(pattern144_t));

	for (index = 0; index < count; index++)
	{
		cam_entry_t* cam_entry = cam_array[index];

        /* filling up the 4 x 36 bit data - to keep compatibility with
         * existing dagipf functions */

        /* 31:0 bits of 64 bits */
        searches[index].value[3] = htonl(cam_entry->source.ip4_addr);
        searches[index].mask[3] = htonl(cam_entry->source.mask);
        /* 35:32 bits of 64 bits */
        searches[index].value[3] |= (htonl(cam_entry->dest.ip4_addr) & 0x0fLL) << 32;
        searches[index].mask[3] |= (htonl(cam_entry->dest.mask) & 0x0fLL) << 32;

        /* 27:0 bits of 64 bits */
        searches[index].value[2] = htonl(cam_entry->dest.ip4_addr) >> 4;
        searches[index].mask[2] = htonl(cam_entry->dest.mask) >> 4;
        /* 35:28 bits of 64 bits */
        searches[index].value[2] |= (htons(cam_entry->src_port.port) & 0xffLL) << 28;
        searches[index].mask[2] |= (htons(cam_entry->src_port.mask) & 0xffLL) << 28;

        /* 7:0 bits of 64 bits */
        searches[index].value[1] = (htons(cam_entry->src_port.port) & 0xff00LL) >> 8;
        searches[index].mask[1] = (htons(cam_entry->src_port.mask) & 0xff00LL) >> 8;
        /* 23:8 bits of 64 bits */
        searches[index].value[1] |= (htons(cam_entry->dest_port.port) & 0xffffLL) << 8;
        searches[index].mask[1] |= (htons(cam_entry->dest_port.mask) & 0xffffLL) << 8;
        /* 31:24 bits of 64 bits */
        searches[index].value[1] |= (cam_entry->protocol.protocol & 0xffLL) << 24;
        searches[index].mask[1] |= (cam_entry->protocol.mask & 0xffLL) << 24;
        /* 35:32 bits of 64 bits */
        searches[index].value[1] |= (cam_entry->tcp_flags.flags & 0x0fLL) << 32;
        searches[index].mask[1] |= (cam_entry->tcp_flags.mask & 0x0fLL) << 32;

        /* 1:0 of 64 bits */
        searches[index].value[0] = (cam_entry->tcp_flags.flags & 0x30LL) >> 4;
        searches[index].mask[0] = (cam_entry->tcp_flags.mask & 0x30LL) >> 4;

        /* 17:2 of 64 bits */
        searches[index].value[0] |= htons((cam_entry->l2_protocol.protocol & 0xffffLL)) << 2;
        searches[index].mask[0] |= htons((cam_entry->l2_protocol.mask & 0xffffLL)) << 2;

	
        /* 35:34 of 64 bits */
        searches[index].mask[0] |= 0x03LL << 34; 
        searches[index].value[0] |= (ruleset & 1LL) << 34;
        searches[index].value[0] |= (port & 1LL) << 35;

        /* further tweaking, as seen in dagfilter_load_cam_entries */
		if (0 == cam_entry->protocol.mask)
		{
			/* Match every frame: don't care for tcp_flags, protocol */
            searches[index].mask[1] &= 0x000ffffffLL;
            searches[index].mask[0] &= 0xffffffffcLL;
		}
		else if (cam_entry->protocol.protocol == IPPROTO_IP)
		{
			/* allow all IP protocols. */
            searches[index].mask[1] &= ~0xffLL << 24;
		}

        /************ from here: same as in dagfilter_load_cam_entries *************/

		/* Add colour. */
		searches[index].colour = cam_entry->colour;

		/* Add snap length. */
		if (cam_entry->snaplength)
		{
			searches[index].snaplength = cam_entry->snaplength;
		}
		else
		{
			searches[index].snaplength = 0xffff;
		}

		/* Add action. */
		searches[index].action = cam_entry->action;
	}

	/* Load searches into the coprocessor. */
	result = dagipf_add_144bit_searches(header->uDagipf, port, ruleset, count, searches, verify);
	if (result != ERR_NOERR)
	{
		fprintf(stderr, "dagipf_add_144bit_searches() failed: %s\n", dagipf_strerror(result));
		free(searches);
		return 0;
	}

	return 1;
}


unsigned int
dag_filter_get_port_count(FilterPrivatePtr private)
{
    FilterPrivateHeaderPtr header = (FilterPrivateHeaderPtr) private;
	dagipf_error_t result;
	uint8_t count = 0;

	assert(header->uDagipf != NULL);

	result = dagipf_get_port_count(header->uDagipf, &count);
	if (result != ERR_NOERR)
	{
		fprintf(stderr, "dagipf_get_port_count() failed: %s\n", dagipf_strerror(result));
		assert(0);
		return 0;
	}

	return count;
}


unsigned int
dag_filter_get_port_ruleset_count(FilterPrivatePtr private, uint8_t port)
{
    FilterPrivateHeaderPtr header = (FilterPrivateHeaderPtr) private;
	dagipf_error_t result;
	uint8_t count = 0;

	assert(header->uDagipf != NULL);

	result = dagipf_get_port_ruleset_count(header->uDagipf, port, &count);
	if (result != ERR_NOERR)
	{
		fprintf(stderr, "dagipf_get_port_ruleset_count() failed: %s\n", dagipf_strerror(result));
		assert(0);
		return 0;
	}

	return count;
}


unsigned int
dag_filter_get_port_ruleset(FilterPrivatePtr private, uint8_t port, uint8_t* ruleset)
{
    FilterPrivateHeaderPtr header = (FilterPrivateHeaderPtr) private;
	dagipf_error_t result;

	assert(header->uDagipf != NULL);

	result = dagipf_get_port_ruleset(header->uDagipf, port, ruleset);
	if (result != ERR_NOERR)
	{
		fprintf(stderr, "dagipf_get_port_ruleset() failed: %s\n", dagipf_strerror(result));
		assert(0);
		return 0;
	}

	return 1;
}


unsigned int
dag_filter_set_port_ruleset(FilterPrivatePtr private, uint8_t port, uint8_t ruleset)
{
	 FilterPrivateHeaderPtr header = (FilterPrivateHeaderPtr) private;
     dagipf_error_t result;

	assert(header->uDagipf != NULL);

	result = dagipf_set_port_ruleset(header->uDagipf, port, ruleset);
	if (result != ERR_NOERR)
	{
		fprintf(stderr, "dagipf_set_port_ruleset() failed: %s\n", dagipf_strerror(result));
		assert(0);
		return 0;
	}

	return 1;
}


unsigned int
dag_filter_set_drop_enable(FilterPrivatePtr private, int dagfd, int enabled)
{
	assert(((FilterPrivateHeaderPtr)private)->uDagipf != NULL);
	assert(enabled <= 1);

    return write_drop_enable(dagfd, enabled);
}


FilterPrivatePtr dag_filter_init() {
    FilterPrivateHeaderPtr header;
    header = (FilterPrivatePtr)calloc(1, sizeof(FilterPrivateHeader));

    header->uResultsMapping = DF_MAPPING_UNKNOWN;
    header->uLinkType = DF_LINK_UNKNOWN;

    return (FilterPrivatePtr)header;
}

void dag_filter_dispose(FilterPrivatePtr private) {
    
    dagipf_dispose(((FilterPrivateHeaderPtr)private)->uDagipf);
    free(private);
    private = NULL;
}
