/* 
 * $Id: idrp_rt_qos.c,v 1.2 1995/10/31 17:13:52 skh Exp $ 
 * Merit IDRP release 1.1 (gated 3.5.4).  Copyright (c) 1994 by Merit Network, Inc. 
 */

#include "include.h"
#include "iso.h"
#include "idrp.h"

#ifdef IDRP_QOS

/* idrp_rt_qos.c 
 *
 */ 


/*----------------------------------------------------------------------------
 * The following eight functions do the appropriate thing for each of the eight
 * possible classes of status change gated might hand us.  Here is an enumeration of the 
 * noble eightfold cases (but see the design doc for exhaustive details):
 *
 * case	old route	new route	action
 * ----	---------	---------	-------------------------------------------
 * 1	<none>		IDRP		Advertise.
 * 2	<none>		non-idrp	(does not exist in current version)
 * 3	IDRP		IDRP		Advertise new.  This will implicitly w/draw old.
 * 					Keep old idrpRoute unless it was explicitly 
 * 					withdrawn.
 * 4	non-idrp	non-idrp	(does not exist (local and remote the same))
 * 5	non-idrp	IDRP		(does not exist (local and remote the same))
 * 6	IDRP		non-idrp	(does not exist (local and remote the same))
 * 7	IDRP		<none>		Delete idrpRoute.  Send explicit withdrawal.
 * 8	non-idrp	<none>		(does not exist (local and remote the same)
 * 
 * QOS cases
 * 1	<none>		IDRP		Advertise.
 * 3	IDRP		IDRP		Advertise new.  This will implicitly w/draw old.
 * 					Keep old idrpRoute unless it was explicitly 
 * 					withdrawn.
 * 7	IDRP		<none>		Delete idrpRoute.  Send explicit withdrawal.
 * 					withdrawn.
 */

/*----------------------------------------------------------------------------
 * ph3_qos_case1()
 * 
 * case	old route	               new route 	action
 * ----	---------	---------	-------------------------------------------
 * 1	<none>		IDRP		Advertise.
 */
void 
ph3_qos_case1(p_idrp_rt,p_ann_list,rib_id,newpolicy)
idrpRoute	*p_idrp_rt;
idrp_ann_list	*p_ann_list; 
int		rib_id;
int		newpolicy;
{

	if (newpolicy == IDRP_STATUS_LOCAL_PH3_NEW_ROUTE)
		{
		IDRP_STATUS_BIT_CLEAR(p_idrp_rt,IDRP_STATUS_LOCAL_PH3_NEW_ROUTE);
		}
	trace_tf (idrp_trace_options, TR_NORMAL, 0, ("ph3_qos_case1() called -- new IDRP-learned route"));
	trace_tf (idrp_trace_options, TR_NORMAL, 0, ("We learned it from peer %s.  Route is %s",p_idrp_rt->peer->name,iso_ptoa(&p_idrp_rt->nlri)));

	IDRP_STATUS_BIT_SET(p_idrp_rt,IDRP_STATUS_LOC_RIB); 
	IDRP_QOS_STATUS_BIT_CLEAR(p_idrp_rt,IDRP_STATUS_QOS_CHG);

	/* link through p_next to announce list
	 * anything but test peers
	  */
	
	if (p_idrp_rt->peer->type == IDRP_PEER_TEST)
		{
		/* test peers should be vewy, vewy quiet */
		trace_log_tf (idrp_trace_options, 0, LOG_ERR, ("ph3_qos_case1(): Warning!  Test peer sent route.  Ignoring the route."));


		return;
		}
		
	/* The peer wasn't a test peer, so it must be (EXTERNAL or INTERNAL or LOCAL).  Go ahead and send the change. */

/* 	if (!rtbit_isset(p_idrp_rt->p_rt,idrp_this_node.task->task_rtbit))  
 *	 	{
 *	 	rtbit_set(p_idrp_rt->p_rt,idrp_this_node.task->task_rtbit);
 * 		}
 */

	if (newpolicy)
		{
		/*  check to see if you have new policy, then set for reconfigure
		 */	
 
		int pref;
		pref_t gated_pref;

		if (p_idrp_rt->pref_cal != PREF(p_idrp_rt, &pref, &gated_pref) || idrp_dist_policy_change(p_idrp_rt))
			{
			/* preference has changed for this route, 
			 * so flag it to be sent in the reconfiguration deltas
			 */

				if ((pref == IDRP_INVALID_ROUTE_PREF) &&
				 	(gated_pref == IDRP_GATED_INVALID_ROUTE_PREF) &&
					(p_idrp_rt->pref_cal != IDRP_INVALID_ROUTE_PREF))
				
				  {
				    /* we are going to have to withdraw this route
				     */
				    trace_tf(idrp_trace_options, TR_NORMAL,0,("ph3_case4 (1)  rt=%s, idrp_pref %d, gated_pref %d",iso_ptoa(&p_idrp_rt->nlri),p_idrp_rt->pref_cal,p_idrp_rt->gated_pref)); 
				  p_idrp_rt->pref_cal = pref;
				  p_idrp_rt->gated_pref = gated_pref;
				  link_ann_list(p_idrp_rt,p_ann_list,NLRI_LINKED_P_WITH);
				  return;		  
				  }
			  IDRP_STATUS_BIT_SET(p_idrp_rt,IDRP_STATUS_RECONFIGURE);
			  p_idrp_rt->pref_cal = pref;
			  p_idrp_rt->gated_pref = gated_pref;

			}
		if (idrp_new_route(p_idrp_rt))
			{ 
			/* this is a new route we've not seen, being added to the route list */
			IDRP_STATUS_BIT_SET(p_idrp_rt,IDRP_STATUS_RECONFIGURE);
			}
		}

	link_ann_list(p_idrp_rt,p_ann_list,NLRI_LINKED_P_ANN);	
	IDRP_QOS_STATUS_BIT_CLEAR(p_idrp_rt,(IDRP_STATUS_QOS_NEW));
	return;
}


