/*******************************************************************************/
/*                                                                             */
/*  Copyright 2004 Pascal Gloor                                                */
/*                                                                             */
/*  Licensed under the Apache License, Version 2.0 (the "License");            */
/*  you may not use this file except in compliance with the License.           */
/*  You may obtain a copy of the License at                                    */
/*                                                                             */
/*     http://www.apache.org/licenses/LICENSE-2.0                              */
/*                                                                             */
/*  Unless required by applicable law or agreed to in writing, software        */
/*  distributed under the License is distributed on an "AS IS" BASIS,          */
/*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   */
/*  See the License for the specific language governing permissions and        */
/*  limitations under the License.                                             */
/*                                                                             */
/*******************************************************************************/


#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <time.h>


#include <p_defs.h>
#include <p_dump.h>

/* opening file */
void p_dump_open_file(struct peer_t *peer, int id, uint32_t ts)
{
	struct tm *tm;
	struct in_addr addr;
	struct stat sb;
	char dirname[1024];
	char filename[1024];
	char mytime[100];

	peer[id].filets = ts - ( ts % DUMPINTERVAL );

	addr.s_addr = peer[id].ip;

	tm = gmtime((time_t*)&peer[id].filets);
	strftime(mytime, sizeof(mytime), "%Y%m%d%H%M%S" , tm);

	snprintf(dirname, sizeof(dirname), "%s/%s", DUMPDIR, inet_ntoa(addr));
	snprintf(peer[id].filename, sizeof(peer[id].filename), "%s/%s/%s", DUMPDIR, inet_ntoa(addr), mytime);
	snprintf(filename, sizeof(filename), "%s/%s/%s", DUMPDIR, inet_ntoa(addr), "temp.dump");
	#ifdef DEBUG
	printf("opening '%s'\n",peer[id].filename);
	#endif
	if ( stat(dirname, &sb) == -1 )
	{
		mkdir(dirname, 0755);
	}
/*
	peer[id].fh = fopen(peer[id].filename, "wb" );
*/
	peer[id].fh = fopen(filename, "wb" );
	peer[id].empty = 1;
}

/* log keepalive msg */
void p_dump_add_keepalive(struct peer_t *peer, int id, uint32_t ts)
{
	p_dump_check_file(peer,id,ts);

	if ( peer[id].fh == NULL ) { return; }
	peer[id].empty = 0;
	{
		struct dump_msg msg;

		msg.type = htons(DUMP_KEEPALIVE);
		msg.ts   = htonl(ts);
		msg.len  = 0;

		fwrite(&msg, sizeof(msg), 1, peer[id].fh);
	}
}

/* log session close */
void p_dump_add_close(struct peer_t *peer, int id, uint32_t ts)
{
	if ( peer[id].fh == NULL ) { return; }
	peer[id].empty = 0;
	{
		struct dump_msg msg;

		msg.type = htons(DUMP_CLOSE);
		msg.ts   = htonl(ts);
		msg.len  = 0;

		fwrite(&msg, sizeof(msg), 1, peer[id].fh);
	}

	p_dump_check_file(peer,id,ts);
}

/* log session open */
void p_dump_add_open(struct peer_t *peer, int id, uint32_t ts)
{
	p_dump_check_file(peer,id,ts);

	if ( peer[id].fh == NULL ) { return; }
	peer[id].empty = 0;
	{
		struct dump_msg msg;

		msg.type = htons(DUMP_OPEN);
		msg.ts   = htonl(ts);
		msg.len  = 0;

		fwrite(&msg, sizeof(msg), 1, peer[id].fh);
	}
}

/* footer for each EOF */
void p_dump_add_footer(struct peer_t *peer, int id, uint32_t ts)
{
	if ( peer[id].fh == NULL ) { return; }
	{
		struct dump_msg msg;

		msg.type = htons(DUMP_FOOTER);
		msg.ts   = htonl(ts);
		msg.len  = 0;

		fwrite(&msg, sizeof(msg), 1, peer[id].fh);
	}
}

