/*
 * Public Release 3
 * 
 * $Id: isis_adjacency.c,v 1.2 1997/07/17 22:41:59 chopps Exp $
 */

/*
 * ------------------------------------------------------------------------
 * 
 * Copyright (c) 1996, 1997 The Regents of the University of Michigan
 * All Rights Reserved
 *  
 * Royalty-free licenses to redistribute GateD Release
 * 3 in whole or in part may be obtained by writing to:
 * 
 * 	Merit GateDaemon Project
 * 	4251 Plymouth Road, Suite C
 * 	Ann Arbor, MI 48105
 *  
 * THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF 
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE REGENTS OF THE
 * UNIVERSITY OF MICHIGAN AND MERIT DO NOT WARRANT THAT THE
 * FUNCTIONS CONTAINED IN THE SOFTWARE WILL MEET LICENSEE'S REQUIREMENTS OR
 * THAT OPERATION WILL BE UNINTERRUPTED OR ERROR FREE. The Regents of the
 * University of Michigan and Merit shall not be liable for
 * any special, indirect, incidental or consequential damages with respect
 * to any claim by Licensee or any third party arising from use of the
 * software. GateDaemon was originated and developed through release 3.0
 * by Cornell University and its collaborators.
 * 
 * Please forward bug fixes, enhancements and questions to the
 * gated mailing list: gated-people@gated.merit.edu.
 * 
 * ------------------------------------------------------------------------
 * 
 * Copyright (c) 1990,1991,1992,1993,1994,1995 by Cornell University.
 *     All rights reserved.
 * 
 * THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT
 * LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE.
 * 
 * GateD is based on Kirton's EGP, UC Berkeley's routing
 * daemon	 (routed), and DCN's HELLO routing Protocol.
 * Development of GateD has been supported in part by the
 * National Science Foundation.
 * 
 * ------------------------------------------------------------------------
 * 
 * Portions of this software may fall under the following
 * copyrights:
 * 
 * Copyright (c) 1988 Regents of the University of California.
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms are
 * permitted provided that the above copyright notice and
 * this paragraph are duplicated in all such forms and that
 * any documentation, advertising materials, and other
 * materials related to such distribution and use
 * acknowledge that the software was developed by the
 * University of California, Berkeley.  The name of the
 * University may not be used to endorse or promote
 * products derived from this software without specific
 * prior written permission.  THIS SOFTWARE IS PROVIDED
 * ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 * ------------------------------------------------------------------------
 * 
 * 	Copyright 1991 D.L.S. Associates
 * 
 * 	Permission to use, copy, modify, distribute, and sell this software
 * 	and its documentation for any purpose is hereby granted without
 * 	fee, provided that the above copyright notice appear in all copies
 * 	and that both that copyright notice and this permission notice
 * 	appear in supporting documentation, and that the name of D.L.S. not
 * 	be used in advertising or publicity pertaining to distribution of
 * 	the software without specific, written prior permission.  D.L.S.
 * 	makes no representations about the suitability of this software for
 * 	any purpose.  It is provided "as is" without express or implied
 * 	warranty.
 * 
 * 	D.L.S. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * 	INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
 * 	NO EVENT SHALL D.L.S.  BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * 	CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
 * 	OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
 * 	NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 * 	CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 * 
 * 	Authors:  Robert Hagens and Dan Schuh
 * 
 * 
 */


#include "include.h"
#include "isis_includes.h"

/*
 *	Hash table used to locate adjacencies quickly
 *	Hash is based upon the MAC address and chains adjacency entries
 *	off of each entry in the table.
 */
static AdjacencyEntry *adjHash[AdjHashTableSize];

/* 
 * This list keeps track of adjacencies which must be
 * deleted after next spf run.
 */
OldAdjacencyEntry OldL1AdjList;
OldAdjacencyEntry OldL2AdjList;

void
initOldAdjacencies()
{
	DLLInit(&OldL1AdjList.links);
	DLLInit(&OldL2AdjList.links);
}

void
addOldAdjacency __PF1(adj, AdjacencyEntry *)
{
	OldAdjacencyEntry *adjList;

	if (adj->adjacencyType == L1IS) adjList = &(OldL1AdjList);
	else adjList = &(OldL2AdjList);
        DLLInsque(adj, &adjList->links);
}

void
deleteOldAdjacencies __PF1(level, int)
{
	AdjacencyEntry *adj, *temp;
	OldAdjacencyEntry *adjList;

	adjList = (level == 1) ? &OldL1AdjList : &OldL2AdjList;
	temp = NULL;
	IterateDLListForw(adj, &adjList->links, AdjacencyEntry *) {
		if (temp) task_mem_free(isis_task, temp);
		DLLRemque(adj);
		isisCounters.adjEntries--;
        	if (adj->neighborInfo) {
			task_mem_free(isis_task, adj->neighborInfo);
			isisCounters.adjNeighborInfo--;
		}
		temp = adj;
	}
	if (temp) task_mem_free(isis_task, temp);
}

