/*
 * ntp_peer.c - management of data maintained for peer associations
 */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <sys/types.h>

#include "ntpd.h"
#include "ntp_stdlib.h"

/*
 * These routines manage the allocation of memory to peer structures
 * and the maintenance of the peer hash table.  The two main entry
 * points are findpeer(), which looks for corresponding peer data
 * in the peer list, newpeer(), which allocates a new peer structure
 * and adds it to the list, and unpeer(), which demobilizes the association
 * and deallocates the structure.
 */

/*
 * The peer hash table (imported by the protocol module).
 */
struct peer *peer_hash[HASH_SIZE];
int peer_hash_count[HASH_SIZE];		/* count of peers in each bucket */

/*
 * The association ID hash table.  Used for lookups by association ID
 */
struct peer *assoc_hash[HASH_SIZE];
int assoc_hash_count[HASH_SIZE];

/*
 * The free list.  Clean structures only, please.
 */
struct peer *peer_free;
int peer_free_count;

/*
 * Association ID.  We initialize this value randomly, the assign a new
 * value every time the peer structure is incremented.
 */
u_short current_association_ID;

/*
 * Memory allocation watermarks.
 */
#define	INIT_PEER_ALLOC		15	/* initialize space for 15 peers */
#define	INC_PEER_ALLOC		5	/* when we run out, add 5 more */

/*
 * Miscellaneous statistic counters which may be queried.
 */
u_long peer_timereset;		/* time stat counters were zeroed */
u_long findpeer_calls;		/* number of calls to findpeer */
u_long assocpeer_calls;		/* number of calls to findpeerbyassoc */
u_long peer_allocations;	/* number of allocations from the free list */
u_long peer_demobilizations;	/* number of structs freed to free list */
int total_peer_structs;		/* number of peer structs in circulation */

/*
 * default interface.  Imported from the io module.
 */
extern struct interface *any_interface;

/*
 * Timer queue and current time.  Imported from the timer module.
 */
extern u_long current_time;
extern struct event timerqueue[];

/*
 * Our initial allocation of peer space
 */
static struct peer init_peer_alloc[INIT_PEER_ALLOC];

/*
 * Initialization data.  When configuring peers at initialization time,
 * we try to get their poll update timers initialized to different values
 * to prevent us from sending big clumps of data all at once.
 */
u_long init_peer_starttime;
extern int initializing;
extern int debug;

static	void	getmorepeermem	P((void));

/*
 * init_peer - initialize peer data structures and counters
 *
 * N.B. We use the random number routine in here.  It had better be
 *      initialized prior to getting here.
 */
void
init_peer()
{
	register int i;

	/*
	 * Clear hash table and counters.
	 */
	for (i = 0; i < HASH_SIZE; i++) {
		peer_hash[i] = 0;
		peer_hash_count[i] = 0;
		assoc_hash[i] = 0;
		assoc_hash_count[i] = 0;
	}

	/*
	 * Clear stat counters
	 */
	findpeer_calls = peer_allocations = 0;
	assocpeer_calls = peer_demobilizations = 0;

	/*
	 * Initialization counter.
	 */
	init_peer_starttime = 0;

	/*
	 * Initialize peer memory.
	 */
	peer_free = 0;
	for (i = 0; i < INIT_PEER_ALLOC; i++) {
		init_peer_alloc[i].next = peer_free;
		peer_free = &init_peer_alloc[i];
	}
	total_peer_structs = INIT_PEER_ALLOC;
	peer_free_count = INIT_PEER_ALLOC;

	/*
	 * Initialize our first association ID
	 */
	current_association_ID = (u_short)ranp2(16);
	if (current_association_ID == 0)
		current_association_ID = 1;
}



/*
 * getmorepeermem - add more peer structures to the free list
 */
static void
getmorepeermem()
{
	register int i;
	register struct peer *peer;

	peer = (struct peer *)emalloc(INC_PEER_ALLOC*sizeof(struct peer));
	for (i = 0; i < INC_PEER_ALLOC; i++) {
		peer->next = peer_free;
		peer_free = peer;
		peer++;
	}

	total_peer_structs += INC_PEER_ALLOC;
	peer_free_count += INC_PEER_ALLOC;
}



/*
 * findexistingpeer - return a pointer to a peer in the hash table
 */
struct peer *
findexistingpeer(addr, start_peer)
	struct sockaddr_in *addr;
	struct peer *start_peer;
{
	register struct peer *peer;

	/*
	 * start_peer is included so we can locate instances of the
	 * same peer through different interfaces in the hash table.
	 */
	if (start_peer == 0)
		peer = peer_hash[HASH_ADDR(addr)];
	else
		peer = start_peer->next;
	
