/*
 * 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: pcapio.c 12591 2010-03-19 01:56:03Z sfd $
 *
 * Read and write PCAP records
 */

/* dagconvert headers. */
#include "inputs.h"
#include "outputs.h"
#include "utils.h"

/* libpcap headers. */
#include "pcap.h"

/*
 * Filename for output, use "-" or NULL for stdout 
 */
static char * uOutfileName;

pcap_dumper_t **dumper; /*beware*/

/*
 * Input stream 
 */
static FILE * uInfile = NULL;

/*
 * Array of pointers to dumpers
 */
static mc_dumper_t * mc_dumper_table = NULL;

static pcap_dumper_t *  uDumper = NULL;

/*
 * pointer to hold first legacy uRecord from each file
 */
extern void * gInpRecord;

/*
 * Reuseable memory for pcap uRecord 
 */
pcap_record_t * uPcapRecord;

/*
 * Snap length to use 
 */
static int uSnapLength = NO_SNAP_LIMIT;

/*
 * Next payload. 
 */
#if !defined(_WIN32)
static void * uPayload = NULL;
#else /* _WIN32 */
static uint8_t * uPayload = NULL;
#endif /* WIN32 */

/*
 * Bytes written to file so far, and file rotation sequence
 */
static uint64_t uBytesWritten = 0;
static uint32_t uRotationNumber = 0;
static char uCurrentOutfileName[TEMP_FILENAME_BUFSIZE];

/*
 * array to hold the pcap packet capture descriptor's pointer
 */
extern pcap_t *gPcapDescriptors[MAXCNT];

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

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

/*
 * Reusable memory for records 
 */
static pcap_record_t * uHead;
static pcap_record_t uTempPcapRecord;
static dag_record_t uDagRecord;

static void get_pcap_hdr(u_char *user, const struct pcap_pkthdr *phdr, const u_char *pdata);

static uint8_t *raw_record = NULL;

/*
 * Prepare the output stream for writing pcap style records.
 */
pcap_dumper_t*
prepare_pcap_output(dag_record_t * header, void *payload, char *out)
{
	int snapshot = 0;
	pcap_t *p;
	pcap_dumper_t *dumper;

	dagutil_verbose("Opening output file: %s\n", out);

	/* Select pcap parameters based on link type. */
	get_dlt(header, &gOutputDataLinkType, &snapshot);

	/* override length with default for variable length */
	if (header->flags.vlen)
		snapshot = DEFAULT_SNAPLEN;

	/* override default length with requested length */
	if (uSnapLength != NO_SNAP_LIMIT)
	{
#ifdef PEDANTIC
		if ((header->type == ERF_TYPE_ATM) && (uSnapLength != ATM_SNAPLEN))
			dagutil_warning("Attempt to alter snap length of ATM traffic.\n");
#endif
		snapshot = uSnapLength;
	}

	p = pcap_open_dead(gOutputDataLinkType, snapshot);
	if (p == NULL)
	{
		return NULL;
	}

	dumper = pcap_dump_open(p, out);
	
	pcap_close(p);

	return dumper;
}

/*
 * Set the stream on which output is to be produced.
 */
void
set_pcap_output(char *out)
{
	/*
	 * cannot establish output stream until such time
	 * as we know what the type of data is
	 */
	uOutfileName = out;
}

char* get_pcap_output(void) {
	return uOutfileName;
}

/*
 * Manually set the snap length. If this routine is not
 * called, then a value is assumed based on the length
 * of the first packet saved.
 */
void
set_pcap_snap_length(int snap)
{
	uSnapLength = snap;
}

/*
 * Close the dumper
 */
void
close_pcap_output() {
	if(*dumper){			
		pcap_dump_close(*dumper);
		*dumper = NULL;
        if ( 0 != change_output_file_extn(uCurrentOutfileName, ".pcap~", ".pcap") )
        {
            dagutil_warning("Failed to convert erf from pcap~\n");
        }
	}
}


