/* ntpdsim.c
 *
 * The source code for the ntp discrete event simulator. 
 *
 * Written By:	Sachin Kamboj
 *		University of Delaware
 *		Newark, DE 19711
 * Copyright (c) 2006
 * (Some code shamelessly based on the original NTP discrete event simulator)
 */

#include <config.h>
#ifdef SIM
#include "ntpd.h"
#include "ntp_config.h"

/* forward prototypes */
int determine_event_ordering(const Event *e1, const Event *e2);
int determine_recv_buf_ordering(const struct recvbuf *b1, 
				const struct recvbuf *b2);
void create_server_associations(void);
void init_sim_io(void);

/* Global Variable Definitions */
sim_info simulation;		/* Simulation Control Variables */
local_clock_info simclock;	/* Local Clock Variables */
queue *event_queue;		/* Event Queue */
queue *recv_queue;		/* Receive Queue */
static double sys_residual = 0;	/* adjustment residue (s) */

void (*event_ptr[]) (Event *) = {
    sim_event_beep, sim_update_clocks, sim_event_timer, sim_event_recv_packet
};			/* Function pointer to the events */


/*
 * Define a function to compare two events to determine which one occurs
 * first.
 */
int
determine_event_ordering(
	const Event *e1,
	const Event *e2
	)
{
	return (e1->time - e2->time);
}


/*
 * Define a function to compare two received packets to determine which
 * one is received first.
 */
int
determine_recv_buf_ordering(
	const struct recvbuf *b1,
	const struct recvbuf *b2
	)
{
	double recv_time1;
	double recv_time2;

	/* Simply convert the time received to double and subtract */
	LFPTOD(&b1->recv_time, recv_time1);
	LFPTOD(&b2->recv_time, recv_time2);

	return (int)(recv_time1 - recv_time2);
}


/* Define a function to create the server associations */
void create_server_associations(void)
{
	int i;

	for (i = 0; i < simulation.num_of_servers; ++i) {
		printf("%s\n", stoa(simulation.servers[i].addr));
		if (peer_config(simulation.servers[i].addr,
				NULL,
				loopback_interface,
				MODE_CLIENT,
				NTP_VERSION,
				NTP_MINDPOLL,
				NTP_MAXDPOLL,
				0, /* peerflags */
				0, /* ttl */
				0, /* peerkey */
				NULL /* group ident */) == 0) {
			fprintf(stderr,
				"ERROR!! Could not create association for: %s\n",
				stoa(simulation.servers[i].addr));
		}
	}
}


/* Main Simulator Code */

int
ntpsim(
	int	argc,
	char *	argv[]
	)
{
	Event *		curr_event;
	struct timeval	seed;

	/* Initialize the local Clock */
	simclock.local_time = 0;
	simclock.adj = 0;
	simclock.slew = 500e-6;

	/* Initialize the simulation */
	simulation.num_of_servers = 0;
	simulation.beep_delay = BEEP_DLY;
	simulation.sim_time = 0;
	simulation.end_time = SIM_TIME;

	/* Initialize ntp modules */
	initializing = TRUE;
	msyslog_term = TRUE;
	init_sim_io();
	init_auth();
	init_util();
	init_restrict();
	init_mon();
	init_timer();
	init_lib();
	init_request();
	init_control();
	init_peer();
	init_proto();
	init_loopfilter();
	mon_start(MON_OFF);

	/* Call getconfig to parse the configuration file */
	getconfig(argc, argv);
	loop_config(LOOP_DRIFTINIT, 0);
	initializing = FALSE;

	/*
	 * Watch out here, we want the real time, not the silly stuff.
	 */
	gettimeofday(&seed, NULL);
	ntp_srandom(seed.tv_usec);

	/* Initialize the event queue */
	event_queue = create_priority_queue((q_order_func)
	    determine_event_ordering);

	/* Initialize the receive queue */
	recv_queue = create_priority_queue((q_order_func)
	    determine_recv_buf_ordering);

	/* Push a beep and a timer on the event queue */
	enqueue(event_queue, event(0, BEEP));
	enqueue(event_queue, event(simulation.sim_time + 1.0, TIMER));

	/* 
	 * Pop the queue until nothing is left or time is exceeded
	 */
	/* maxtime = simulation.sim_time + simulation.end_time;*/
	while (simulation.sim_time <= simulation.end_time &&
	   (!empty(event_queue))) {
		curr_event = dequeue(event_queue);
		/* Update all the clocks to the time on the event */
		sim_update_clocks(curr_event);

		/* Execute the function associated with the event */
		(*event_ptr[curr_event->function])(curr_event);
		free_node(curr_event);
	}
	printf("sys_received: %lu\n", sys_received);
	printf("sys_badlength: %lu\n", sys_badlength);
	printf("sys_declined: %lu\n", sys_declined);
	printf("sys_restricted: %lu\n", sys_restricted);
	printf("sys_newversion: %lu\n", sys_newversion);
	printf("sys_oldversion: %lu\n", sys_oldversion);
	printf("sys_limitrejected: %lu\n", sys_limitrejected);
	printf("sys_badauth: %lu\n", sys_badauth);

	return (0);
}