static u_short 
MACHash __PF1(mac, MACAddress)
{
	u_short	hash = 0;

	hash ^= mac[3];
	hash ^= mac[4];
	hash ^= mac[5];
	
	return(hash);
}

/* insert adj into hash table based upon mac address */
static void
insertAdjByMAC __PF2(adj, AdjacencyEntry *,
		     mac, MACAddress)
{
	u_short indx = MACHash(mac) % AdjHashTableSize;

	adj->hashIndex = indx;
	if (adjHash[indx]) {
		adj->hashNext = adjHash[indx];
	}
	adjHash[indx] = adj;
}

/*
 *	Remove adj from the adjacency hash table
 */
void
removeAdjFromHash(adj)
AdjacencyEntry	*adj;
{
#ifdef	notdef
/* this won't happen */
	if (adj->hashIndex < 0 || adj->hashIndex > AdjHashTableSize) {
		trace_log_tf(isis_trace_options,
			     0,
			     LOG_ERR,
			     ("removeAdjFromHash: bad hash index"));
		return;
	}
#endif	/* notdef */
	if (adjHash[adj->hashIndex] == adj)
		adjHash[adj->hashIndex] = adj->hashNext;
	else {
		AdjacencyEntry	*scan, *lastScan = NULL;
		
		for (scan = adjHash[adj->hashIndex]; scan; lastScan=scan, 
			scan=scan->hashNext) {
			if (scan == adj)
				lastScan->hashNext = adj->hashNext;
		}
	}
}

/* 
 *	return a pointer to (or NULL) the first matching adjacency 
 *	entry in hashtable 
 */
AdjacencyEntry *
locateAdjByMAC(mac)
MACAddress	mac;
{
	u_short indx = MACHash(mac) % AdjHashTableSize;
	return(adjHash[indx]);
}

/*
 *	Remove adj from circuit list, and then re-insert with new
 *	holding time
 */
void
updateAdjHt __PF3(circuit, CircuitEntry *,
		  adj, AdjacencyEntry *,
		  ht, u_short)
{
	switch (adj->adjacencyType) {
		case L1IS:
		case L2IS:
			assert(!DLListEmpty(&circuit->isAdjs->links));
			ageListRemove(circuit->isAdjs, (u_char*) adj);
			adj->holdingTime = ht;
			adj->remainingTime = ht;
			ageListInsert(circuit->isAdjs, (u_char *) adj);
			break;
		case ES:
			assert(!DLListEmpty(&circuit->esAdjs->links));
			ageListRemove(circuit->esAdjs, (u_char*) adj);
			adj->holdingTime = ht;
			adj->remainingTime = ht;
			ageListInsert(circuit->esAdjs, (u_char*) adj);
			break;
		default:
			;
	}
}

/*
 *	Create an adjacency, link into the circuit and insert into
 *	adjacency hash table. If memory is not available to store this
 *	adjacency, then steal the memory from another adjacency.
 */
AdjacencyEntry *
createAdjacency __PF5(
	    circuit, CircuitEntry *,
	    adjType, SystemType,
	    srcMAC, MACAddress,
	    ht, u_short,
	    infoLen, int)
{
	AdjacencyEntry	*adj;

	if ((!(adj = castMalloc(AdjacencyEntry *, sizeof(AdjacencyEntry)))) ||
		(!(adj->neighborInfo = castMalloc(u_char *, infoLen)))) {
		trace_log_tf(isis_trace_options,
			     0,
			     LOG_ERR,
			     ("%s: Adjacency %s: src mac %s: insufficient memory to store", 
			      circuit->name,
			      systemTypeToStr(adjType),
			      IDToStr(srcMAC, 6)));
		return(NULL);
	}
	isisCounters.adjEntries++;
	isisCounters.adjNeighborInfo++;

	adj->circuit = circuit;
	adj->adjacencyType = adjType;
	adj->holdingTime = ht;
	adj->remainingTime = ht;
	copyID(srcMAC, adj->neighborMAC, sizeof(MACAddress));
	insertAdjByMAC(adj, srcMAC);

	switch (adj->adjacencyType) {
		case L1IS:
		case L2IS:
		case IS:
		case Unknown:
			ageListInsert(circuit->isAdjs, (u_char*) adj);
			break;
		case ES:
			ageListInsert(circuit->esAdjs, (u_char*) adj);
			break;
		default:
			;
	}

	return(adj);
}


