/*
 * Copyright (c) 2002-2006 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: erfio.c 13350 2010-10-27 21:12:14Z sfd $
 *
 * Write ERF records in ERF format.
 */

/* dagconvert headers. */
#include "inputs.h"
#include "outputs.h"
#include "utils.h"
#include "dagcrc.h"
#define ETHER_HEADER_SIZE 16

/*
 * array to hold the input file names.
 */
extern char gInpArray[MAXCNT][100];

/*
 * array to hold the input files' pointers.
 */
extern FILE *gInputFiles[MAXCNT];

/*
 * pointer to hold the list of first dag record from each file.
 */
extern void *gInpRecord;

/*
 * counter variable to hold number of input files.
 */
extern int gFileCount;


/*
 * Output stream.
 */
static FILE * uOutfile = NULL;

/*
 * Input stream.
 */
static FILE * uInfile = NULL;
static char * uInfileName = NULL;

/*
 * Reusable memory for dag records.
 */
static dag_record_t * uHead;

/*
 * Next payload.
 */
static uint8_t * uPayload = NULL;

static int uSnapLength = NO_SNAP_LIMIT;

static int uRecordAlignment = 4;

/*
 * Variable or fixed length output?
 */
#define STATUS_QUO -1
static int uVariableLength = STATUS_QUO;

/*
 * Remember if warning about truncation has been printed.
 */
static int uDoneTruncationWarning = 0;

/*
 * Remember if warning about ATM snapping has been printed.
 */
static int uDoneAtmWarning = 0;

/*
 * Bytes written to file so far, and file rotation sequence.
 */
static uint64_t uBytesWritten = 0;
static unsigned uRotationNumber = 0;

/*
 * Save output file name for later use.
 */
static char *uOutfileName = NULL;
/*
 * Save current out file name (created frm orig name+ rotation number etc) for later use.
 */
static char uCurrentOutfileName[TEMP_FILENAME_BUFSIZE];

/*
 * Used for padding.
 */
static const char ZERO[64] = {0};

/*
 * Set the stream on which output is to be produced.
 */
void
set_erf_output(char *out)
{
#if defined(__linux__) || defined(__FreeBSD__) || (defined(__SVR4) && defined(__sun)) || defined(__APPLE__)
	int outfd;
#endif
	char *oname;

	uOutfileName = out;

	dagutil_verbose_level(2, "DEBUG: set_erf_output %s\n", out);

	if (out == NULL)
		uOutfile = stdout;
	else 
	{
		if (gRotateSize) 
		{
            unsigned int oname_len = 0;
            uint8_t use_temp_name = 0;
            oname_len = strlen(out)+45; /* using  a new var to hold the len */
			oname = calloc(1, oname_len);
			if(gFileNumber > 0) {
				if( uRotationNumber > gFileNumber ) 
				{
					uRotationNumber = 0;
				}
			};
            if ( gUseTempOutputFiles && (oname_len < (TEMP_FILENAME_BUFSIZE-1)) )
            {
                use_temp_name = 1;
            }
			snprintf(oname, oname_len, "%s%04u.erf%s", out, uRotationNumber,(use_temp_name?"~":""));
			dagutil_verbose("Set output file to %s\n", oname);
            /* copy the name to global variable*/
            strncpy(uCurrentOutfileName, oname, TEMP_FILENAME_BUFSIZE);
#if defined(__linux__) || defined(__FreeBSD__) || (defined(__SVR4) && defined(__sun)) || defined(__APPLE__)
			outfd = open(oname, O_RDWR|O_CREAT|O_TRUNC|O_LARGEFILE, 0664);
			if (outfd == -1) 
			{
				dagutil_panic("Could not open %s for writing.\n", out);
			}
			uOutfile = fdopen(outfd, "w");
#elif defined (_WIN32)
			uOutfile = fopen(oname, "wb");
#endif
			dagutil_free(oname);
			uBytesWritten = 0;
		}
		else
		{
#if defined(__linux__) || defined(__FreeBSD__) || (defined(__SVR4) && defined(__sun)) || defined(__APPLE__)
			outfd = open(out, O_RDWR|O_CREAT|O_TRUNC|O_LARGEFILE, 0664);
			if (outfd == -1)
			{
				dagutil_panic("Could not open %s for writing.\n", out);
			}
			uOutfile = fdopen(outfd, "w");
#elif defined(_WIN32)
			uOutfile = fopen(out, "wb");
#endif
		}
		
		if (uOutfile == NULL)
		{
			dagutil_panic("Could not open %s for writing.\n", out);
		}
	}
}