void
init_sim_io(void)
{
	loopback_interface = emalloc_zero(sizeof(*loopback_interface));
	ep_list = loopback_interface;
	strlcpy(loopback_interface->name, "IPv4loop",
		sizeof(loopback_interface->name));
	loopback_interface->flags = INT_UP | INT_LOOPBACK;
	loopback_interface->fd = -1;
	loopback_interface->bfd = -1;
	loopback_interface->ifnum = 1;
	loopback_interface->family = AF_INET;
	AF(&loopback_interface->sin) = AF_INET;
	SET_ADDR4(&loopback_interface->sin, LOOPBACKADR);
	SET_PORT(&loopback_interface->sin, NTP_PORT);
	AF(&loopback_interface->mask) = AF_INET;
	SET_ADDR4(&loopback_interface->mask, LOOPNETMASK);
}


/* Define a function to create an return an Event  */

Event *event(double t, funcTkn f)
{
    Event *e;

    if ((e = get_node(sizeof(*e))) == NULL)
	abortsim("get_node failed in event");
    e->time = t;
    e->function = f;
    return (e);
}

/* NTP SIMULATION FUNCTIONS */

/* Define a function for processing a timer interrupt.
 * On every timer interrupt, call the NTP timer to send packets and process
 * the clock and then call the receive function to receive packets.
 */
void sim_event_timer(Event *e)
{
    struct recvbuf *rbuf;

    /* Call the NTP timer.
     * This will be responsible for actually "sending the packets."
     * Since this is a simulation, the packets sent over the network
     * will be processed by the simulate_server routine below.
     */
    timer();

    /* Process received buffers */
    while (!empty(recv_queue)) {
	rbuf = (struct recvbuf *)dequeue(recv_queue);
	(*rbuf->receiver)(rbuf);
	free_node(rbuf);
    }

    /* Arm the next timer interrupt. */
    enqueue(event_queue, 
	    event(simulation.sim_time + (1 << EVENT_TIMEOUT), TIMER));
}



/* Define a function to simulate a server.
 * This function processes the sent packet according to the server script,
 * creates a reply packet and pushes the reply packet onto the event queue
 */