	while (peer != 0) {
		if (NSRCADR(addr) == NSRCADR(&peer->srcadr)
		    && NSRCPORT(addr) == NSRCPORT(&peer->srcadr))
			return peer;
		peer = peer->next;
	}

	return (struct peer *)0;
}


/*
 * findpeer - find and return a peer in the hash table.
 */
struct peer *
findpeer(srcadr, dstadr, fd)
	struct sockaddr_in *srcadr;
	struct interface *dstadr;
	int fd;
{
	register struct peer *any_inter_peer;
	register struct peer *peer;
	register struct peer *best = (struct peer *) 0;
	int hash;

	findpeer_calls++;

	any_inter_peer = 0;
	hash = HASH_ADDR(srcadr);
	for (peer = peer_hash[hash]; peer != 0; peer = peer->next) {
		if (NSRCADR(srcadr) == NSRCADR(&peer->srcadr)
		    && NSRCPORT(srcadr) == NSRCPORT(&peer->srcadr)) {
			if (peer->dstadr == dstadr) {
				int rfd = (peer->cast_flags & MDF_BCAST) ?
				    dstadr->bfd : dstadr->fd;

				if (rfd == fd)
					return peer;	/* got it! */
				best = peer;
			}
			if (peer->dstadr == any_interface) {

				/*
				 * We shouldn't have more than one
				 * instance of the peer in the table,
				 * but I don't trust this.  Save this
				 * one for later and continue search.
				 */
				if (any_inter_peer == 0)
					any_inter_peer = peer;
				else
					msyslog(LOG_ERR,
		"two instances of default interface for %s in hash table",
					    ntoa(srcadr));
			}

			/* 
			 * Multicast hacks to determine peer when a 
			 * packet arrives and there exists an assoc.
			 * with src in client/server mode
			 */
 			if (((dstadr == any_interface) || (peer->cast_flags &
			     MDF_MCAST)) && peer->flags & FLAG_MCAST2)
				return peer;
		}
	}

 	if(best) {
 		return best;
 	}

	/*
	 * If we didn't find the specific peer but found a wild card,
	 * modify the interface and return him.
	 */
	if (any_inter_peer != 0) {
		any_inter_peer->dstadr = dstadr;
		return any_inter_peer;
	}

	/*
	 * Out of luck.  Return 0.
	 */
	return (struct peer *)0;
}

/*
 * findpeerbyassocid - find and return a peer using his association ID
 */
struct peer *
findpeerbyassoc(assoc)
	int assoc;
{
	register struct peer *peer;
	int hash;

	assocpeer_calls++;

	hash = assoc & HASH_MASK;
	for (peer = assoc_hash[hash]; peer != 0; peer = peer->ass_next) {
		if ((u_short)assoc == peer->associd)
			return peer;	/* got it! */
	}

	/*
	 * Out of luck.  Return 0.
	 */
	return (struct peer *)0;
}

/*
 * unpeer - remove peer structure from hash table and free structure
 */
void
unpeer(peer_to_remove)
	struct peer *peer_to_remove;
{
	int hash;

	hash = HASH_ADDR(&peer_to_remove->srcadr);
	peer_hash_count[hash]--;
	peer_demobilizations++;

#ifdef REFCLOCK
	/*
	 * If this peer is actually a clock, shut it down first
	 */
	if (peer_to_remove->flags & FLAG_REFCLOCK)
		refclock_unpeer(peer_to_remove);
#endif

	if (peer_hash[hash] == peer_to_remove)
		peer_hash[hash] = peer_to_remove->next;
	else {
		register struct peer *peer;

		peer = peer_hash[hash];
		while (peer != 0 && peer->next != peer_to_remove)
			peer = peer->next;
		
		if (peer == 0) {
			peer_hash_count[hash]++;
			msyslog(LOG_ERR, "peer struct for %s not in table!",
			    ntoa(&peer->srcadr));
		} else {
			peer->next = peer_to_remove->next;
		}
	}

	/*
	 * Remove him from the association hash as well.
	 */
	hash = peer_to_remove->associd & HASH_MASK;
	assoc_hash_count[hash]--;
	if (assoc_hash[hash] == peer_to_remove)
		assoc_hash[hash] = peer_to_remove->ass_next;
	else {
		register struct peer *peer;

		peer = assoc_hash[hash];
		while (peer != 0 && peer->ass_next != peer_to_remove)
			peer = peer->ass_next;
		
		if (peer == 0) {
			assoc_hash_count[hash]++;
			msyslog(LOG_ERR,
			    "peer struct for %s not in association table!",
			    ntoa(&peer->srcadr));
		} else {
			peer->ass_next = peer_to_remove->ass_next;
		}
	}