/*
 * Find the IP header inside of ethernet packet
 */
void *
find_ip_header(void *payload, int* eth_hdr_len)
{
	int ethertype;
	int offset;
	int pad;
	uint8_t* temp_pload_ptr;

	ethertype = ntohs(((eth_rec_t*)payload)->etype);
	offset = ntohs(((eth_rec_t*)payload)->offset);
	pad = ntohs(((eth_rec_t*)payload)->offset);
//	printf("ethertype:%x, offset:%x, pad:%x\n", ethertype, offset, pad);

	temp_pload_ptr = ((eth_rec_t*)(payload))->pload;

	if((ethertype == 0x8100) || (ethertype == 0x88a8)) 
	{
		uint16_t* overlay16;
		int inter_type;
		do {
			overlay16 = (uint16_t*) temp_pload_ptr;
			inter_type = ntohs(overlay16[1]);
			/* update ethernet header length */
			*eth_hdr_len += 4;		
			
			if ((inter_type == 0x0800) || (inter_type == 0x86dd))
			{
				/* Found IP header */
				return (uint8_t*) &overlay16[2];
			}
			/* Skip over VLAN tags */
			temp_pload_ptr = (uint8_t*) &overlay16[2];

		} while((inter_type == 0x8100) || (inter_type == 0x88a8));
				
	} 
	else if((ethertype == 0x8848) || (ethertype == 0x8847))
	{
		uint32_t* overlay32 = (uint32_t*) temp_pload_ptr;
		uint32_t  mpls_value;
		uint32_t  mpls_index = 0;
		
		/* Skip over MPLS header */
		do {
			mpls_value = ntohl(overlay32[mpls_index]);
			mpls_index ++;
			*eth_hdr_len += 4;

		}while((mpls_value & 0x00000100) == 0);
		/* Found IP header */
		return (uint8_t*) &overlay32[mpls_index];					
	}
	else
	{
		return (uint8_t*) ((eth_rec_t*)payload + 16);
	}

	return NULL;
}

/*
 * Converts the existing ERF type to the desired ERF type
 */