/*
 *	Called when an adjacency entry expires
 */
int
adjacencyExpired(adj)
AdjacencyEntry	*adj;
{
	CircuitEntry	*c = adj->circuit;
	IFTRACE(T_LANADJ|T_P2PADJ)
		trace_log_tf(isis_trace_options,
			     0,
			     LOG_ERR,
			     ("%s: Adjacency %s: %s %s: timed out", 
			      c->name, systemTypeToStr(adj->adjacencyType),
			      (adj->adjacencyType != ES) ? "SysID" : "MAC",
			      (adj->adjacencyType != ES) ? IDToStr(adj->neighborSysID, 6) : IDToStr(adj->neighborMAC, 6)));
	ENDTRACE
	adjDownEvent(c, adj, HoldingTimerExpired);
	return(0);
}

static int	scanOff;
static AdjacencyEntry	*scan;

AdjacencyEntry *
startAdjScan()
{
	scanOff = 0;
	while (((scan = adjHash[scanOff]) == NULL) && scanOff < AdjHashTableSize)
		scanOff++;
	if (scanOff >= AdjHashTableSize)
		return(NULL);
	else
		return(scan);
}

AdjacencyEntry *
nextAdj()
{
	if (scan->hashNext) {
		return(scan = scan->hashNext);
	} else {
		while (((scan = adjHash[++scanOff]) == NULL) && 
			scanOff < AdjHashTableSize)
			scanOff++;
		if (scanOff >= AdjHashTableSize)
			return(NULL);
		else
			return(scan);
	}
}

#define AgeListCount	((MaximumCircuits*2) + 3)

static AgeList	*ageListBase[AgeListCount];

AgeList *
newAgeList __PF2(
	    deleteFunc, PTIF,
	    offset, u_short)
{
	int		i;
	AgeList	*al;

	for (i=0; i<AgeListCount; i++) {
		if (!ageListBase[i]) {
			al = ageListBase[i] = castMalloc(AgeList *, sizeof(AgeList));
			isisCounters.agelists++;
			DLLInit(&al->links);
			al->delete = deleteFunc;
			al->ttlOffset = offset;
			return(al);
		}
	}
	trace_log_tf(isis_trace_options,
		     0,
		     LOG_ERR,
		     ("out of AgeList entries"));
	task_quit(0);
	return((AgeList *) 0);
}

#define ttl(al, item) (*((u_short *)(((u_char *)item)+(al->ttlOffset))))

void
ageListAge()
{
	int		i;
	AgeList	*al;

	for (i=0; i<AgeListCount; i++) {
		if (al = ageListBase[i]) {
			if (!DLListEmpty(&al->links)) {
				u_char	*item, *nextItem;

				item = DLListForw(&al->links, u_char *);
				/* STEVE - check for 0 before decrementing */
				if ((ttl(al, item) == 0) || (--(ttl(al, item)) == 0)) {
					while (item && (ttl(al, item) == 0)) {
						nextItem = DLListNext(item, &al->links, u_char *);
						DLLRemque(item);
						(al->delete)(item);
						item = nextItem;
					}
				}
			}
		}
	}
	setTimer(1, ageListAge, NULL);
}

/*
 *	Insert item in the age list specified. List is maintained in ascending
 *	order based upon time-to-live. Dis is ya basic
 *	clock queue algorithm
 */
void
ageListInsert(al, item)
AgeList		*al;
u_char		*item;
{
	u_short		remaining = ttl(al, item);
	u_char		*ascan;

	/* consistency check - don't want to insert the same thing twice! */
	IterateDLListForw(ascan, &al->links, u_char *) {
		assert((ascan) && (ascan != item));
	}

	IterateDLListForw(ascan, &al->links, u_char *) {
		if (remaining < ttl(al, ascan)) {
			/* insert before ascan */
			DLLInsque(item, DLListBack(ascan, DLList *));	
			ttl(al, ascan) -= remaining;
			ttl(al, item) = remaining;
			return;
		} else {
			remaining -= ttl(al, ascan);
		}
	}
	DLLInsque(item, DLListBack(&al->links, DLList *));	
	ttl(al, item) = remaining;
}

/*
 *	Remove item from age list 
 */
void
ageListRemove(al, item)
AgeList		*al;
u_char		*item;
{
	u_char		*nextItem;

	assert(!DLListEmpty(&al->links));

	if (ttl(al, item) > 0) {
		/* Must add this remaining time to next item's remaining time */
		if (nextItem = DLListNext(item, &al->links, u_char *)) {
			ttl(al, nextItem) += ttl(al, item);
		}
	}
	DLLRemque(item);
}
