/*
 *  Copyright (c) 2012-2020, 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 the author 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.
 *  
 */

#ifdef HAVE_CONFIG_H 
#include "config.h"
#endif

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.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 <stdarg.h>

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

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

/* global */
exporter_t **exporter_list;

/* local variables */
#define MAX_EXPORTERS 65536
static exporter_t *exporter_root;

#include "nffile_inline.c"

/* local prototypes */

/* functions */
int InitExporterList(void) {

	exporter_list = calloc(MAX_EXPORTERS, sizeof(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;
int i;
char *p1, *p2;

	if ( exporter_record->header.size != sizeof(exporter_info_record_t) ) {
		LogError("Corrupt exporter record in %s line %d\n", __FILE__, __LINE__);
		return 0;
	}

	// sanity check
	id = exporter_record->sysid;
	if ( id >= MAX_EXPORTERS ) {
		LogError("Corrupt exporter record in %s line %d\n", __FILE__, __LINE__);
		return 0;
	}
	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] = (exporter_t *)calloc(1, sizeof(exporter_t));
	if ( !exporter_list[id] ) {
		LogError("malloc() error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
		return 0;
	}

	// SPARC gcc fails here, if we use directly a pointer to the struct.
	// SPARC barfs and core dumps otherwise
	// memcpy((void *)&(exporter_list[id]->info), (void *)exporter_record, sizeof(exporter_info_record_t));
	p1 = (char *)&(exporter_list[id]->info);
	p2 = (char *)exporter_record;
	for ( i=0; i<sizeof(exporter_info_record_t); i++ ) 
		*p1++ = *p2++;

	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_t	**sampler;

	if ( sampler_record->header.size != sizeof(sampler_info_record_t) ) {
		LogError("Corrupt sampler record in %s line %d\n", __FILE__, __LINE__);
		return 0;
	}

	id = sampler_record->exporter_sysid;
	if ( id >= MAX_EXPORTERS) {
		LogError("Corrupt sampler record in %s line %d\n", __FILE__, __LINE__);
		return 0;
	}

	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 = (sampler_t *)malloc(sizeof(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, use_copy;
exporter_stats_record_t *rec;
size_t required;

	if ( stat_record->header.size < sizeof(exporter_stats_record_t) ) {
		LogError("Corrupt exporter record in %s line %d\n", __FILE__, __LINE__);
		return 0;
	}

	required = sizeof(exporter_stats_record_t) + (stat_record->stat_count-1) * sizeof(struct exporter_stat_s);
	if ((stat_record->stat_count == 0) || (stat_record->header.size != required)) {
		LogError("Corrupt exporter record in %s line %d\n", __FILE__, __LINE__);
		return 0;
	}

	// 64bit counters can be potentially unaligned
	if ( ((ptrdiff_t)stat_record & 0x7) != 0 ) {
		rec = malloc(stat_record->header.size);
		if ( !rec ) {
			LogError("malloc() error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
			exit(255);
		}
		memcpy(rec, stat_record, stat_record->header.size);
		use_copy = 1;
	} else {
		rec = stat_record;
		use_copy = 0;
	}

	for (i=0; i<rec->stat_count; i++ ) {
		uint32_t id = rec->stat[i].sysid;
		if ( id >= MAX_EXPORTERS ) {
			LogError("Corrupt exporter record in %s line %d\n", __FILE__, __LINE__);
			return 0;
		}
		if ( !exporter_list[id] ) {
			LogError("Exporter SysID: %u not found! - Skip stat record record.\n");
			continue;
		}
		exporter_list[id]->sequence_failure += rec->stat[i].sequence_failure;
		exporter_list[id]->packets 			+= rec->stat[i].packets;
		exporter_list[id]->flows 			+= rec->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);
	}

	if ( use_copy )
		free(rec);

	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;
       	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;
	}

	total_bytes	   = 0;
	skipped_blocks = 0;
	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 ) {
				case LegacyRecordType1: 
				case LegacyRecordType2: 
						LogError("Legacy record type: %i no longer supported\n", record->type);
					 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;
       	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

