/*
 * Basic/interleaved protocol simulator
 *
 * This program simulates the NTP on-wire protocol operating in either
 * the current basic (1-step) mode or proposed interleaved (2-step)
 * mode. Basic mode delivers one set of timestamps for each symmetric
 * peer on each round. It is compatible with the NTP protocol
 * specification and all past and present NTP reference implementations.
 * Interleaved mode delivers two sets of timestamps for each symmetric
 * peer in two successive rounds. To operate in interleaved mode, both
 * symmetric peers must support this mode.
 *
 * In either basic or interleaved mode The protocol can recover from
 * most error conditions involving a lost or duplicated packet. However,
 * a crossover error in interleaved mode requires a protocol reset,
 * which requires a delay of two rounds. The program can automatically
 * synchronize to a preferred mode. If one peer can operate in either
 * basic or interleaved mode and the other can ooperate only in basic
 * mode, the interleaved peer reverts to basic mode automatically.
 *
 * The timestamp numbering is designed to detect an incorrect timetamp
 * set. Timestamps are numbered sequentially so that transmit timestamps
 * have odd numbers while receive timestamps have even numbers. There
 * are four timetamps in a set: t1, t2, t3 and t4. A correct timestamp
 * set has t2 = t1 + 1 and t4 = t3 + 1. Any other numbering is an error.
 *
 * The program runs the protcol until both peers are synchronized, then
 * executes an optional scenario represening an error condition, then
 * continues until both peers are again synchronized. The program is
 * controlled by three flags. The -s option selects the scenario as an
 * integer: 0 normal operation, 1 network duplicate, 2 peer duplicate, 3
 * peer reset, 4 broadcast. The -a and -b options set the mode and
 * initial state of peer A and B, respectively: 0 force basic mode, 1
 * force interleaved mode, 2 negotiate starting in basic mode, 3
 * negotiate starting in interleaved mode.
 */ 
#include <stdlib.h>

/*
 * Scenarios
 */
#define	S_NORM	0		/* normal */
#define	S_NETD	1		/* net duplicate */
#define	S_PEED	2		/* peer duplicate */
#define	S_REST	3		/* crossover error */
#define	S_BCST	4		/* broadcast */

/*
 * Modes
 */
#define BASIC	0x0		/* force basic mode */
#define	XLEAV	0x1		/* force interleaved mode */
#define	BNEGO	0x2		/* start basic; will do either */
#define	XNEGOT	ox3		/* start interleaved; will do either */

/*
 * Error codes
 */
#define	R_OK	0		/* okay */
#define	R_DUPE	1		/* duplicate packet */
#define	R_LOOP	2		/* loopback error */
#define	R_SYNC	3		/* syncrhonization error */

/*
 * Peer state variables
 */
struct peer {
	int	mode;		/* mode */
	int	err;		/* last error */
	int	rec;		/* receive timestamp */
	int	xmt;		/* transmit timestamp */
	int	dst;		/* destination timestamp */
	int	aorg;		/* a origin */
	int	borg;		/* b origin */
	int	x;		/* transmit origin switch */
	int	r;		/* t/r switch */
	int	b;		/* broadcast switch */
	char	id;		/* ident */
};

/*
 * Packet header variables
 */
struct {
	int	torg;		/* origin timestamp */
	int	trec;		/* receive timestamp */
	int	txmt;		/* transmit timestamp */
} pkt;

/*
 * Global variables, etc.
 */
int	n = 1;			/* timestamp counter */
int	tcnt;			/* successful rounds */
int	sw;			/* scenario */

void	trans(struct peer *);	/* transmit routine */
void	bcast(struct peer *);	/* broadcast routine */
void	recv(struct peer *);	/* receive routine */


/*
 * Main program
 */