void
create_pcap_record(dag_record_t * header, uint8_t *payload)
{
	register uint64_t ts;
	struct sunatm_hdr *sunatm;
	uint32_t rawatm;
	char temp_packet[MAX_PACKET];

#ifdef PEDANTIC
	if (header == NULL)
	{
		dagutil_warning("NULL header passed to create_pcap_record\n");
		return 0;
	}
	if (payload == NULL)
	{
		dagutil_warning("NULL payload passed to create_pcap_record\n");
		return 0;
	}
#endif

	/*
	 * convert between timestamp formats, this used to
	 * swap in situ, but now makes local copy
	 */
	ts = swapll(header->ts);
	
	uTempPcapRecord.pkthdr.ts.tv_sec = (long)(ts >> 32);
	ts = ((ts & 0xffffffffULL) * 1000 * 1000);
	ts += (ts & 0x80000000ULL) << 1; /* rounding */
	uTempPcapRecord.pkthdr.ts.tv_usec = (long)(ts >> 32);
	if (uTempPcapRecord.pkthdr.ts.tv_usec >= 1000000)
	{
		uTempPcapRecord.pkthdr.ts.tv_usec -= 1000000;
		uTempPcapRecord.pkthdr.ts.tv_sec += 1;
	}

	/* dump packet according to type */
	switch (header->type & 0x7f)
	{
	case ERF_TYPE_ATM:
		uTempPcapRecord.pkthdr.caplen = get_atm_snap_length(header);
		if (uTempPcapRecord.pkthdr.caplen > uSnapLength)
			uTempPcapRecord.pkthdr.caplen = uSnapLength;
		uTempPcapRecord.pkthdr.len = get_atm_snap_length(header);
		/* skip ATM header */
		uTempPcapRecord.pkt = payload + 4;
		break;

        case ERF_TYPE_MC_ATM:
		uTempPcapRecord.pkthdr.caplen = get_atm_snap_length(header);
		if (uTempPcapRecord.pkthdr.caplen > uSnapLength)
			uTempPcapRecord.pkthdr.caplen = uSnapLength;
		uTempPcapRecord.pkthdr.len = get_atm_snap_length(header);
		/* skip multichannel header and ATM header */
		uTempPcapRecord.pkt = payload + 8;
		break;
		
	case ERF_TYPE_AAL5:
		uTempPcapRecord.pkthdr.caplen = get_aal5_snap_length(header);
		if (uTempPcapRecord.pkthdr.caplen > uSnapLength)
			uTempPcapRecord.pkthdr.caplen = uSnapLength;
		uTempPcapRecord.pkthdr.len = get_erf_wire_length(header);
		/* skip ATM header */
		uTempPcapRecord.pkt = payload + 4;
		break;

	case ERF_TYPE_MC_AAL5:
	case ERF_TYPE_AAL2:
	case ERF_TYPE_MC_AAL2:
		uTempPcapRecord.pkthdr.caplen = get_aal5_snap_length(header);
		if (uTempPcapRecord.pkthdr.caplen > uSnapLength)
			uTempPcapRecord.pkthdr.caplen = uSnapLength;
		uTempPcapRecord.pkthdr.len = get_erf_wire_length(header);
		/* skip MC (or AAL2 ext) header and ATM header */
		uTempPcapRecord.pkt = payload + 8;
		break;
		
	case ERF_TYPE_ETH:
	case ERF_TYPE_COLOR_ETH:
	case ERF_TYPE_DSM_COLOR_ETH:
		uTempPcapRecord.pkthdr.caplen = get_ethernet_snap_length(header);
		if (uTempPcapRecord.pkthdr.caplen > uSnapLength)
			uTempPcapRecord.pkthdr.caplen = uSnapLength;
		uTempPcapRecord.pkthdr.len = get_erf_wire_length(header);
		/* skip offset and pad bytes from Ethernet capture */
		uTempPcapRecord.pkt = payload + 2;
		break;
		
	case ERF_TYPE_HDLC_POS:
	case ERF_TYPE_COLOR_HDLC_POS:
	case ERF_TYPE_DSM_COLOR_HDLC_POS:
		uTempPcapRecord.pkthdr.caplen = get_hdlc_snap_length(header);
		if (uTempPcapRecord.pkthdr.caplen > uSnapLength)
			uTempPcapRecord.pkthdr.caplen = uSnapLength;
		uTempPcapRecord.pkthdr.len = get_erf_wire_length(header);
		/* write complete payload */
		uTempPcapRecord.pkt = payload;
		break;

        case ERF_TYPE_MC_HDLC:
        case ERF_TYPE_COLOR_MC_HDLC_POS:
        case ERF_TYPE_MC_RAW:
        case ERF_TYPE_MC_RAW_CHANNEL:
		uTempPcapRecord.pkthdr.caplen = get_hdlc_snap_length(header);
		if (uTempPcapRecord.pkthdr.caplen > uSnapLength)
			uTempPcapRecord.pkthdr.caplen = uSnapLength;
		uTempPcapRecord.pkthdr.len = get_erf_wire_length(header);
		/* skip MC header */
		uTempPcapRecord.pkt = payload + 4;
		break;

#if defined(DLT_ERF)
	case ERF_TYPE_RAW_LINK:
		uTempPcapRecord.pkthdr.caplen = get_raw_snap_length(header);
		if (uTempPcapRecord.pkthdr.caplen > uSnapLength)
			uTempPcapRecord.pkthdr.caplen = uSnapLength;

		raw_record = realloc (raw_record, uTempPcapRecord.pkthdr.caplen);
		if (raw_record == NULL)
		{
			dagutil_panic("Insufficient memory for raw record\n");
			return;
		}

		uTempPcapRecord.pkthdr.len = get_erf_wire_length(header) + dag_record_size + 8; // Add 8 bytes for the extension header

		memcpy(raw_record, header, dag_record_size); // Copy the ERF header
		memcpy(raw_record + dag_record_size, payload, uTempPcapRecord.pkthdr.len - dag_record_size);	// Copy the ERF extension header and raw frame contained in payload

		/* from the start of the ERF record */
		uTempPcapRecord.pkt = raw_record;
		break;
#endif

	case TYPE_PAD:
		return;
	case ERF_TYPE_IPV4:
	case ERF_TYPE_IPV6:
		uTempPcapRecord.pkthdr.caplen = get_ipv4_snap_length(header);
		if (uTempPcapRecord.pkthdr.caplen > uSnapLength)
                        uTempPcapRecord.pkthdr.caplen = uSnapLength;
                uTempPcapRecord.pkthdr.len = get_erf_wire_length(header);
                uTempPcapRecord.pkt = payload;
		
		break;

	default:
		dagutil_panic("unknown ERF Record type %d\n", header->type);
	}
	
	/* DLT specific processing */
	switch(gOutputDataLinkType) {
	case DLT_SUNATM:
		/* Must create 4-byte pseudo-header based on ATM header */

		/* SUNATM data is longer by 4 bytes */
		uTempPcapRecord.pkthdr.caplen += 4;
		/* Rewind data pointer by 4 buyes to find ERF ATM header */
		uTempPcapRecord.pkt = (uint8_t *)uTempPcapRecord.pkt - 4;

		if (uTempPcapRecord.pkthdr.caplen > MAX_PACKET)
			dagutil_panic("Packet too large (%d > %d) in %s line %d",
			      uTempPcapRecord.pkthdr.caplen,
			      MAX_PACKET,
			      __FILE__,
			      __LINE__);

		/* copy packet data to temp buffer to create SUNATM pseudo header */
		memcpy(temp_packet, uTempPcapRecord.pkt, uTempPcapRecord.pkthdr.caplen);
		sunatm = (struct sunatm_hdr *)temp_packet;
					
		rawatm = (uint32_t)ntohl(*((unsigned long *)temp_packet));
		sunatm->vci = htons((rawatm >>  4) & 0xffff);
		sunatm->vpi = (rawatm >> 20) & 0x00ff;
		sunatm->flags = ((header->flags.iface & 1) ? 0x80 : 0x00) | 
			((sunatm->vpi == 0 && sunatm->vci == htons(5)) ? 6 :
			 ((sunatm->vpi == 0 && sunatm->vci == htons(16)) ? 5 : 
			  ((payload[ATM_HDR_SIZE] == 0xaa &&
			    payload[ATM_HDR_SIZE+1] == 0xaa &&
			    payload[ATM_HDR_SIZE+2] == 0x03) ? PT_LLC : PT_LANE)));

		uTempPcapRecord.pkt = temp_packet;
		break;
		
	}

	gPcapRecord = &uTempPcapRecord;

}

