/*
 *  Copyright (c) 2012, Peter Haag
 *  
 *  Redistribution and use in source and binary forms, with or without 
 *  modification, are permitted provided that the following conditions are met:
 *  
 *   * Redistributions of source code must retain the above copyright notice, 
 *     this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright notice, 
 *     this list of conditions and the following disclaimer in the documentation 
 *     and/or other materials provided with the distribution.
 *   * Neither the name of SWITCH nor the names of its contributors may be 
 *     used to endorse or promote products derived from this software without 
 *     specific prior written permission.
 *  
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
 *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
 *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
 *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
 *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
 *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
 *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 *  POSSIBILITY OF SUCH DAMAGE.
 *  
 *  $Author: peter $
 *
 *  $Id: exporter.c 135 2012-11-10 11:40:54Z peter $
 *
 *  $LastChangedRevision: 135 $
 *	
 */

#include "config.h"

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <syslog.h>
#include <stdarg.h>

#ifdef HAVE_STDINT_H
#include <stdint.h>
#endif

#ifndef DEVEL
#   define dbg_printf(...) /* printf(__VA_ARGS__) */
#else
#   define dbg_printf(...) printf(__VA_ARGS__)
#endif

#include "util.h"
#include "nffile.h"
#include "nfx.h"
#include "nfnet.h"
#include "bookkeeper.h"
#include "nfxstat.h"
#include "collector.h"
#include "exporter.h"
#include "nf_common.h"
#include "netflow_v1.h"
#include "netflow_v5_v7.h"
#include "netflow_v9.h"
#include "ipfix.h"

/* global */
generic_exporter_t **exporter_list;

/* local variables */
#define MAX_EXPORTERS 256
static generic_exporter_t *exporter_root;

#include "nffile_inline.c"

/* local prototypes */