main(
	int	argc,		/* number of arguments */
	char	**argv		/* vector of argument pointers */
	)
{
	struct peer *a;		/* peer A structure */
	struct peer *b;		/* peer B structure */
	struct peer *c;		/* peer C structure */
	int	i, temp;

	/*
	 * Default basic A and B normal mode. Default C interleaved
	 * mode. It will be used as a broadcast server.
	 */
	a = malloc(sizeof(struct peer));
	memset(a, 0, sizeof(struct peer));
	a->id = 'A';
	b = malloc(sizeof(struct peer));
	memset(b, 0, sizeof(struct peer));
	b->id = 'B';
	c = malloc(sizeof(struct peer));
	memset(c, 0, sizeof(struct peer));
	c->x = 1;
	c->id = 'C';

	/*
	 * Preset modes.
	 */
	while ((temp = getopt(argc, argv, "a:b:s:")) != -1) {
		switch(temp) {

		case 'a':		/* peer A */
			a->mode = atoi(optarg);
			a->x = a->mode & 0x1;
			break;

		case 'b':		/* peer B */
			b->mode = atoi(optarg);
			b->x = b->mode & 0x1;
			break;

		case 's':		/* scenario */
			sw = atoi(optarg);
			break;

		default:
			printf("invalid option\n");
		}
	}
	printf("sw %d A %d B %d\n", sw, a->mode, b->mode);
	printf("    rec dst aorg borg   torg trec txmt       rec dst aorg borg\n");
	printf("   +---+---+---+---+    +---+---+---+       +---+---+---+---+ \n");

	/*
	 * Main loop
	 */
	for (i = 1; i < 7; i++) {
		switch (sw) {

		/*
		 * No error. This is the normal case.
		 */
		case S_NORM:
			trans(a);		/* transmit A to B */
			recv(b);
			trans(b);		/* transmit B to A */
			recv(a);
			break;

		/*
		 * Net duplicate. The packet has either been duplicated
		 * in the network or retransmitted by the other peer.
		 */
		case S_NETD:
			trans(a);	/* transmit A to B */
			recv(b);
			if (tcnt == 2) {
				recv(b); /* receive duplicate */
				tcnt++;
			}
			trans(b);	/* transmit B to A */
			recv(a);
			break;

		/*
		 * Peer duplicate. The other peer has not been heard
		 * before the next poll.
		 */
	 	case S_PEED:
			trans(a);	/* transmit A to B */
			recv(b);
			if (tcnt == 2) {
				trans(a); /* transmit A to B again */
				recv(b);
				tcnt++;
			}
			trans(b);	/* transmit B to A */
			recv(a);
			break;

		/*
		 * Crossover error. Packets from A and B are on the wire
		 * at the same time and cross in flight. This is not a
		 * perfect test, as only as single backet buffer is
		 * used; however, even when simulated with two buffers,
		 * the numbering scheme doesn't work and some scenarios
		 * might in fact be valid.
		 */
		case S_REST:
			trans(a);	/* transmit A to B */
			recv(b);
			if (tcnt == 2) {
				trans(a); /* transmit A to B */
				trans(b); /* transmit B to A */
				recv(a);
				recv(b);
				tcnt++;
			}
			trans(b);	/* transmit B to A */
			recv(a);
			break;

		/*
		 * Broadcast mode. This simulates the case where the
		 * client has just received a broadcast packet and needs
		 * to calibrate the delay.
		 */
		case S_BCST:
			bcast(c);	/* broadcast to A */
			recv(a);
			trans(a);	/* transmit A to B */
			recv(b);
			trans(b);	/* transmit B to A */
			recv(a);
			break;

		default:
			printf("Invalid scenario %d\n", sw);
			break;
		}
	}
}


/*
 * Transmit routine
 */
void
trans(struct peer *peer)		/* peer structure */
{
	pkt.torg = peer->rec;
	pkt.trec = peer->dst;

	/*
	 * Basic mode. Do not alternate the origin timestamp.
	 */
	if (peer->x == 0) {
		peer->aorg = n++;
		pkt.txmt = peer->aorg;

	/*
	 * Interleaved mode. If no receive packet after last transmit
	 * packet, transmit a duplicate.
	 */
	} else if (peer->r != 0) {
		if (peer->x > 0) {
			pkt.txmt = peer->aorg;
		} else {
			pkt.txmt = peer->borg;
		}

	/*
	 * Receive packet after last transmit packet. Alternate the
	 * origin timestamp.
	 */
	} else {
		if (peer->x > 0) {
			peer->aorg = n++;
			pkt.txmt = peer->borg;
		} else {
			peer->borg = n++;
			pkt.txmt = peer->aorg;
		}
		peer->x = -peer->x;
	}
	peer->r = 1;
	printf("%cx | %2d  %2d  %2d  %2d| -> | %2d  %2d  %2d|",
	    peer->id, peer->rec, peer->dst,
	    peer->aorg, peer->borg, pkt.torg, pkt.trec, pkt.txmt);
}


/*
 * Broadcast routine
 */
void
bcast(struct peer *peer)		/* peer structure */
{

	/*
	 * Alternate the origin on each packet.
	 */
	if (peer->x > 0) {
		peer->aorg = n++;
		pkt.txmt = peer->aorg;
		pkt.torg = peer->borg;
	} else {
		peer->borg = n++;
		pkt.txmt = peer->borg;
		pkt.torg = peer->aorg;
	}
	pkt.trec = 0;
	peer->x = -peer->x;
	printf("%cx | %2d  %2d  %2d  %2d| -> | %2d  %2d  %2d|",
	    peer->id, peer->rec, peer->dst,
	    peer->aorg, peer->borg, pkt.torg, pkt.trec, pkt.txmt);
}