void
ph3_qos_case3(p_idrp_rt,p_idrp_rt_old,p_ann_list,rib_id,newpolicy)
idrpRoute	*p_idrp_rt;
idrpRoute	*p_idrp_rt_old;
idrp_ann_list	*p_ann_list; 
int		rib_id;
int		newpolicy;
{
	rt_entry	*p_rt,*p_rt_old;

	trace_tf (idrp_trace_options, TR_NORMAL,0,("ph3_qos_case3() called -- replace one IDRP route with another"));

	p_rt = p_idrp_rt->p_rt;
	p_rt_old = p_idrp_rt_old->p_rt;
	p_idrp_rt_old = (idrpRoute *) p_rt_old->rt_idrp;
	IDRP_STATUS_BIT_CLEAR(p_idrp_rt_old,IDRP_STATUS_LOC_RIB); 
	IDRP_STATUS_BIT_CLEAR(p_idrp_rt,(IDRP_STATUS_LOCAL_PH3_NEW_ROUTE));
	IDRP_STATUS_BIT_SET(p_idrp_rt,IDRP_STATUS_LOC_RIB);

	IDRP_QOS_STATUS_BIT_CLEAR(p_idrp_rt,(IDRP_STATUS_QOS_CHG|IDRP_STATUS_QOS_NEW));
	IDRP_QOS_STATUS_BIT_CLEAR(p_idrp_rt_old,IDRP_STATUS_QOS_CHG);
	trace_tf(idrp_trace_options, TR_NORMAL,0,("ph3_qos_case3() - old: %s, status %x qos_status %x",iso_ptoa(&p_idrp_rt_old->nlri),p_idrp_rt_old->status,p_idrp_rt_old->qos_status));
	trace_tf(idrp_trace_options, TR_NORMAL,0,("ph3_qos_case3() - new: %s, status %x qos_status %x",iso_ptoa(&p_idrp_rt->nlri),p_idrp_rt->status,p_idrp_rt->qos_status));
	

/*  	if (!rtbit_isset(p_idrp_rt->p_rt,idrp_this_node.task->task_rtbit))  
 * 		{
 * 		rtbit_set(p_idrp_rt->p_rt,idrp_this_node.task->task_rtbit);
 * 		}
 */ 

	/* Sub case 1 - 
	 *  Route is being damped
	 * 
	 * 	1.1)  old is being withdrawn - link to withdrawl list,
	 *		set the DELETE flag
	 *
	 *		Minimum advertisement routes will handle finding
	 *		1) set STATUS_MIN_ADV_CHG flag on the DAMPED route
	 *		2) coping with the route if it is the damped route	
	 * 
	 *	1.2) old is simply being replaced - link announce list
	 * 		1) link to minadv list, and let minimum route
	 *		   advertisement handle route
	 *		
	 *  Why the withdrawl to the peer if route is damped?
	 *  bad news always travels fast
	 *
	 * if this route was not sent to a peer, 
	 * the send_with_nlri will handle not sending the route 
	 * 
	 */

	if (IDRP_DAMPED_ROUTE(p_idrp_rt))
		{
		/* if the route change was originally withdrawn, link to
		 * withdraw route
		 */

		IDRP_STATUS_BIT_TEST(p_idrp_rt_old,IDRP_STATUS_WITH)
			{
			/* set the replace flag on the old route to
			 * indicate that a phase3 replacement has been found
			 *
			 * minimum route advertisement processing
			 */
			trace_tf(idrp_trace_options, TR_NORMAL,0,("case 3 damped route, with set, link old route to withdraw list, set min_adv_chg"));

			IDRP_STATUS_BIT_SET(p_idrp_rt_old, (IDRP_STATUS_REPLACE | IDRP_STATUS_DELETE)); 
			IDRP_STATUS_BIT_SET(IDRP_DAMPED_ROUTE(p_idrp_rt), IDRP_STATUS_MIN_ADV_CHG);	
			IDRP_STATUS_BIT_CLEAR(p_idrp_rt,IDRP_STATUS_REPLACE);
			link_ann_list(p_idrp_rt_old,p_ann_list,NLRI_LINKED_P_WITH);
			}
		else
			{
			/* not withdraw, so let regular minimum route
			 * advertisement testing work
			 */
			
			trace_tf(idrp_trace_options, TR_NORMAL,0,(" case3 damped route, with not set, link route to annlist "));

		
			/* for new policy set reconfigure on any truly changed
			 * route
			 */

			if (newpolicy)
				{
				/* check to see if we have the same routes reconfigured 
				 */

				if (p_idrp_rt == p_idrp_rt_old)
					{
					int pref;
					pref_t gated_pref;

					/* check to see if this is needed 
					 */
 
					if ((p_idrp_rt->pref_cal != PREF(p_idrp_rt, &pref, &gated_pref)) || idrp_dist_policy_change(p_idrp_rt))
						{


						  if ((pref == IDRP_INVALID_ROUTE_PREF) &&
						      (gated_pref == IDRP_GATED_INVALID_ROUTE_PREF) &&
						      (p_idrp_rt->pref_cal != IDRP_INVALID_ROUTE_PREF))
				
						    {
						      /* we are going to have to withdraw this route
						             */
						      trace_tf(idrp_trace_options, TR_NORMAL,0,("qos_ph3_case3 (1)  rt=%s, idrp_pref %d, gated_pref %d",iso_ptoa(&p_idrp_rt->nlri),p_idrp_rt->pref_cal,p_idrp_rt->gated_pref)); 
						      p_idrp_rt->pref_cal = pref;
						      p_idrp_rt->gated_pref = gated_pref;
						      link_ann_list(p_idrp_rt,p_ann_list,NLRI_LINKED_P_WITH);
						      return;		  
						    }
						IDRP_STATUS_BIT_SET(p_idrp_rt,IDRP_STATUS_RECONFIGURE);
						p_idrp_rt->pref_cal = pref;
						p_idrp_rt->gated_pref = gated_pref;
						}
					}
				else
					{
					IDRP_STATUS_BIT_SET(p_idrp_rt,IDRP_STATUS_RECONFIGURE);
					}
				}
			link_ann_list(p_idrp_rt,p_ann_list,NLRI_LINKED_P_ANN);

			}
		return;
		}

	/* Sub case 2/3 
	 * Route is not being damped and old route, and has delete
	 * - need to delete route after it has been replaced
	 *   in distribution  
	 * - the min_adv processing sets the DEL_SEND flag 
	 *  
	 * SubCase 3  - simple replace one idrp
	 *  	        route for another of the same preference
	 *
	 * replaced save so that if route cannot be distributed
	 *   - it will be replaced 
	 */

	if (p_idrp_rt_old != p_idrp_rt)
		{	
		IDRP_STATUS_BIT_SET(p_idrp_rt,IDRP_STATUS_REPLACE);
		IDRP_STATUS_BIT_SET(p_idrp_rt_old,IDRP_STATUS_REPLACE);
		p_idrp_rt->p_replace = p_idrp_rt_old;
		trace_tf(idrp_trace_options, TR_NORMAL,0,("ph3_qos_case3 -  announce list linked with current route and old as replace route")); 
		if (newpolicy)
			{
			IDRP_STATUS_BIT_SET(p_idrp_rt,IDRP_STATUS_RECONFIGURE);
			}

		}
	else
		{
		if (newpolicy)
			{
			int pref;
			pref_t gated_pref;

			/* check to see if this is needed 
			 */
 
			if ((p_idrp_rt->pref_cal != PREF(p_idrp_rt, &pref, &gated_pref)) || idrp_dist_policy_change(p_idrp_rt))
				{

				if ((pref == IDRP_INVALID_ROUTE_PREF) &&
				    (gated_pref == IDRP_GATED_INVALID_ROUTE_PREF) &&
				    (p_idrp_rt->pref_cal != IDRP_INVALID_ROUTE_PREF))
				
				    {
				    /* we are going to have to withdraw this route
				         */
				    trace_tf(idrp_trace_options, TR_NORMAL,0,("qos_ph3_case3 (1)  rt=%s, idrp_pref %d, gated_pref %d",
						      iso_ptoa(&p_idrp_rt->nlri),p_idrp_rt->pref_cal,p_idrp_rt->gated_pref)); 
				    p_idrp_rt->pref_cal = pref;
				    p_idrp_rt->gated_pref = gated_pref;
				    link_ann_list(p_idrp_rt,p_ann_list,NLRI_LINKED_P_WITH);
				    return;		  
				    }
				IDRP_STATUS_BIT_SET(p_idrp_rt,IDRP_STATUS_RECONFIGURE);
				p_idrp_rt->pref_cal = pref;
				p_idrp_rt->gated_pref = gated_pref;
				}
			}
		}	

	link_ann_list(p_idrp_rt,p_ann_list,NLRI_LINKED_P_ANN);
	return;
}