/*
 * Write given PCAP header and payload in PCAP format.
 * Returns 1 if uDagRecord successfully written and 0
 * otherwise.
 */
int
#if !defined(_WIN32)
write_pcap_record(dag_record_t * header, void *payload)
#else /* _WIN32 */
write_pcap_record(dag_record_t * header, char *payload)
#endif /* WIN32 */
{
	uint64_t * written; 
	unsigned * rotated;
    char    *cur_name = NULL;
	int channel;
	char *oname, *chext, *rotext;
    unsigned int oname_len = 0;

#ifdef PEDANTIC
	if (header == NULL)
	{
		dagutil_warning("NULL header passed to write_pcap_record\n");
		return 0;
	}
	if (payload == NULL)
	{
		dagutil_warning("NULL payload passed to write_pcap_record\n");
		return 0;
	}
#endif

#define CH_EXT_SIZE 5
#define ROT_EXT_SIZE 15
    oname_len = strlen(uOutfileName == NULL ? "-" : uOutfileName) + CH_EXT_SIZE + ROT_EXT_SIZE + 1;
	oname = calloc(sizeof(char), oname_len);
	chext = calloc(sizeof(char), CH_EXT_SIZE + 1);
	rotext = calloc(sizeof(char), ROT_EXT_SIZE + 1);
	if (!oname || !chext || !rotext) {
		dagutil_panic("Could not allocate memory in %s at %d\n",
			      __FILE__, __LINE__);
	}

	strcat(oname, uOutfileName == NULL ? "-" : uOutfileName  );

	channel = get_mc_channel(header, payload);
	if (0 <= channel) { 
		if (uOutfileName) {
			snprintf(chext, CH_EXT_SIZE + 1, ".%04u", channel);
		}
		dumper = &(mc_dumper_table[channel].dumper);
		written = &(mc_dumper_table[channel].written);
		rotated = &(mc_dumper_table[channel].rotated);
        cur_name = mc_dumper_table[channel].mc_file_name;
	} else {
		/* not multichannel */
		dumper = &uDumper;
		written = &uBytesWritten;
		rotated = &uRotationNumber;
        cur_name = uCurrentOutfileName;

	}

	if (NULL == *dumper) { /* got closed due to file rotation */
        uint8_t use_temp_name = 0;
        if ( gUseTempOutputFiles && (oname_len < (TEMP_FILENAME_BUFSIZE-1)) )
        {
            use_temp_name = 1;
        }
		if (gRotateSize) {
			if(gFileNumber > 0) {
				if( *rotated > gFileNumber ) 
				{
					uRotationNumber = 0;
				}
			};
			
			snprintf(rotext, ROT_EXT_SIZE + 1, ".%04u.pcap%s", (*rotated), ((use_temp_name)?"~":""));
		}

		strncat(oname, chext, CH_EXT_SIZE + 1);
		strncat(oname, rotext, ROT_EXT_SIZE + 1);

		(*written) += sizeof(struct pcap_file_header);
        
		/* initiate pcap output with appropriate type */
		*dumper = prepare_pcap_output(header, payload, oname);
		if (NULL == *dumper)
			dagutil_panic("Could not open %s for writing.\n", oname);
        strncpy(cur_name, oname , TEMP_FILENAME_BUFSIZE);
	}

	dagutil_free(oname);
	dagutil_free(chext);
	dagutil_free(rotext);

	if (gPcapRecord == NULL)
		create_pcap_record(header, payload);

	if (gPcapRecord != NULL)
		/* dump pcap record to file */
		pcap_dump((u_char*)(*dumper), &(gPcapRecord->pkthdr), gPcapRecord->pkt);

	if ((gPcapRecord != NULL) && gRotateSize)
	{
		/* XXX opaque sizeof(struct pcap_sf_pkthdr) = 16 today */
		*written  += (gPcapRecord->pkthdr.caplen + 16);
		if (*written >= (uint64_t)gRotateSize)
		{
			(*rotated)++;
			*written = 0;
			pcap_dump_close((*dumper));
			*dumper = NULL;
            if ( 0 != change_output_file_extn(cur_name, ".pcap~", ".pcap") )
            {
                dagutil_warning("Failed to convert erf from pcap~\n");
            }
		}
	}

	return 1;
}