/* functions */
int InitExporterList(void) {

	exporter_list = calloc(MAX_EXPORTERS, sizeof(generic_exporter_t *));
	if ( !exporter_list ) {
		LogError("malloc() error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
		return 0;
	}
	exporter_root = NULL;
	return 1;

} // End of InitExporterList

int AddExporterInfo(exporter_info_record_t *exporter_record) {
uint32_t id = exporter_record->sysid;

	if ( exporter_list[id] != NULL ) {
		// slot already taken - check if exporters are identical
		exporter_record->sysid = exporter_list[id]->info.sysid;
		if ( memcmp((void *)exporter_record, (void *)&(exporter_list[id]->info), sizeof(exporter_info_record_t)) == 0 ) {
			dbg_printf("Found identical exporter record at SysID: %i, Slot: %u\n", exporter_record->sysid, id);
			// we are done
			return 2;
		} else {
			// exporters not identical - move current slot
			int i;
			// search first emty slot at the top of the list
			for ( i = id+1; i < MAX_EXPORTERS  && exporter_list[i] != NULL; i++ ) {;}
			if ( i >= MAX_EXPORTERS ) {
				// all slots taken
				LogError("Too many exporters (>256)\n");
				return 0;
			} 
			dbg_printf("Move existing exporter from slot %u, to %i\n", id, i);
			// else - move slot
			exporter_list[i] = exporter_list[id];
			exporter_list[id] = NULL;
			exporter_record->sysid = i;
		}
	}

	// slot[id] is now available
	exporter_list[id] = (generic_exporter_t *)calloc(1, sizeof(generic_exporter_t));
	if ( !exporter_list[id] ) {
		LogError("malloc() error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
		return 0;
	}
	memcpy((void *)&(exporter_list[id]->info), (void *)exporter_record, sizeof(exporter_info_record_t));
	dbg_printf("Insert exporter record in Slot: %i, Sysid: %u\n", id, exporter_record->sysid);

#ifdef DEVEL
	{
		#define IP_STRING_LEN   40
		char ipstr[IP_STRING_LEN];
		if ( exporter_record->sa_family == AF_INET ) {
			uint32_t _ip = htonl(exporter_record->ip.v4);
			inet_ntop(AF_INET, &_ip, ipstr, sizeof(ipstr));
			printf("SysID: %u, IP: %16s, version: %u, ID: %2u, Slot: %u\n", exporter_record->sysid,
				ipstr, exporter_record->version, exporter_record->id, id);
		} else if ( exporter_record->sa_family == AF_INET6 ) {
			uint64_t _ip[2];
			_ip[0] = htonll(exporter_record->ip.v6[0]);
			_ip[1] = htonll(exporter_record->ip.v6[1]);
			inet_ntop(AF_INET6, &_ip, ipstr, sizeof(ipstr));
			printf("SysID: %u, IP: %40s, version: %u, ID: %2u, Slot: %u\n", exporter_record->sysid,
				ipstr, exporter_record->version, exporter_record->id, id);
		} else {
			strncpy(ipstr, "<unknown>", IP_STRING_LEN);
			printf("**** Exporter IP version unknown ****\n");
		}
	}
	printf("\n");
#endif

	if ( !exporter_root ) {
		exporter_root = exporter_list[id];
	}

	return 1;
} // End of AddExporterInfo

int AddSamplerInfo(sampler_info_record_t *sampler_record) {
uint32_t id = sampler_record->exporter_sysid;
generic_sampler_t	**sampler;

	if ( !exporter_list[id] ) {
		LogError("Exporter SysID: %u not found! - Skip sampler record", id);
		return 0;
	}
	sampler = &exporter_list[id]->sampler;
	while ( *sampler ) {
		if ( memcmp((void *)&(*sampler)->info, (void *)sampler_record, sizeof(sampler_info_record_t)) == 0 ) {
			// Found identical sampler already registered
			dbg_printf("Identical sampler already registered: Exporter SysID: %u, Sampler: id: %i, mode: %u, interval: %u\n",
				sampler_record->exporter_sysid, sampler_record->id, sampler_record->mode, sampler_record->interval);
			return 2;
		}
		sampler = &((*sampler)->next);
	}

	*sampler = (generic_sampler_t *)malloc(sizeof(generic_sampler_t));
	if ( !*sampler ) {
		LogError("malloc() error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
		return 0;
	}
	(*sampler)->next = NULL;
	sampler_record->exporter_sysid = exporter_list[id]->info.sysid;

	memcpy((void *)&(*sampler)->info, (void *)sampler_record, sizeof(sampler_info_record_t));
	dbg_printf("Insert sampler record for exporter at slot %i:\n", id);

#ifdef DEVEL
	{
		if ( sampler_record->id < 0 ) {
			printf("Exporter SysID: %u,	Generic Sampler: mode: %u, interval: %u\n",
				sampler_record->exporter_sysid, sampler_record->mode, sampler_record->interval);
		} else {
			printf("Exporter SysID: %u, Sampler: id: %i, mode: %u, interval: %u\n",
				sampler_record->exporter_sysid, sampler_record->id, sampler_record->mode, sampler_record->interval);
		}
	}
#endif

	return 1;
} // End of AddSamplerInfo

int AddExporterStat(exporter_stats_record_t *stat_record) {
int i;

	for (i=0; i<stat_record->stat_count; i++ ) {
		uint32_t id = stat_record->stat[i].sysid;
		if ( !exporter_list[id] ) {
			LogError("Exporter SysID: %u not found! - Skip stat record record.\n");
			continue;
		}
		exporter_list[id]->sequence_failure += stat_record->stat[i].sequence_failure;
		exporter_list[id]->packets 			+= stat_record->stat[i].packets;
		exporter_list[id]->flows 			+= stat_record->stat[i].flows;
		dbg_printf("Update exporter stat for SysID: %i: Sequence failures: %u, packets: %llu, flows: %llu\n", 
			id, exporter_list[id]->sequence_failure, exporter_list[id]->packets, exporter_list[id]->flows);
	}
	return 1;

} // End of AddExporterStat

void ExportExporterList( nffile_t *nffile ) {
int i;

	// sysid 0 unused -> no exporter available
	i = 1;
	while ( i < MAX_EXPORTERS  && exporter_list[i] != NULL ) {
		exporter_info_record_t *exporter;
       	generic_sampler_t *sampler;

		exporter = &exporter_list[i]->info;
		AppendToBuffer(nffile, (void *)exporter, exporter->header.size);

		sampler  = exporter_list[i]->sampler;
        while ( sampler ) {
            AppendToBuffer(nffile, (void *)&(sampler->info), sampler->info.header.size);
            sampler = sampler->next;
        }

		i++;
	}

	
} // End of ExportExporterList

void PrintExporters(char *filename) {
int i, done, found = 0;
nffile_t	*nffile;
record_header_t *record;
uint32_t skipped_blocks;
uint64_t total_bytes;

	printf("Exporters:\n");

	nffile = OpenFile(filename, NULL);
	if ( !nffile ) {
		return;
	}

	done = 0;
	while ( !done ) {
	int i, ret;

		// get next data block from file
		ret = ReadBlock(nffile);

		switch (ret) {
			case NF_CORRUPT:
			case NF_ERROR:
				if ( ret == NF_CORRUPT ) 
					LogError("Corrupt data file '%s': '%s'\n",filename);
				else 
					LogError("Read error in file '%s': %s\n",filename, strerror(errno) );
				done = 1;
				continue;
				break;
				// fall through - get next file in chain
			case NF_EOF:
				done = 1;
				continue;
				break;
	
			default:
				// successfully read block
				total_bytes += ret;
		}

		if ( nffile->block_header->id != DATA_BLOCK_TYPE_2 ) {
			skipped_blocks++;
			continue;
		}

		// block type = 2

		record = (record_header_t *)nffile->buff_ptr;

		for ( i=0; i < nffile->block_header->NumRecords; i++ ) {
			switch ( record->type ) {
				// ExporterRecordType and SamplerRecordype tc versions only
				case ExporterRecordType: {
					#define IP_STRING_LEN   40
					char ipstr[IP_STRING_LEN];
					exporter_record_t *exporter_record = (exporter_record_t *)record ;
					found = 1;
					printf("\n");
					if ( exporter_record->sa_family == AF_INET ) {
						uint32_t _ip = htonl(exporter_record->ip.v4);
						inet_ntop(AF_INET, &_ip, ipstr, sizeof(ipstr));
						printf("SysID: %u, IP: %16s, version: %u, ID: %2u, Sequence Failures: %u\n", exporter_record->sysid,
							ipstr, exporter_record->version, exporter_record->exporter_id, exporter_record->sequence_failure);
					} else if ( exporter_record->sa_family == AF_INET6 ) {
						uint64_t _ip[2];
						_ip[0] = htonll(exporter_record->ip.v6[0]);
						_ip[1] = htonll(exporter_record->ip.v6[1]);
						inet_ntop(AF_INET6, &_ip, ipstr, sizeof(ipstr));
						printf("SysID: %u, IP: %40s, version: %u, ID: %2u, Sequence Failures: %u\n", exporter_record->sysid,
							ipstr, exporter_record->version, exporter_record->exporter_id, exporter_record->sequence_failure);
					} else {
						strncpy(ipstr, "<unknown>", IP_STRING_LEN);
						printf("**** Exporter IP version unknown ****\n");
					}
				} break;
				case SamplerRecordype: {
					sampler_record_t  *sampler_record = (sampler_record_t *)record;;
					if ( sampler_record->id < 0 ) {
						printf("	Generic Sampler: mode: %u, interval: %u\n",
							sampler_record->mode, sampler_record->interval);
					} else {
						printf("	Sampler: id: %i, mode: %u, interval: %u\n",
							sampler_record->id, sampler_record->mode, sampler_record->interval);
					}
				} break;
				case ExporterInfoRecordType:
					found = 1;
					if ( !AddExporterInfo((exporter_info_record_t *)record) ) {
						LogError("Failed to add Exporter Record\n");
					}
					break;
				case ExporterStatRecordType:
					AddExporterStat((exporter_stats_record_t *)record);
					break;
				case SamplerInfoRecordype:
					if ( !AddSamplerInfo((sampler_info_record_t *)record) ) {
						LogError("Failed to add Sampler Record\n");
					}
					break;
			}
			// Advance pointer by number of bytes for netflow record
			record = (record_header_t *)((pointer_addr_t)record + record->size);	
		}
	}

	CloseFile(nffile);
	DisposeFile(nffile);
	if ( !found ) {
		printf("No Exporter records found\n");
	}

	i = 1;
	while ( i < MAX_EXPORTERS  && exporter_list[i] != NULL ) {
		#define IP_STRING_LEN   40
		char ipstr[IP_STRING_LEN];

		exporter_info_record_t *exporter;
       	generic_sampler_t *sampler;

		printf("\n");
		exporter = &exporter_list[i]->info;
		if ( exporter->sa_family == AF_INET ) {
			uint32_t _ip = htonl(exporter->ip.v4);
			inet_ntop(AF_INET, &_ip, ipstr, sizeof(ipstr));
			if ( exporter_list[i]->flows ) 
				printf("SysID: %u, IP: %16s, version: %u, ID: %2u, Sequence failures: %u, packets: %llu, flows: %llu\n", 
					exporter->sysid, ipstr, exporter->version, exporter->id, 
					exporter_list[i]->sequence_failure, 
					(long long unsigned)exporter_list[i]->packets, 
					(long long unsigned)exporter_list[i]->flows);
			else 
				printf("SysID: %u, IP: %16s, version: %u, ID: %2u\n", 
					exporter->sysid, ipstr, exporter->version, exporter->id);
					
		} else if ( exporter->sa_family == AF_INET6 ) {
			uint64_t _ip[2];
			_ip[0] = htonll(exporter->ip.v6[0]);
			_ip[1] = htonll(exporter->ip.v6[1]);
			inet_ntop(AF_INET6, &_ip, ipstr, sizeof(ipstr));
			if ( exporter_list[i]->flows ) 
				printf("SysID: %u, IP: %40s, version: %u, ID: %2u, Sequence failures: %u, packets: %llu, flows: %llu\n ", 
					exporter->sysid, ipstr, exporter->version, exporter->id, 
					exporter_list[i]->sequence_failure, 
					(long long unsigned)exporter_list[i]->packets, 
					(long long unsigned)exporter_list[i]->flows);
			else
				printf("SysID: %u, IP: %40s, version: %u, ID: %2u\n ", 
					exporter->sysid, ipstr, exporter->version, exporter->id);
		} else {
			strncpy(ipstr, "<unknown>", IP_STRING_LEN);
			printf("**** Exporter IP version unknown ****\n");
		}

		sampler  = exporter_list[i]->sampler;
        while ( sampler ) {
			if ( sampler->info.id < 0 ) {
				printf("	Sampler for Exporter SysID: %u,	Generic Sampler: mode: %u, interval: %u\n",
					sampler->info.exporter_sysid, sampler->info.mode, sampler->info.interval);
			} else {
				printf("	Sampler for Exporter SysID: %u, Sampler: id: %i, mode: %u, interval: %u\n",
					sampler->info.exporter_sysid, sampler->info.id, sampler->info.mode, sampler->info.interval);
			}
           	sampler = sampler->next;
       	}

		i++;
	}


} // End of PrintExporters