int simulate_server(
    sockaddr_u *serv_addr,	/* Address of the server */
    endpt *	inter,		/* Interface on which the reply should
				   be inserted */
    struct pkt *rpkt		/* Packet sent to the server that
				   needs to be processed. */
    )
{
    struct pkt xpkt;		/* Packet to be transmitted back
				   to the client */
    struct recvbuf rbuf;	/* Buffer for the received packet */
    Event *e;			/* Packet receive event */
    server_info *server;	/* Pointer to the server being simulated */
    script_info *curr_script;	/* Current script being processed */
    int i;
    double d1, d2, d3;		/* Delays while the packet is enroute */
    double t1, t2, t3, t4;	/* The four timestamps in the packet */
    l_fp lfp_host;		/* host-order l_fp */

    ZERO(xpkt);
    ZERO(rbuf);

    /* Search for the server with the desired address */
    server = NULL;
    for (i = 0; i < simulation.num_of_servers; ++i) {
	if (memcmp(simulation.servers[i].addr, serv_addr, 
		   sizeof(*serv_addr)) == 0) { 
	    server = &simulation.servers[i];
	    break;
	}
    }

    fprintf(stderr, "Received packet from %s on %s\n",
	    stoa(serv_addr), latoa(inter));
    if (server == NULL)
	abortsim("Server with specified address not found!!!");
    
    /* Get the current script for the server */
    curr_script = server->curr_script;

    /* Create a server reply packet. 
     * Masquerade the reply as a stratum-1 server with a GPS clock
     */
    xpkt.li_vn_mode = PKT_LI_VN_MODE(LEAP_NOWARNING, NTP_VERSION,
				     MODE_SERVER);
    xpkt.stratum = STRATUM_TO_PKT(((u_char)1));
    memcpy(&xpkt.refid, "GPS", 4);
    xpkt.ppoll = rpkt->ppoll;
    xpkt.precision = rpkt->precision;
    xpkt.rootdelay = 0;
    xpkt.rootdisp = 0;

    /* TIMESTAMP CALCULATIONS
	    t1				 t4
	     \				/
	  d1  \			       / d3
	       \		      /
	       t2 ----------------- t3
			 d2
    */
    /* Compute the delays */
    d1 = poisson(curr_script->prop_delay, curr_script->jitter);
    d2 = poisson(curr_script->proc_delay, 0);
    d3 = poisson(curr_script->prop_delay, curr_script->jitter);

    /* Note: In the transmitted packet: 
     * 1. t1 and t4 are times in the client according to the local clock.
     * 2. t2 and t3 are server times according to the simulated server.
     * Compute t1, t2, t3 and t4
     * Note: This function is called at time t1. 
     */

    NTOHL_FP(&rpkt->xmt, &lfp_host);
    LFPTOD(&lfp_host, t1);
    t2 = server->server_time + d1;
    t3 = server->server_time + d1 + d2;
    t4 = t1 + d1 + d2 + d3;

    /* Save the timestamps */
    xpkt.org = rpkt->xmt;
    DTOLFP(t2, &lfp_host);
    HTONL_FP(&lfp_host, &xpkt.rec);
    DTOLFP(t3, &lfp_host);
    HTONL_FP(&lfp_host, &xpkt.xmt);
    xpkt.reftime = xpkt.xmt;

    /* 
     * Ok, we are done with the packet. Now initialize the receive
     * buffer for the packet.
     */
    rbuf.used = 1;
    rbuf.receiver = &receive;   /* callback to process the packet */
    rbuf.recv_length = LEN_PKT_NOMAC;
    rbuf.recv_pkt = xpkt;
    rbuf.dstadr = inter;
    rbuf.fd = inter->fd;
    memcpy(&rbuf.srcadr, serv_addr, sizeof(rbuf.srcadr));
    memcpy(&rbuf.recv_srcadr, serv_addr, sizeof(rbuf.recv_srcadr));

    /*
     * Create a packet event and insert it onto the event_queue at the
     * arrival time (t4) of the packet at the client 
     */
    e = event(t4, PACKET);
    e->rcv_buf = rbuf;
    enqueue(event_queue, e);

    /*
     * Check if the time of the script has expired. If yes, delete it.
     */
    if (curr_script->duration > simulation.sim_time && 
	NULL == HEAD_PFIFO(server->script)) {
	printf("Hello\n");
	/* 
	 * For some reason freeing up the curr_script memory kills the
	 * simulation. Further debugging is needed to determine why.
	 * free(curr_script);
	 */
	UNLINK_FIFO(curr_script, *server->script, link);
    }

    return (0);
}


/* Define a function to update all the clocks 
 * Most of the code is modified from the systime.c file by Prof. Mills
 */