void
format_erf(dag_record_t* header, void *payload, uint8_t outerftype, void **newpayload)
{
	uint32_t hdlc = 0, acc = 0x00;
	int pad = 0, eth_ext_hdr_len = 0;
	uint16_t rlen = 0, wlen = 0, ploadlen = 0;
	void *ethpload = NULL, *pospload = NULL, *ipv4pload = NULL;
	uint32_t *crc_addr;
	char dest_mac[6] = {0x00, 0x0e, 0xa7, 0x00, 0x00, 0x00};
	char src_mac[6] = {0x00, 0x0e, 0xa7, 0x00, 0x00, 0x00};

	switch(outerftype)
	{
		case ERF_TYPE_HDLC_POS:
			if (dagerf_is_ethernet_type((uint8_t*) header))
			{
				hdlc = 0x0f000000 | ntohs(((eth_rec_t *)payload)->etype);
				
				rlen = ntohs(header->rlen);
				ploadlen = rlen - dag_record_size - 16;
				pospload = malloc(ploadlen);
				ethpload = ((eth_rec_t *)(payload))->pload;
				memcpy(pospload, ethpload, ploadlen);
				memcpy(((pos_rec_t *)(payload))->pload, pospload, ploadlen);
				((pos_rec_t *)payload)->hdlc = htonl(hdlc);
				
				header->type = ERF_TYPE_HDLC_POS;
				header->rlen = htons(rlen - 12);
				header->wlen = htons(ntohs(header->wlen) - 12);
				free(pospload);
			}
			break;

		case ERF_TYPE_IPV4:
		case ERF_TYPE_IPV6:
  			if (dagerf_is_ethernet_type((uint8_t*) header))
                        {
				rlen = ntohs(header->rlen);
				wlen = ntohs(header->wlen);
				/* calculate the size of pad */
				pad = (rlen - wlen) - 18;
				
				/* Handle cases where rlen is smaller than wlen, and rlen 
				 * is smaller than the size of ERF ethernet header plus 
				 * ethernet header plus IP header 
				 */
				if((rlen < wlen) && (rlen < 72))
					return;
				
				ethpload = find_ip_header(payload, &eth_ext_hdr_len);
				/* 18 - ERF ethernet header, 14 - Ethernet header*/
				if (pad > 0)
                                	ploadlen = rlen - 18 - 14 - pad - eth_ext_hdr_len;
				else
					ploadlen = rlen - 18 - 14 - eth_ext_hdr_len;
				
                                ipv4pload = malloc(ploadlen);
                                memcpy(ipv4pload, ethpload, ploadlen);
				/* ploadlen - 4 : take off the checksum in end of ethernet packet*/
                		memcpy((uint8_t*) payload, ipv4pload, ploadlen - 4);
                                
                                if(outerftype == ERF_TYPE_IPV4)
					header->type = ERF_TYPE_IPV4;
				else 
					header->type = ERF_TYPE_IPV6;

                                header->rlen = htons(ploadlen + dag_record_size - 4);
                                header->wlen = htons(ploadlen - 4);
                                free(ipv4pload);
                    	}
			break;
		case ERF_TYPE_ETH:
			if (dagerf_is_ip_type((uint8_t*)header))
			{
                               	rlen = ntohs(header->rlen);
				wlen = ntohs(header->wlen);
				pad = (rlen - wlen) - 16;

				if(pad > 0)
					ploadlen = rlen - 16 - pad;
				else
					ploadlen = rlen - 16;

                                /* IPv4 payload length */
                                ethpload = malloc(ploadlen + 20);

                                /* Construct fake ethernet header*/
                                ((eth_rec_t *)(ethpload))->offset = 0;
                                ((eth_rec_t *)(ethpload))->pad = 0;
								
                                
				
				/* Init MAC address */
                                memcpy(((eth_rec_t *)(ethpload))->dst, dest_mac, 6);
                                memcpy(((eth_rec_t *)(ethpload))->src, src_mac, 6);
				if (header->type == ERF_TYPE_IPV4)
                                	((eth_rec_t *)(ethpload))->etype = htons(0x0800);
				else
                                	((eth_rec_t *)(ethpload))->etype = htons(0x86dd);

				/* Copy the IP payload to ethernet payload*/
                                memcpy(((eth_rec_t *)(ethpload))->pload, payload, ploadlen);

				header->type = ERF_TYPE_ETH;
				/* 18 - ERF ethernet header, 14 -ethernet header, 4 - ethernet CRC*/
                                header->rlen = htons(ploadlen + 36);
                                header->wlen = htons(wlen + 18);
				
				/* Calculate ethernet 4 bytes CRC and append to end of ERF ethernet packets */
				dagcrc_make_ethernet_table_r();
                                acc = dagcrc_ethernet_crc32_r(0x0UL, ((eth_rec_t *)(ethpload))->dst, ntohs(header->wlen) - 4);
				crc_addr = (uint32_t*)(((eth_rec_t *)(ethpload))->dst+(ntohs(header->wlen) - 4));
				*crc_addr = acc;
                                
				*newpayload = ethpload;
			}
			break;
	}

}

/*
 * Close the currently open input (if any).
 */