/*----------------------------------------------------------------------------
 * ph3_qos_case7()
 *
 * case	old route	new route	action
 * ----	---------	---------	-------------------------------------------
 * 7	IDRP		<none>		Delete idrpRoute.  Send explicit withdrawal.
 */
void
ph3_qos_case7(p_idrp_rt,p_ann_list,rib_id,newpolicy)
idrpRoute	*p_idrp_rt;
idrp_ann_list	*p_ann_list; 
int		rib_id;	
int		newpolicy;
{
	idrpRoute	*p_idrp_rt_old = NULL;
	u_int		rtbit;


	

	trace_tf (idrp_trace_options, TR_NORMAL,0,("ph3_qos_case7() called -- deleting IDRP-learned route nlri %s",iso_ptoa(&p_idrp_rt->nlri)));
 
	/* tag it for explicit withdrawal */

	IDRP_STATUS_BIT_CLEAR(p_idrp_rt,(IDRP_STATUS_LOC_RIB));
	IDRP_STATUS_BIT_SET(p_idrp_rt, (IDRP_STATUS_DEL_SEND|IDRP_STATUS_WITH));

	/* clear status changes for QOS
 	 */

	IDRP_QOS_STATUS_BIT_CLEAR(p_idrp_rt,IDRP_STATUS_QOS_CHG);


	/* if damped route
	 * 1) check for damped route being this route - if so skip setting
	 *    of flag
	 *
	 * 2) check for no routes being announced out for
	 *    this route out  - if routes out, skip setting of flag 
	 *  
	 * 3) set p_damped_clear - to the damped route so that
	 *    the send_with will handle it
	 *  
	 */

	{
	idrpRoute *p_idrp_damped_rt = IDRP_DAMPED_ROUTE(p_idrp_rt);

	if ((p_idrp_damped_rt != (idrpRoute *) NULL) &&
		(p_idrp_damped_rt != p_idrp_rt) &&
		(idrp_any_rt_anns(p_idrp_rt) == FALSE) &&
		(idrp_any_rt_anns(p_idrp_damped_rt)))   
		{
		trace_tf(idrp_trace_options, TR_NORMAL,0,("ph3_qos_case7(): Setting p_damped_clear on %x", p_idrp_rt));
		p_idrp_rt->p_damped_clear = p_idrp_damped_rt; 
		IDRP_STATUS_BIT_SET(p_idrp_damped_rt, IDRP_STATUS_WITH);
		}
	else
		{
		if (p_idrp_damped_rt == (idrpRoute *) NULL)
			trace_tf(idrp_trace_options, TR_NORMAL,0,("ph3_qos_case7(): p_idrp_damped_rt == NULL"));
		if (p_idrp_damped_rt == p_idrp_rt)
			trace_tf(idrp_trace_options, TR_NORMAL,0,("ph3_qos_case7(): p_idrp_damped_rt == p_idrp_rt"));
		if (idrp_any_rt_anns(p_idrp_rt) != FALSE)
			trace_tf(idrp_trace_options, TR_NORMAL,0,("ph3_qos_case7(): idrp_any_rt_anns(p_idrp_rt) did not return FALSE"));
		if (idrp_any_rt_anns(p_idrp_damped_rt) == FALSE)
			trace_tf(idrp_trace_options, TR_NORMAL,0,("ph3_qos_case7(): idrp_any_rt_anns(p_idrp_damped_rt) did return FALSE"));
		}
	}
	
	 
	/* and link it to the withdraw list */

	link_ann_list(p_idrp_rt,p_ann_list,NLRI_LINKED_P_WITH);
	return; 	
}