void sim_update_clocks(Event *e)
{
    double time_gap;
    double adj;
    int i;

    /* Compute the time between the last update event and this update */
    time_gap = e->time - simulation.sim_time;

    if (time_gap < 0)
	    printf("WARNING: e->time %.6g comes before sim_time %.6g (gap %+.6g)\n",
		   e->time, simulation.sim_time, time_gap);

    /* Advance the client clock */
    if (e->time + time_gap < simclock.local_time)
	    printf("WARNING: e->time + gap %.6g comes before local_time %.6g\n",
		   e->time + time_gap, simclock.local_time);
    simclock.local_time = e->time + time_gap;

    /* Advance the simulation time */
    simulation.sim_time = e->time;

    /* Advance the server clocks adjusted for systematic and random frequency
     * errors. The random error is a random walk computed as the
     * integral of samples from a Gaussian distribution.
     */
    for (i = 0; i < simulation.num_of_servers; ++i) {
	simulation.servers[i].curr_script->freq_offset +=
	    gauss(0, time_gap * simulation.servers[i].curr_script->wander);

	simulation.servers[i].server_time += time_gap * 
	    (1 + simulation.servers[i].curr_script->freq_offset);
    }

    /* Perform the adjtime() function. If the adjustment completed
     * in the previous interval, amortize the entire amount; if not,
     * carry the leftover to the next interval.
     */

    adj = time_gap * simclock.slew;
    if (adj < fabs(simclock.adj)) {
	if (simclock.adj < 0) {
	    simclock.adj += adj;
	    simclock.local_time -= adj;
	} else {
	    simclock.adj -= adj;
	    simclock.local_time += adj;
	}    
    } else {
	simclock.local_time += simclock.adj;
	simclock.adj = 0;
    }
}


/* Define a function that processes a receive packet event. 
 * This function simply inserts the packet received onto the receive queue
 */   

void sim_event_recv_packet(Event *e)
{
    struct recvbuf *rbuf;

    /* Allocate a receive buffer and copy the packet to it */
    if ((rbuf = get_node(sizeof(*rbuf))) == NULL)
	abortsim("get_node failed in sim_event_recv_packet");
    memcpy(rbuf, &e->rcv_buf, sizeof(*rbuf));

    /* Store the local time in the received packet */
    DTOLFP(simclock.local_time, &rbuf->recv_time);

    /* Insert the packet received onto the receive queue */
    enqueue(recv_queue, rbuf);
}



/* Define a function to output simulation statistics on a beep event
 */