/* log bgp withdrawn msg */
void p_dump_add_withdrawn(struct peer_t *peer, int id, uint32_t ts, uint32_t prefix, uint8_t mask)
{
	p_dump_check_file(peer,id,ts);

	if ( peer[id].fh == NULL ) { return; }
	peer[id].empty = 0;
	{
		struct dump_msg msg;
		struct dump_withdrawn withdrawn;

		msg.type = htons(DUMP_WITHDRAWN);
		msg.ts   = htonl(ts);

		withdrawn.mask   = mask;

		if ( mask == 0 )
		{
			prefix = 0;
		}
		else
		{
			prefix &= ( 0xffffffff ^ ( ( 1 << ( 32 - mask ) ) - 1 ) );
		}

		withdrawn.prefix = htonl(prefix);

		msg.len = htons(sizeof(withdrawn));

		fwrite(&msg, sizeof(msg), 1, peer[id].fh);
		fwrite(&withdrawn, sizeof(withdrawn), 1, peer[id].fh);

	}
}

/* log bgp announce msg */
void p_dump_add_announce(struct peer_t *peer, int id, uint32_t ts,
                         uint32_t prefix, uint8_t mask,
                         void *aspath, uint16_t aspathlen,
                         void *community, uint16_t communitylen )
{
	p_dump_check_file(peer,id,ts);

	if ( peer[id].fh == NULL ) { return; }
	peer[id].empty = 0;
	{
		struct dump_msg                msg;
		struct dump_announce           announce;
		struct dump_announce_aspath    opt_aspath;
		struct dump_announce_community opt_community;

		msg.type = htons(DUMP_ANNOUNCE);
		msg.ts   = htonl(ts);

		announce.mask   = mask;

		if ( mask == 0 )
		{
			prefix = 0;
		}
		else
		{
			prefix &= ( 0xffffffff ^ ( ( 1 << ( 32 - mask ) ) - 1 ) );
		}

		announce.prefix = htonl(prefix);

		#ifdef DEBUG
		{
			struct in_addr addr;
			addr.s_addr = htonl(prefix);
			printf("DUMP ANNOUNCE %s/%u\n",inet_ntoa(addr),announce.mask);
		}
		#endif

		opt_aspath.code = DUMP_OPT_ASPATH;
		opt_aspath.len  = htons(aspathlen * 2);

		opt_community.code = DUMP_OPT_COMMUNITY;
		opt_community.len  = htons(communitylen * 4);

		msg.len = htons( (communitylen*4) + (aspathlen*2) + sizeof(announce) + 6 );

		memcpy(opt_aspath.data, aspath, aspathlen*2);
		memcpy(opt_community.data, community, communitylen*4);

		fwrite(&msg, sizeof(msg), 1, peer[id].fh);
		fwrite(&announce, sizeof(announce), 1, peer[id].fh);

		fwrite(&opt_aspath,    (aspathlen*2) + 3, 1, peer[id].fh);
		fwrite(&opt_community, (communitylen*4) + 3, 1, peer[id].fh);
	}
}

/* check if need to reopen a new file */
void p_dump_check_file(struct peer_t *peer, int id, uint32_t ts)
{
	uint32_t mts = ts - ( ts % DUMPINTERVAL );

	if ( mts == peer[id].filets && peer[id].fh != NULL ) { return; }

	if ( mts != peer[id].filets )
	{
		if ( peer[id].fh != NULL )
		{
			p_dump_add_footer(peer,id,ts);
			p_dump_close_file(peer,id);
		}

		if ( peer[id].status != 0 )
		{
			p_dump_open_file(peer,id,ts);
			p_dump_add_header(peer,id,ts);
		}
	}
	else if ( peer[id].fh == NULL && peer[id].status != 0)
	{
		p_dump_open_file(peer,id,ts);
		p_dump_add_header(peer,id,ts);
	}
}

/* file header */
void p_dump_add_header(struct peer_t *peer, int id, uint32_t ts)
{
	if ( peer[id].fh == NULL ) { return; }
	{
		struct dump_msg msg;
		struct dump_header header;

		msg.type = htons(DUMP_HEADER);
		msg.ts   = htonl(ts);
		msg.len  = htons(sizeof(header));

		header.ip = peer[id].ip;
		header.as = htons(peer[id].as);

		fwrite(&msg,    sizeof(msg),    1, peer[id].fh);
		fwrite(&header, sizeof(header), 1, peer[id].fh);
	}
}

/* close file */
void p_dump_close_file(struct peer_t *peer, int id)
{
	char filename[1024];
	struct in_addr addr;

	if ( peer[id].fh == NULL ) { return; }

	fclose(peer[id].fh);

	addr.s_addr = peer[id].ip;

	snprintf(filename, sizeof(filename), "%s/%s/%s", DUMPDIR, inet_ntoa(addr), "temp.dump");

	rename(filename, peer[id].filename);

	if ( peer[id].empty == 1 )
	{
		unlink(peer[id].filename);
	}
}