/*
 * Set the stream from which input is obtained.
 */
void
set_pcap_input(char *in)
{
	int fd;

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

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

	uInfile = NULL;

	if (raw_record)
		dagutil_free(raw_record);
	raw_record = 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_pcap_header(void)
{
	static int get[MAXCNT];
	static int earliest = -1;
	
	int i;
	int npkts = 2;
	uint64_t rec_ts;
	uint64_t head_ts;

	uHead = NULL;
	
	if (earliest == -1)
		memset(get, 1, sizeof(int)*MAXCNT);

	for (i = 0; i < gFileCount; i++)
	{
		uPcapRecord = ((pcap_record_t*)gInpRecord)+i;
		if (get[i])
		{
			if (gPcapDescriptors[i] != NULL)
			{
				if (uPcapRecord->pkt != NULL)
				{
					dagutil_free(uPcapRecord->pkt);
					uPcapRecord->pkt = NULL;
				}
				npkts = pcap_dispatch(gPcapDescriptors[i], (int) 1, get_pcap_hdr, (u_char*)gPcapDescriptors[i]);
				if (npkts != 1)
				{
					if (0 == npkts)
					{
						get[i] = 0;
						pcap_close(gPcapDescriptors[i]);
						gPcapDescriptors[i] = NULL;
					}
					else if (npkts < 1)
					{
						dagutil_panic("error: %s\n", pcap_geterr(gPcapDescriptors[i]));
					}
					else
					{
						dagutil_panic("Unknown error occured while retrieving the pcap records \n");
					}
				}
				else
				{
					get[i] = 0;
				}
			}
		}
	}

	uHead = (pcap_record_t *)gInpRecord;
	earliest = 0;
	head_ts = ((uint64_t) (uHead->pkthdr.ts.tv_sec) << 32) + (((uint64_t)uHead->pkthdr.ts.tv_usec<<32) / 1000 / 1000); 
	
	memset(&uDagRecord, 0, sizeof(dag_record_t));

	for (i = 0; i < gFileCount; i++)
	{
		uPcapRecord = (pcap_record_t *)gInpRecord + i;
		rec_ts = ((uint64_t) (uPcapRecord->pkthdr.ts.tv_sec) << 32) + (((uint64_t)uPcapRecord->pkthdr.ts.tv_usec<<32) / 1000 / 1000); 

		if (gPcapDescriptors[earliest])
		{
			if (gPcapDescriptors[i])
			{
				if (head_ts > rec_ts) 
				{
					uHead = uPcapRecord;
					earliest = i;
					head_ts = rec_ts;
				}
			}
		} /* if */
		else
		{
			uHead = uPcapRecord;
			head_ts = rec_ts;
			earliest = i;
		}
	
	} /* for */

	get[earliest] = 1;

	if (gPcapDescriptors[earliest] == NULL)
		return NULL;

#if defined(DLT_ERF)
	if (pcap_datalink(gPcapDescriptors[earliest]) == DLT_ERF) {
		memcpy(&uDagRecord, uHead->pkt, dag_record_size);
		uPayload = (void*)(uHead->pkt + dag_record_size);
		gPcapRecord = uPcapRecord;
		return &uDagRecord;
	}
#endif /* DLT_ERF */

	uDagRecord.ts = swapll(head_ts);

	uDagRecord.rlen = htons(uHead->pkthdr.caplen + dag_record_size);  /* length of portion present + ERF header size */
	uDagRecord.wlen = htons(uHead->pkthdr.len); /* length of this packet (off wire) */
	uDagRecord.flags.vlen = 1;
	uPayload = (u_char*)(uHead->pkt);

	switch (pcap_datalink(gPcapDescriptors[earliest]))
	{
	case DLT_ATM_RFC1483:
		uDagRecord.type = ERF_TYPE_ATM;
		uDagRecord.flags.vlen = 0;
		break;
		
#if defined(DLT_DOCSIS)
	case DLT_DOCSIS:
#endif
	case DLT_EN10MB:
		uDagRecord.type = ERF_TYPE_ETH;
		break;
		
#if defined(DLT_MTP2)
	case DLT_MTP2:
#endif
	case DLT_FRELAY:
	case DLT_PPP_SERIAL:
	case DLT_CHDLC:
		uDagRecord.type = ERF_TYPE_HDLC_POS;
		break;
#if defined(DLT_IPV4)
	case DLT_IPV4:
		uDagRecord.type = ERF_TYPE_IPV4;
		break;
#endif	
	case DLT_RAW:
		if (gErfOutputType == 4)
			uDagRecord.type = ERF_TYPE_IPV4;
		else if (gErfOutputType == 6)
			uDagRecord.type = ERF_TYPE_IPV6;
		break;

#if defined(DLT_IPV6)
	case DLT_IPV6:
		uDagRecord.type = ERF_TYPE_IPV6;
		break;
#endif
		
	default:
		dagutil_panic("Unhandled libpcap datalink type %d\n", pcap_datalink(gPcapDescriptors[earliest]));
		break;
	}

	gPcapRecord = uPcapRecord;
	return &uDagRecord;
}

/*
 * The parameter, phdr, is a pointer to the pcap_pkthdr structure which precedes each packet in the savefile.
 */
void
get_pcap_hdr(u_char *user, const struct pcap_pkthdr *phdr, const u_char *pdata)
{
	if (phdr)
	{
		uPcapRecord->pkthdr.ts.tv_sec = phdr->ts.tv_sec;
		uPcapRecord->pkthdr.ts.tv_usec = phdr->ts.tv_usec;
		uPcapRecord->pkthdr.caplen = phdr->caplen;
		uPcapRecord->pkthdr.len = phdr->len;
		if(pcap_datalink((pcap_t*)user) == DLT_EN10MB) {
			/* add gFcsBits of space for FCS, will be zero */
			uPcapRecord->pkt = calloc(1, (phdr->caplen)+2+(gFcsBits>>3));
			memcpy(((char*)(uPcapRecord->pkt))+2,pdata,phdr->caplen);
			uPcapRecord->pkthdr.caplen += 2;
		} else {
			uPcapRecord->pkt = calloc(1, phdr->caplen+(gFcsBits>>3));
			memcpy(uPcapRecord->pkt,pdata,phdr->caplen);
		}
		uPcapRecord->pkthdr.caplen += (gFcsBits>>3);
		uPcapRecord->pkthdr.len += (gFcsBits>>3);		
	}
}

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


void
alloc_pcap_dumper_table() {

	mc_dumper_table = calloc(sizeof(mc_dumper_t), MC_DUMPERS);
	if (!mc_dumper_table) {
		dagutil_panic("Could not initialize dumpers\n");
	}
}

void
cleanup_pcap_dumper_table() {
	int i;
	for (i = 0; i < MC_DUMPERS; i++) {
		if (mc_dumper_table[i].dumper) {
			pcap_dump_close(mc_dumper_table[i].dumper);
			mc_dumper_table[i].dumper = NULL;
            if ( 0 != change_output_file_extn(mc_dumper_table[i].mc_file_name, ".pcap~", ".pcap") )
            {
                dagutil_warning("Failed to convert erf from pcap~\n");
            }
		}
	}
	dagutil_free(mc_dumper_table);
}