int
qos_in_ann_list(p_ann_list)
idrp_ann_list *p_ann_list;
{
idrp_ann_list *p_atl;

	ANN_LIST(p_atl,p_ann_list)
		{
		if (p_atl->p_attr->rib_id > RIB_ID_DEFAULT)
			{
			return(TRUE); 
			}
		} ANN_LIST_END;		

	return(FALSE);
}

void
idrp_process_qos(tip,interval)
task_timer	*tip;
time_t		interval;
{
	/* walk through and process any changes to the
	 * qos ribs - that come in from 
	 */ 

	if (qos_rib_changed)
		{
		idrp_qos_chg_rib();
		qos_rib_changed = FALSE;
		}	

	task_timer_reset(idrp_this_node.idrp_qos_timer);	
}


/* check for any changes in the qos 
 * rib
 */

void
idrp_qos_chg_rib()
{
FILE *fd = NULL;
int	rib_id;
idrp_ann_list *p_ann;

	/* walk the qos ribs
	 *  via each attribute list and   
	 *  link to announce list the changes in routes
	 * and send out via phase3_rib
	 */

        QOS_RIB_ONLY_LIST(rib_id)
                {
                p_ann = qos_chg_active(rib_id,FALSE);
                phase3_rib(p_ann,rib_id);
		if (p_ann)
			{
			IDRP_MEM_FIT_FREE(p_ann);
			}
                } QOS_RIB_ONLY_LIST_END;

        /* temporary dump of the routes  to file
         * instead of kernel dump
         *
         */

       idrp_qos_kernel_dump(fd);
 	

}
void
idrp_qos_kernel_dump()
{
FILE *fd = NULL;  /* attribute file*/
FILE *fdk = NULL; /* kernel dump file */

  idrp_qos_attr_dump(fd,fdk);
  qos_rib_changed = FALSE;
  task_timer_reset(idrp_this_node.idrp_qos_timer);
}

void
idrp_qos_status_repl(p_idrp_rt,p_idrp_rt_old)
idrpRoute	*p_idrp_rt;
idrpRoute	*p_idrp_rt_old;
{
int	rib_id = p_idrp_rt->p_attr->rib_id;

	/*
	 * Replace the qos status in the header
	 */	 


	IDRP_QOS_STATUS_BIT_TEST(p_idrp_rt_old,IDRP_STATUS_QOS_ACTIVE)
		{
		ACTIVE_QOS_IDRP_ROUTE(p_idrp_rt_old) = p_idrp_rt; 
		}  
	IDRP_QOS_STATUS_BIT_TEST(p_idrp_rt_old,IDRP_STATUS_QOS_LAST_ACTIVE)
		{
		IDRP_QOS_LAST_ACTIVE(p_idrp_rt_old,rib_id) = p_idrp_rt; 
		}  
	trace_tf (idrp_trace_options, TR_NORMAL,0,("idrp_qos_status_repl called nlri %s,(%x,%x)",iso_ptoa(&p_idrp_rt->nlri),p_idrp_rt->status,p_idrp_rt->qos_status));
 

}	
#endif
		