void
close_erf_output(void)
{
	if (uOutfile && uOutfile != stdout)
    {
        fclose(uOutfile);
        if ( 0 != change_output_file_extn(uCurrentOutfileName, ".erf~", ".erf") )
        {
            dagutil_warning("Failed to convert erf from erf~\n");
        }
    }
	
	uOutfile = NULL;
}

/*
 * Set a snap length to apply to packets. Packets
 * longer than this value are truncated to the specified
 * value. To allow arbitrary length packets, set
 * uSnapLength to -1. If varlen is nonzero then packets
 * can be of variable length, otherwise all packets
 * will have the specified length.
 */
void
set_erf_snap_length(int snap, int varlen)
{
#ifdef PEDANTIC
	if ((snap < -1) || ((snap == NO_SNAP_LIMIT) && (varlen == 0)))
	{
		dagutil_panic("Bad arguments to set_erf_snap_length.\n");
	}
	else if ((varlen == STATUS_QUO) && (snap != NO_SNAP_LIMIT))
	{
		dagutil_panic("Must specify one of -F or -V to use this mode.\n");
	}
#endif

	/*
	 * make the snap length a multiple of four
	 */
	if (snap == NO_SNAP_LIMIT)
		uSnapLength = NO_SNAP_LIMIT;
	else
		uSnapLength = snap & ~0x3;

	if (uSnapLength != snap)
		dagutil_warning("snap length set to %d.\n", uSnapLength);
	
	uVariableLength = varlen;

	/*
	 * reset warnings
	 */
	uDoneTruncationWarning = 0;
	uDoneAtmWarning = 0;
}

void
set_erf_record_alignment(int alignment)
{
	uRecordAlignment = alignment;
}

/*
 * Write given ERF header and payload in ERF format.
 * Returns 1 if record successfully written and 0
 * otherwise.
 */