	TIMER_DEQUEUE(&peer_to_remove->event_timer);

	peer_to_remove->next = peer_free;
	peer_free = peer_to_remove;
	peer_free_count++;
}


/*
 * peer_config - configure a new peer
 */
struct peer *
peer_config(srcadr, dstadr, hmode, version, minpoll, maxpoll, flags, ttl, key)
	struct sockaddr_in *srcadr;
	struct interface *dstadr;
	int hmode;
	int version;
	int minpoll;
	int maxpoll;
	int flags;
	int ttl;
	u_int32 key;
{
	register struct peer *peer;

#ifdef DEBUG
	if (debug)
		printf("peer_config: addr %s mode %d version %d minpoll %d maxpoll %d flags %d ttl %d key %lu\n",
		    ntoa(srcadr), hmode, version, minpoll, maxpoll, flags,
		    ttl, (u_long)key);
#endif
	/*
	 * See if we have this guy in the tables already.  If
	 * so just mark him configured.
	 */
	peer = findexistingpeer(srcadr, (struct peer *)0);
	if (dstadr != 0) {
		while (peer != 0) {
			if (peer->dstadr == dstadr)
				break;
			peer = findexistingpeer(srcadr, peer);
		}
	}

	/*
	 * Torque the flags to make sure they're valid
	 */
	flags &= (FLAG_AUTHENABLE|FLAG_PREFER);

	/*
	 * If we found one, just change his mode and mark him configured.
	 */
	if (peer != 0) {
		peer->hmode = (u_char)hmode;
		peer->version = (u_char)version;
		peer->minpoll = (u_char)minpoll;
		peer->maxpoll = (u_char)maxpoll;
		peer->hpoll = peer->minpoll;
		peer->ppoll = peer->minpoll;
		peer->flags = ((u_char)(flags | FLAG_CONFIG)) |
		    (peer->flags & FLAG_REFCLOCK);
		peer->cast_flags = (hmode == MODE_BROADCAST) ?
			IN_CLASSD(ntohl(srcadr->sin_addr.s_addr)) ? MDF_MCAST : MDF_BCAST : MDF_UCAST;
		peer->ttl = (u_char)ttl;
		peer->keyid = key;
		return peer;
	}

	/*
	 * If we're here this guy is unknown to us.  Make a new peer
	 * structure for him.
	 */
	peer = newpeer(srcadr, dstadr, hmode, version, minpoll, maxpoll,
	    ttl, key);
	if (peer != 0)
		peer->flags |= (u_char)(flags|FLAG_CONFIG);
	return peer;
}


/*
 * newpeer - initialize a new peer association
 */