/*** TODO: Need to decide on how to output for multiple servers ***/
void sim_event_beep(Event *e)
{
#if 0
    static int first_time = 1;
    char *dash = "-----------------";
#endif

    fprintf(stderr, "BEEP!!!\n");
    enqueue(event_queue, event(e->time + simulation.beep_delay, BEEP));
#if 0
    if(simulation.beep_delay > 0) {
	if (first_time) {
	    printf("\t%4c    T    %4c\t%4c  T+ERR  %3c\t%5cT+ERR+NTP\n", 
	           ' ', ' ', ' ', ' ',' ');
	    printf("\t%s\t%s\t%s\n", dash, dash, dash);
	    first_time = 0;

	    printf("\t%16.6f\t%16.6f\t%16.6f\n",
	           n->time, n->clk_time, n->ntp_time);
	    return;
	}
	printf("\t%16.6f\t%16.6f\t%16.6f\n",
	       simclock.local_time, 
	       n->time, n->clk_time, n->ntp_time);
#endif

}


/* Define a function to abort the simulation on an error and spit out an
 * error message
 */

void abortsim(char *errmsg)
{
    perror(errmsg);
    exit(1);
}



/* CODE ORIGINALLY IN libntp/systime.c 
 * -----------------------------------
 * This code was a part of the original NTP simulator and originally 
 * had its home in the libntp/systime.c file. 
 *
 * It has been shamelessly moved to here and has been modified for the
 * purposes of the current simulator.
 */


/*
 * get_systime - return the system time in NTP timestamp format 
 */
void
get_systime(
    l_fp *now		/* current system time in l_fp */        )
{
    /*
     * To fool the code that determines the local clock precision,
     * we advance the clock a minimum of 200 nanoseconds on every
     * clock read. This is appropriate for a typical modern machine
     * with nanosecond clocks. Note we make no attempt here to
     * simulate reading error, since the error is so small. This may
     * change when the need comes to implement picosecond clocks.
     */
    if (simclock.local_time == simclock.last_read_time)
        simclock.local_time += 200e-9;

    simclock.last_read_time = simclock.local_time;
    DTOLFP(simclock.local_time, now);
/* OLD Code
   if (ntp_node.ntp_time == ntp_node.last_time)
   ntp_node.ntp_time += 200e-9;
   ntp_node.last_time = ntp_node.ntp_time;
   DTOLFP(ntp_node.ntp_time, now);
*/
}
 
 
/*
 * adj_systime - advance or retard the system clock exactly like the
 * real thng.
 */
int				/* always succeeds */
adj_systime(
    double now		/* time adjustment (s) */
    )
{
    struct timeval adjtv;	/* new adjustment */
    double	dtemp;
    long	ticks;
    int	isneg = 0;

    /*
     * Most Unix adjtime() implementations adjust the system clock
     * in microsecond quanta, but some adjust in 10-ms quanta. We
     * carefully round the adjustment to the nearest quantum, then
     * adjust in quanta and keep the residue for later.
     */
    dtemp = now + sys_residual;
    if (dtemp < 0) {
	isneg = 1;
	dtemp = -dtemp;
    }
    adjtv.tv_sec = (long)dtemp;
    dtemp -= adjtv.tv_sec;
    ticks = (long)(dtemp / sys_tick + .5);
    adjtv.tv_usec = (long)(ticks * sys_tick * 1e6);
    dtemp -= adjtv.tv_usec / 1e6;
    sys_residual = dtemp;

    /*
     * Convert to signed seconds and microseconds for the Unix
     * adjtime() system call. Note we purposely lose the adjtime()
     * leftover.
     */
    if (isneg) {
	adjtv.tv_sec = -adjtv.tv_sec;
	adjtv.tv_usec = -adjtv.tv_usec;
	sys_residual = -sys_residual;
    }
    simclock.adj = now;
/*	ntp_node.adj = now; */
    return (1);
}
 
 
/*
 * step_systime - step the system clock. We are religious here.
 */
int				/* always succeeds */
step_systime(
    double now		/* step adjustment (s) */
    )
{
#ifdef DEBUG
    if (debug)
	printf("step_systime: time %.6f adj %.6f\n",
	       simclock.local_time, now);
#endif
    simclock.local_time += now;
    return (1);
}
 
/*
 * gauss() - returns samples from a gaussion distribution
 */
double				/* Gaussian sample */
gauss(
    double m,		/* sample mean */
    double s		/* sample standard deviation (sigma) */
    )
{
    double q1, q2;

    /*
     * Roll a sample from a Gaussian distribution with mean m and
     * standard deviation s. For m = 0, s = 1, mean(y) = 0,
     * std(y) = 1.
     */
    if (s == 0)
        return (m);
    while ((q1 = drand48()) == 0)
	/* empty statement */;
    q2 = drand48();
    return (m + s * sqrt(-2. * log(q1)) * cos(2. * PI * q2));
}

 
/*
 * poisson() - returns samples from a network delay distribution
 */
double				/* delay sample (s) */
poisson(
    double m,		/* fixed propagation delay (s) */
    double s		/* exponential parameter (mu) */
    )
{
    double q1;

    /*
     * Roll a sample from a composite distribution with propagation
     * delay m and exponential distribution time with parameter s.
     * For m = 0, s = 1, mean(y) = std(y) = 1.
     */
    if (s == 0)
        return (m);
    while ((q1 = drand48()) == 0)
	/* empty statement */;
    return (m - s * log(q1 * s));
}

#endif