int
write_erf_record(dag_record_t * header, void *payload)
{
	int rlen;
	int wlen; /* record length, wire length */
	int payload_length; /* everything except ERF header */
	int data_length; /* payload_length less alignment */
	
	int alignment = 0; /* correction factor */
	int padding = 0; /* number of bytes of pad to add */
	int plen = 0; /* padding added */
	
#ifdef PEDANTIC
	if (header == NULL) {
		dagutil_warning("Null record passed to write_erf_record\n");
		return 0;
	}
#endif

	/* check output is valid */
	if (uOutfile== NULL)
	{
		set_erf_output(uOutfileName);
	}
	
	rlen = ntohs(header->rlen);
	wlen = ntohs(header->wlen);
	data_length = payload_length = rlen - dag_record_size;

	/*
	 * for Ethernet packets there are 4 extra bytes not
	 * counted in the wlen field
	 */
	if ((header->type == ERF_TYPE_ETH) || (header->type == ERF_TYPE_COLOR_ETH) || (header->type == ERF_TYPE_DSM_COLOR_ETH))
		alignment = 2;
	data_length -= alignment;

	/* complain about attempts to change ATM packets */
	if (!uDoneAtmWarning && header->type == ERF_TYPE_ATM && uSnapLength != NO_SNAP_LIMIT && uSnapLength != 52)
	{
		dagutil_warning("Attempt to alter snap length of ATM traffic.\n");
		uDoneAtmWarning = 1;
	}
#ifdef PEDANTIC
	if (rlen < dag_record_size)
	{
		dagutil_warning("Corruption of rlen field.\n");
		return 0;
	}
	
	if (payload == NULL && payload_length != 0)
	{
		dagutil_warning("Null payload when a payload was expected.\n");
		return 0;
	}
#endif

	if (uVariableLength != STATUS_QUO)
	{
		/*
		 * some modification of packet characteristics has been
		 * requested. The change could be going from fixed to
		 * variable, fixed to fixed, variable to fixed, or
		 * variable to variable, each with a change in snap
		 * length.
		 */

		if (uVariableLength == 0)
		{
			/*
			 * handle the situation where the caller has
			 * requested fixed length output. This usually
			 * means that some packets will need to be truncated
			 * and others padded to the correct length.
			 */

			header->flags.vlen = 0;
			header->rlen = htons(uSnapLength + dag_record_size + alignment);

			if (data_length > uSnapLength)
			{
				payload_length = uSnapLength + alignment;
			}
			else if (data_length < uSnapLength)
			{
				/* 
				 * need to pad the original data, but spit 
				 * out a warning if this occurs on previously
				 * truncated data, this warning is only printed
				 * once
				 */
				if (!uDoneTruncationWarning && wlen > data_length) {
					dagutil_warning
					    ("Padding previously truncated packets.\n");
					uDoneTruncationWarning = 1;
				}
				padding = uSnapLength - data_length;
			}
		}
		else
		{
			/*
			 * Variable length output is to be produced.
			 * Depending on whether a snap length was
			 * specified some packets may have to be truncated.
			 * Also, snaps made with fixed length should have any
			 * padding removed.
			 */
			int was_variable = header->flags.vlen;

			header->flags.vlen = 1;

			/*
			 * if the current packet is fixed length, then we
			 * need to determine how much, if any, is padding.
			 * Any padding can then be neglected.
			 */
			if (!was_variable && wlen < data_length)
			{
				data_length = wlen;
				payload_length = data_length + alignment;
			}
			/*
			 * handle the situation where the requested snap
			 * length is less than the current packet length.
			 * This will nearly always involve truncation.
			 */
			if ((uSnapLength != NO_SNAP_LIMIT) && (data_length > uSnapLength))
			{
				header->rlen = htons(uSnapLength + dag_record_size + alignment);
				payload_length = uSnapLength + alignment;
			}
		}
	}
	
	if (uRecordAlignment > 1)
	{
		int rem = (dag_record_size + payload_length + padding) % uRecordAlignment;
		int extrapad = rem > 0 ? (uRecordAlignment - rem) : 0;

		header->rlen = htons(ntohs(header->rlen)+extrapad);
		padding += extrapad;
	}
	
	/* Modify interface bits. */
	if (-1 != gErfOutputIface)
	{
		header->flags.iface = gErfOutputIface;
	}

	/* write header, return 0 if write fails */
	if (fwrite(header, dag_record_size, 1, uOutfile) != 1)
	{
		return 0;
	}

	/* write payload, return 0 if write fails */
	if (payload_length && fwrite(payload, payload_length, 1, uOutfile) != 1)
	{
		return 0;
	}
   
	/* write zero padding if required */
	while (padding>  0)
	{
		plen = padding<sizeof(ZERO)?padding:sizeof(ZERO);
		if (fwrite(ZERO, 1, plen, uOutfile) != plen)
		{
			return 0;
		}
		padding -= plen;
		uBytesWritten += plen;
	}

	if (gRotateSize)
	{
		uBytesWritten += (dag_record_size + payload_length);
		if (uBytesWritten >= gRotateSize)
		{
			dagutil_verbose_level(2, "DEBUG: about to rotate\n");
			uRotationNumber++;
			uBytesWritten = 0;
			close_erf_output();
		}
	}
	return 1;
}

/*
 * Set the stream from which input is obtained. If "in" is
 * null, then input is set to stdin.
 */
void
set_erf_input(char *in)
{
	uInfileName = in;

	if (in == NULL)
	{
		uInfile = stdin;
	}
	else
	{

#if defined(__linux__) || defined(__FreeBSD__) || (defined(__SVR4) && defined(__sun)) || defined(__APPLE__)

		int fd = open(in, O_RDONLY|O_LARGEFILE);
		if (fd == -1)
		{
			dagutil_panic("Could not open %s for reading.\n", in);
		}
		uInfile = fdopen(fd, "r");

#elif defined(_WIN32)

		uInfile = fopen(in, "rb");

#endif /* Platform-specific code. */

		if (uInfile == NULL)
		{
			dagutil_panic("Could not open %s for reading.\n", in);
		}
	}
}