struct peer *
newpeer(srcadr, dstadr, hmode, version, minpoll, maxpoll, ttl, key)
	struct sockaddr_in *srcadr;
	struct interface *dstadr;
	int hmode;
	int version;
	int minpoll;
	int maxpoll;
	int ttl;
	u_int32 key;
{
	register struct peer *peer;
	register int i;

	/*
	 * Some dirt here.  Some of the initialization requires
	 * knowlege of our system state.
	 */
	extern s_fp sys_bdelay;
	extern long sys_clock;
	
	if (peer_free_count == 0)
		getmorepeermem();

	peer = peer_free;
	peer_free = peer->next;
	peer_free_count--;

	/*
	 * Initialize the structure.  This stuff is sort of part of
	 * the receive procedure and part of the clear procedure rolled
	 * into one.
	 *
	 * Zero the whole thing for now.  We might be pickier later.
	 */
	memset((char *)peer, 0, sizeof(struct peer));

	peer->srcadr = *srcadr;
	if (dstadr != 0)
		peer->dstadr = dstadr;
	else if (hmode == MODE_BROADCAST)
		peer->dstadr = findbcastinter(srcadr);
	else
		peer->dstadr = any_interface;
	peer->cast_flags = (hmode == MODE_BROADCAST) ?
		(IN_CLASSD(ntohl(srcadr->sin_addr.s_addr))) ? MDF_MCAST : MDF_BCAST :
		(hmode == MODE_BCLIENT || hmode == MODE_MCLIENT) ?
		(peer->dstadr->flags & INT_MULTICAST) ? MDF_MCAST : MDF_BCAST :
		MDF_UCAST;
	peer->hmode = (u_char)hmode;
	peer->version = (u_char)version;
	peer->minpoll = (u_char)minpoll;
	peer->maxpoll = (u_char)maxpoll;
	peer->hpoll = peer->minpoll;
	peer->ppoll = peer->minpoll;
	peer->ttl = ttl;
	peer->keyid = key;
	peer->estbdelay = sys_bdelay;
	peer->leap = LEAP_NOTINSYNC;
	peer->precision = DEFPRECISION;
	peer->dispersion = NTP_MAXDISPERSE;
	peer->stratum = STRATUM_UNSPEC;
	peer->update = sys_clock;

	for (i = 0; i < NTP_SHIFT; i++) {
		peer->filter_order[i] = i;
		peer->filter_error[i] = NTP_MAXDISPERSE;
	}

	/*
	 * Assign him an association ID and increment the system variable
	 */
	peer->associd = current_association_ID;
	if (++current_association_ID == 0)
		++current_association_ID;

	/*
	 * Note time on statistics timers.
	 */
	peer->timereset = current_time;
	peer->timereachable = current_time;
	peer->timereceived = current_time;

#ifdef REFCLOCK
	if (ISREFCLOCKADR(&peer->srcadr)) {
		/*
		 * We let the reference clock support do clock
		 * dependent initialization.  This includes setting
		 * the peer timer, since the clock may have requirements
		 * for this.
		 */
		if (!refclock_newpeer(peer)) {
			/*
			 * Dump it, something screwed up
			 */
			peer->next = peer_free;
			peer_free = peer;
			peer_free_count++;
			return 0;
		}
	} else {
#endif
	/*
	 * Set up timer.  If initializing, just make sure we start polling
	 * in different 4 second intervals.
	 */
	peer->event_timer.peer = peer;
	peer->event_timer.event_handler = transmit;

	if (initializing) {
		init_peer_starttime += (1 << EVENT_TIMEOUT);
		if (init_peer_starttime >= (u_long)(1 << peer->minpoll))
			init_peer_starttime = (1 << EVENT_TIMEOUT);
		peer->event_timer.event_time = init_peer_starttime;
	} else {
		/*
		 * First expiry is set to eight seconds from now.
		 */
		peer->event_timer.event_time
		    = (1 << (peer->minpoll - 1)) + current_time;
	}
	TIMER_ENQUEUE(timerqueue, &peer->event_timer);
#ifdef REFCLOCK
	}
#endif

	/*
	 * Put him in the hash tables.
	 */
	i = HASH_ADDR(&peer->srcadr);
	peer->next = peer_hash[i];
	peer_hash[i] = peer;
	peer_hash_count[i]++;

	i = peer->associd & HASH_MASK;
	peer->ass_next = assoc_hash[i];
	assoc_hash[i] = peer;
	assoc_hash_count[i]++;

	return peer;
}


/*
 * peer_unconfig - remove the configuration bit from a peer
 */
int
peer_unconfig(srcadr, dstadr)
	struct sockaddr_in *srcadr;
	struct interface *dstadr;
{
	register struct peer *peer;
	int num_found;

	num_found = 0;
	peer = findexistingpeer(srcadr, (struct peer *)0);
	while (peer != 0) {
		if (peer->flags & FLAG_CONFIG
		    && (dstadr == 0 || peer->dstadr == dstadr)) {
			num_found++;
			/*
			 * Tricky stuff here.  If the peer is polling us
			 * in active mode, turn off the configuration bit
			 * and make the mode passive.  This allows us to
			 * avoid dumping a lot of history for peers we
			 * might choose to keep track of in passive mode.
			 * The protocol will eventually terminate undesirables
			 * on its own.
			 */
			if (peer->hmode == MODE_ACTIVE
			    && peer->pmode == MODE_ACTIVE) {
				peer->hmode = MODE_PASSIVE;
				peer->flags &= ~FLAG_CONFIG;
			} else {
				unpeer(peer);
				peer = 0;
			}
		}
		peer = findexistingpeer(srcadr, peer);
	}
	return num_found;
}


/*
 * peer_clr_stats - clear peer module stat counters
 */
void
peer_clr_stats()
{
	findpeer_calls = 0;
	assocpeer_calls = 0;
	peer_allocations = 0;
	peer_demobilizations = 0;
	peer_timereset = current_time;
}

/*
 * peer_reset - reset stat counters in a peer structure
 */
void
peer_reset(peer)
	struct peer *peer;
{
	if (peer == 0)
		return;
	peer->sent = 0;
	peer->received = 0;
	peer->processed = 0;
	peer->badauth = 0;
	peer->bogusorg = 0;
	peer->oldpkt = 0;
	peer->seldisptoolarge = 0;
	peer->selbroken = 0;
	peer->seltooold = 0;
	peer->timereset = current_time;
}


/*
 * peer_all_reset - reset all peer stat counters
 */
void
peer_all_reset()
{
	struct peer *peer;
	int hash;

	for (hash = 0; hash < HASH_SIZE; hash++)
		for (peer = peer_hash[hash]; peer != 0; peer = peer->next)
			peer_reset(peer);
}