/*
 * Receive routine
 */
void
recv(struct peer *peer)		/* peer structure */
{
	int	t1, t2, t3, t4;
	int	err;

	err = R_OK;

	/*
	 * Discard duplicate packets before any further processing.
	 */
	if (pkt.txmt != 0 && peer->xmt == pkt.txmt) {
		err = R_DUPE;
	} else if (peer->x == 0) {

		/*
		 * Basic mode
		 *
		 * An interleaved broadcast mode packet is recognized
		 * when the orgin timestamp is nonzero and the receive
		 * timestamp is zero. An ordinary broadcast packet is
		 * not simulate here.
		 */
		if (pkt.torg != 0 && pkt.trec == 0) {
			peer->b = 1;
			t1 = pkt.torg;
			t2 = peer->borg;
			t3 = peer->aorg;
			t4 = peer->dst;

			/*
			 * The hardware origin timestamp must be later
			 * than the software orign timestamp by no more
			 * than a small amount; otherwise, a previous
			 * broadcast packet has been lost.
			 */
			if (t1 != pkt.torg)
				err = R_LOOP;
			peer->rec = pkt.txmt;
			peer->dst = n++;
			peer->borg = peer->dst;
			/*
			 * Discard unsynchronized packets.
			 */
			if (t1 == 0 || t2 == 0)
				err = R_SYNC;

		/*
		 * Ordinary basic mode packet. In interleaved broadcast
		 * mode the destination timestamp is overriden by the
		 * receive timestamp, which is actually the hardware
		 * origin timestamp.
		 */
		} else {
			peer->rec = pkt.txmt;
			peer->dst = n++;
			t1 = pkt.torg;
			t2 = pkt.trec;
			t3 = pkt.txmt;
			t4 = peer->dst;
			if (peer->b != 0) {
				peer->dst = pkt.trec;
				peer->b = 0;
			}

			/*
			 * Discard unsynchronized and bogus packets.
			 */
			if (t1 == 0 || t2 == 0)
				err = R_SYNC;
			else if (t1 != peer->aorg)
				err = R_LOOP;
		}
	} else {

		/*
		 * Interleaved mode
		 */
		if (peer->x > 0)
			t1 = peer->aorg;
		else if (peer->x < 0)
			t1 = peer->borg;
		t2 = peer->rec;
		t3 = pkt.txmt;
		t4 = peer->dst;
		peer->rec = pkt.trec;
		peer->dst = n++;

		/*
		 * Discard unsynchronized packets. Reset if a bogus
		 * packet.
		 */
		if (t1 == 0 || t2 == 0) {
			err = R_SYNC;
		} else if (pkt.torg != 0 && pkt.torg != t4) {
			peer->rec = peer->dst = 0;
			peer->aorg = peer->borg = 0;
			err = R_LOOP;
		}
	}
	peer->xmt = pkt.txmt;
	peer->r = 0;
	printf(" -> %cr | %2d  %2d  %2d  %2d|", peer->id, peer->rec,
	    peer->dst, peer->aorg, peer->borg, peer->x);
	printf(" %2d\n", peer->x);
	printf("   +---------------+    +-----------+       +---------------+\n");
	printf("%c timestamp %2d %2d %2d %2d tcnt %2d", peer->id,
	    t1, t2, t3, t4, tcnt);

	/*
	 * Document error conditions. Correct behavior requires the
	 * receive timestamp (t2) be one greater than the origin
	 * timestamp (t1) and the destination timestamp (t4) be one
	 * greater than the transmit timestamp (t3).
	 */
	switch (err) {

	case R_OK:			/* no apparent error */
		if (t1 + 1 != t2 || t3 + 1 != t4)
			printf( " ERROR\n");
		else
			printf("\n");
		tcnt++;
		break;

	case R_DUPE:			/* duplicate packet */
		printf(" dupe\n");
		break;

	case R_LOOP:			/* bogus packet */
		if (peer->err == R_SYNC && peer->mode & 0x2) {
			if (peer->x == 0)
				peer->x = 1;
			else
				peer->x = 0;
		}
		printf(" bogus\n");
		break;

	case R_SYNC:			/* unsynchronized packet */
		printf(" sync\n");
		break;
	}
	printf("   +---------------+    +-----------+       +---------------+\n");
	peer->err = err;
}