/*
 * Close the current input stream.
 */
void
close_erf_input(void)
{
	if (uInfile && uInfile != stdin)
		fclose(uInfile);
	uInfile = NULL;

	if (uPayload)
		dagutil_free(uPayload);
	uPayload = NULL;
}

/*
 * Get pointer to the ERF header for the next packet
 * in the input stream. Returns null if no further
 * packets are available.
 */

dag_record_t *
get_next_erf_header(void)
{
	static int get[MAXCNT];
	static int earliest = -1;
	
	int i;
	int rlen;
	dag_record_t *rec = NULL;

	uHead = NULL;

	if (earliest == -1)
		memset(get, 1, sizeof(int)*MAXCNT);
	
	for (i = 0; i < gFileCount; i++)
	{
		rec = (dag_record_t*)gInpRecord+i;
		if (get[i])
		{
			if (gInputFiles[i] != NULL)
			{
				do{
					if (fread(rec, dag_record_size, 1, gInputFiles[i]) != 1)
					{
						if (feof(gInputFiles[i]))
						{
							get[i] = 0;
							fclose(gInputFiles[i]);
							gInputFiles[i] = NULL;
							break;
						}
						else if (ferror(gInputFiles[i]))
						{
							dagutil_panic("error reading the file %s\n", gInpArray[i]);
						}
						
					}
					else 
					{
						get[i] = 0;
					}
					/* if the pad record is longer than 16 bytes we also have to skip over the 
						rest of the record here */
					if(rec->type == TYPE_PAD) {
						rlen = ntohs(rec->rlen);
						fseek(gInputFiles[i], rlen - dag_record_size, SEEK_CUR);
					}
				}
				while(rec->type == TYPE_PAD);
			}
		} 
	
	}


	uHead = gInpRecord;
	earliest = 0;

	for (i = 0; i < gFileCount; i++)
	{
		rec = (dag_record_t*)gInpRecord + i;
		if (gInputFiles[earliest] != NULL)
		{
			if (gInputFiles[i] != NULL)
			{
				if (uHead->ts > rec->ts)
				{
					uHead = rec;
					earliest = i;
				}
			}
		}
		else
		{
			uHead = rec;
			earliest = i;
		}
	}

	get[earliest] = 1;

	if (gInputFiles[earliest] == NULL)
		return NULL;
	
	rlen = ntohs(uHead->rlen);
	if (rlen == 20)
	{
		/* DS error truncates the packet, but the packet has effectively been padded to 28 bytes by the card. */
		rlen = 28;
	}
	rlen -= dag_record_size;

	/* allocate memory for and read the payload */
	if (rlen == 0)
	{
		/*
		* In this case there is no payload, but we still want
		* to return a non-null pointer. Therefore, if payload
		* is not yet allocated, get a single byte of memory.
		*/
		if (uPayload == NULL)
		{
			uPayload = dagutil_malloc(1);
		}
	} 
	else
	{
		/*
		* In the usual case, simply resize the payload buffer
		* to be large enough for the data
		*/
		uPayload = realloc(uPayload, rlen);
		
		if (uPayload == NULL)
		{
			dagutil_panic("Could not get memory for payload. (rlen=%d)\n", rlen);
		}

		if (gInputFiles[earliest] != NULL)
		{
			if (fread(uPayload, 1, rlen, gInputFiles[earliest]) != rlen)
			{
				if (feof(gInputFiles[earliest]))
				{
					get[earliest] = 0;
					fclose(gInputFiles[earliest]);
					gInputFiles[earliest] = NULL;
					return NULL;
				}
				else if (ferror(gInputFiles[earliest]))
				{
					dagutil_warning("Problem with payload.\n");
					dagutil_panic("error reading the file %s\n", gInpArray[i]);
				}
			}
		}
	}
	
	return uHead;
}


/*
 * Returns a pointer to the payload of the most recent
 * packet. Returns null if there is no current packet.
 */
void*
get_erf_payload(void)
{
	return uPayload;
}
