/* 
 * $Id: idrp_rdpath.c,v 1.4 1995/08/01 20:26:09 sjr Exp $ sjr 
 * 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"
/*
 * idrp_rdpath.c -- sjr
 */

boolean 
policy_and_actual_rdpath_cmp __PF2(p_policy_path, policy_rdpath *, 
			p_actual_path, rdpath_list *)
{
	policy_rdpath *p_policy;
	policy_actual_path_tracking actual;
	boolean synch = FALSE;
	
	p_policy = p_policy_path;
	if (p_actual_path) {
		actual.p_path = p_actual_path->p_start;
		actual.count = actual.p_path->cnt;
		actual.p_array = &(actual.p_path->rdi_list);
		actual.index = 0;

		/* 
		 * For each piece of the policy path, match actual path.
		 * NOTE:  The policy and real path adjustments are done
		 * by match_one() and the other functions which it calls;
		 * hence, the commented-out third argument of the for()
		 * loop control expressions.
		 */
		for (; (p_policy); /* p_policy = p_policy->p_next */)
			/* try to match a piece of the policy */
			if (!match_one(&p_policy, &actual, &synch))
				return FALSE;
	} else {
		/* Can never match */
		return FALSE;
	}

	return TRUE;
}

#define ADVANCE_TO_P_NEXT(p_struct) *(p_struct) = (*(p_struct))->p_next
#define ADVANCE_POLICY(pp_pol) ADVANCE_TO_P_NEXT(pp_pol) 

void
CONSUME_ACTUAL __PF1(p_act, policy_actual_path_tracking *)
{
	if (!p_act->p_path || !p_act->p_path->p_next) {
		p_act->p_path = (rdpath *) NULL;
		p_act->count = 0;
		p_act->p_array = (rdi_array *) NULL;
		p_act->index = 0;
	} else if (p_act->p_path->status == IDRP_RDPATH_SEQ) {
		ADVANCE_ACTUAL(p_act);
	} else { 
		/* IDRP_RDPATH_SET */
		p_act->p_path = p_act->p_path->p_next;
		p_act->count = p_act->p_path->cnt;
		p_act->p_array = &(p_act->p_path->rdi_list);
		p_act->index = 0;
	}

	return;
}

void
ADVANCE_ACTUAL __PF1(p_act, policy_actual_path_tracking *)
{
	if (--(p_act->count)) {
		/* Still some left for this segment. */
		if (p_act->index < IDRP_RD_PATH_ARRAY_SIZE - 1) {
			p_act->index++;
		} else if (!p_act->p_array->p_next) {
			trace_tf(idrp_trace_options, 0, TR_NORMAL, ("%s (%d)--ERROR; malformed actual policy array (needs p_next)!  Bailing...", __FILE__, __LINE__));
			exit(-1);
		} else {
			/* Advance array, index. */
			p_act->p_array = p_act->p_array->p_next;
			p_act->index = 0;
		}
	} else {
		/* Nothing left, so advance. */
		p_act->p_path = p_act->p_path->p_next;
		if (p_act->p_path) {
			p_act->count = p_act->p_path->cnt;
			p_act->p_array = &(p_act->p_path->rdi_list);
		} else {
			p_act->count = 0;
			p_act->p_array = (rdi_array *) NULL;
		}
		p_act->index = 0;
	}
}

boolean
match_one __PF3(pp_policy, policy_rdpath **,
		p_actual, policy_actual_path_tracking *,
		p_synch, boolean *)
{
	/* Shouldn't be here if there is no policy path. */
	if (!(pp_policy) || !(*pp_policy)) {
		trace_tf(idrp_trace_options, 0, TR_NORMAL, ("%s (%d)--ERROR; no policy path!  Bailing...", __FILE__, __LINE__));
		exit(-1);
	}

	/* 
	 * This is here because you can match "ANY" EVEN WHEN THERE IS *NO*
	 * REAL POLICY LEFT!
	 */
	if ((*pp_policy)->status == IDRP_RDPATH_ANY) {
		/* Always matches, but there is no synch; consume this policy element. */
		*p_synch = FALSE;
		ADVANCE_POLICY(pp_policy);
		return TRUE;
	}

	if (!p_actual || !p_actual->p_array) {
		/* if there is NO actual path, then there can be NO match */
		return FALSE;
	}

	if (*p_synch == TRUE) {
		switch((*pp_policy)->status & IDRP_RDPATH_TYPE) {
			case (IDRP_RDPATH_SINGLE):
				/* Match is OK */
				/* Advance both policy and real paths. */
				ADVANCE_POLICY(pp_policy);
				break;
			
			case (IDRP_RDPATH_SEQ):
				if (!match_rdi((*pp_policy), p_actual)) 
					return FALSE;

				/* Advance both policy and real paths. */
				ADVANCE_POLICY(pp_policy);
				break;
			
			case (IDRP_RDPATH_SET):
				if (!match_rdset(pp_policy, p_actual))
					return FALSE;

				/* match_rdset() adjusts the policy path. */
				break;

			case (IDRP_RDPATH_OR):
				if (!match_rd_or_list(pp_policy, p_actual))
					return FALSE;

				/* match_rd_or_list() adjusts the policy path. */
				break;
			
			default:
				trace_tf(idrp_trace_options, 0, TR_NORMAL, ("%s (%d)--ERROR; can't handle flag 0x%x.  Bailing...", __FILE__, __LINE__, (*pp_policy)->status & IDRP_RDPATH_TYPE));
		
				exit(-1);
		}

		/* 
		 * Have to advance "consume" the current SEQ RDI or the
		 * whole current SET.
		 */
		CONSUME_ACTUAL(p_actual);
	} else {
		switch((*pp_policy)->status & IDRP_RDPATH_TYPE) {
			case (IDRP_RDPATH_SINGLE):
				/* ERROR! */
				trace_tf(idrp_trace_options, 0, TR_NORMAL, ("%s (%d)--ERROR; encountered IDRP_RDPATH_SINGLE while NO synch!  Bailing...", __FILE__, __LINE__));
				exit(-1);
			
			case (IDRP_RDPATH_SEQ):
				if (!scan_for_rdi((*pp_policy), p_actual))
					return FALSE;

				ADVANCE_POLICY(pp_policy);
				break;
			
			case (IDRP_RDPATH_SET):
				if (!scan_for_rdset(pp_policy, p_actual))
					return FALSE;

				break;
			
			case (IDRP_RDPATH_OR):
				if (!scan_for_rdi_in_or_list(pp_policy, p_actual))
					return FALSE;
				break;
		}
		/* If we're here, then there is a match, and we are now synch'd. */ 
		*p_synch = TRUE;
	}

	return TRUE;
}

rdi *
extract_rdi_from_rdpath __PF2(p_path, rdpath *,
			      index, int)
{
	rdi_array *p_array;

	if (!p_path)
		return ((rdi *) NULL);

	p_array = &(p_path->rdi_list);

	/* Get to the correct array. */
	while (index / IDRP_RD_PATH_ARRAY_SIZE) {
		index -= IDRP_RD_PATH_ARRAY_SIZE;
		p_array = p_array->p_next;
	}
	
	return (p_array->p_rdi_array[index]);
}

rdi *
extract_rdi __PF1(p_act, const policy_actual_path_tracking *)
{
	if (!p_act)
		return ((rdi *) NULL);

	return (extract_rdi_with_index(p_act, p_act->index));
}

rdi *
extract_rdi_with_index __PF2(p_act, const policy_actual_path_tracking *,
			     index, int)
{
	rdi_array *p_array;

	if (!p_act)
		return ((rdi *) NULL);

	p_array = p_act->p_array;

	/* Get to the correct array. */
	while (index / IDRP_RD_PATH_ARRAY_SIZE) {
		index -= IDRP_RD_PATH_ARRAY_SIZE;
		p_array = p_array->p_next;
	}

	return (p_array->p_rdi_array[index]);
}

boolean
find_rdi_in_seq __PF2(p_rdi, const rdi *,
		      p_actual, policy_actual_path_tracking *)
{
	boolean found = FALSE;
	int i;

	if (!p_actual || (p_actual->p_path->status != IDRP_RDPATH_SEQ)) {
		return(FALSE);
	}

	for (i = p_actual->index; i < p_actual->count; i++) {
		if (!RDI_CMP(p_rdi, extract_rdi_with_index(p_actual, i))) {
			found = TRUE;
			/* reset index */
			p_actual->index = i;
			break;
		}
	}

	return(found);
}

boolean
find_rdi_in_set __PF2(p_rdi, const rdi *,
		      p_path, rdpath *)
{
	boolean found = FALSE;
	int i;

	if (!p_path || (p_path->status != IDRP_RDPATH_SET)) {
		return found;
	}

	for (i = 0; i < p_path->cnt; i++) {
		if (!RDI_CMP(p_rdi, extract_rdi_from_rdpath(p_path, i))) {
			found = TRUE;
			break;
		}
	}
		
	return(found);
}

boolean
match_rdi __PF2(p_pol, const policy_rdpath *,
	       	p_act, const policy_actual_path_tracking *)

{
	boolean answer;

	if (!((p_pol) && (p_act)))
		return(FALSE);
	
	switch (p_act->p_path->status) {
		case (IDRP_RDPATH_SEQ):
			/* skh - Steve's compare doesn't work here
			 * answer = !RDI_CMP(p_pol->p_rdi, extract_rdi(p_act));
			 */

			answer = (p_pol->p_rdi == extract_rdi(p_act));
			break;

		case (IDRP_RDPATH_SET):
			answer = find_rdi_in_set(p_pol->p_rdi, p_act->p_path);
			break;
		
		default:
			trace_tf(idrp_trace_options, 0, TR_NORMAL, ("%s (%d)--ERROR; can't handle flag 0x%x.  Bailing...", __FILE__, __LINE__, p_pol->status & IDRP_RDPATH_TYPE));
			exit(-1);
	}
	
	return(answer);
}

boolean
match_rdset __PF2(pp_pol, policy_rdpath **,
	    	  p_act, policy_actual_path_tracking *)
{
	boolean answer = TRUE, found = FALSE;
	boolean exact_match;
	boolean *p_boolarray;
	int i;

	if (!((pp_pol) && (p_act)))
		return(FALSE);
	
	/* If the actual path is not a SET, no match */
	if (p_act->p_path->status != IDRP_RDPATH_SET)
		return(FALSE);
	
	exact_match = BIT_TEST((*pp_pol)->status, IDRP_RDPATH_SUBSET);

	/* 
	 * Since an RD SET is inherently unordered, all of these SETs are
	 * equivalent:
	 *
	 * [ 0x490000 0x500000 0x480000 ]
	 * [ 0x500000 0x490000 0x480000 ]
	 * [ 0x480000 0x490000 0x500000 ], etc.
	 *
	 * SO--in order to match these sorts of lists correctly, we have to 
	 * keep track of whether or not we've matched an element of the actual
	 * list against an element of the policy list.  To do this, we set up
	 * an array of booleans to tell us whether or not such a match has
	 * already been made.
	 */
	p_boolarray = (boolean *) idrp_local_mem_fit(p_act->count * sizeof(boolean));
	for (i = p_act->count; i;) {
		p_boolarray[--i] = FALSE;
	}

	/* 
	 * For each element of the SET in the policy path:
	 */
	while((*pp_pol) && ((*pp_pol)->status == IDRP_RDPATH_SET)) {
		/* look through the real elements... */
		for (i = 0; i < p_act->count; i++) {
			/* 
			 * If we haven't already matched on the real RDI,
			 * and it matches the current policy RDI...
			 */
			if (!p_boolarray[i] && !RDI_CMP((*pp_pol)->p_rdi, extract_rdi_with_index(p_act, i))) {
				/* 
				 * then:  
				 *   1) set the boolean flag for this value,
				 *   2) advance the sense of "how many actual
				 *      elements do I have left?"
				 *   3) set the "found" flag to TRUE
				 *   4) break out of the for loop
				 */
				p_boolarray[i] = TRUE;
				found = TRUE;
				break;
			}
				
		}

		/*
		 * If we didn't find a match for this policy RDI, quit and
		 * return FALSE; otherwise, reset "found" flag, advance the
		 * policy, and try again.
		 */
		if (!found) {
			answer = FALSE;
			break;
		} else {
			found = FALSE;
			ADVANCE_POLICY(pp_pol);
		}
	}
		
	/*
	 * This next piece of code checks the policy's flag to
	 * see what sort of SET match is being sought:
	 *
	 * 	1) Match must be exact.
	 *	2) Policy RD SET can be a subset of the real RD SET.
	 *
	 * If the former, then we need this check.
	 */
	if (exact_match) {
		/*
	 	 * If there are any extra members of the actual SET, 
		 * then we've failed.
	 	 */
		for (i = p_act->count; i;) {
			if (p_boolarray[--i] == FALSE) {
				answer = FALSE;
				break;
			}
		}
	}

	/* Free the array. */
	idrp_mem_fit_free((void **) &p_boolarray, p_act->count * sizeof(boolean));

	return(answer);
}

boolean
match_rd_or_list __PF2(pp_pol, policy_rdpath **,
	    	       p_act, policy_actual_path_tracking *)
{
	boolean found = FALSE;
	rdi *p_rdi; 
	int i;

	if (!((pp_pol) && (p_act)))
		return(FALSE);

	switch(p_act->p_path->status) {
		/*
		 * If the actual path is a SEQ element, then try to match it
		 * with anything in the OR list.  We have to consume the
		 * entire policy OR list, so we keep on calling ADVANCE_POLICY()
		 * even after we've made the match.
		 */
		case(IDRP_RDPATH_SEQ):
			p_rdi = extract_rdi(p_act);
			for (; 
			    (*pp_pol) && ((*pp_pol)->status == IDRP_RDPATH_OR); 
			    (*pp_pol) = (*pp_pol)->p_next) {
				if (!found) {
					if (!RDI_CMP((*pp_pol)->p_rdi, p_rdi)) {
						found = TRUE;
						break;
					}
				}
				ADVANCE_POLICY(pp_pol);
			}
			break;
			
		/*
		 * If the actual path is a SET, then we have to match what is
		 * essentially one OR list against another...
		 * If we match, we consume all of the policy OR list *AND*
		 * the current actual list element (the whole SET).
		 */
		case(IDRP_RDPATH_SET):
			for (;
			    (*pp_pol) && ((*pp_pol)->status == IDRP_RDPATH_OR); 
			    (*pp_pol) = (*pp_pol)->p_next) {
				if (!found) {
					for (i = 0; i < p_act->count; i++) {
						if (!RDI_CMP((*pp_pol)->p_rdi, extract_rdi_with_index(p_act, i))) {
						found = TRUE;
						break;
						}
					}
				}
				ADVANCE_POLICY(pp_pol);
			}
			break;

		default:
			trace_tf(idrp_trace_options, 0, TR_NORMAL, ("%s (%d)--ERROR; can't handle flag 0x%x.  Bailing...", __FILE__, __LINE__, p_act->p_path->status & IDRP_RDPATH_TYPE));
			exit(-1);
	}

	return found;
}

boolean
scan_for_rdi __PF2(p_pol, const policy_rdpath *,
	    	   p_act, policy_actual_path_tracking *)
{
	boolean found = FALSE;

	if (!((p_pol) && (p_act)))
		return(FALSE);

	while (!found) {
		if (match_rdi(p_pol, p_act)) {
			found = TRUE;
		}

		/* Eat it, whether or not it matched. */
		CONSUME_ACTUAL(p_act);

		/* 
		 * If it hasn't been found and there is nowhere else to look,
		 * declare a lack of victory...
		 */
		if (!found && (!p_act->p_path))
				return(FALSE);
	}

	/* The only way to reach this code. */
	return(TRUE);
}

boolean
scan_for_rdset __PF2(pp_pol, policy_rdpath **,
	    	     p_act, policy_actual_path_tracking *)
{
	boolean found = FALSE;
	policy_rdpath **pp_pol_tmp;

	if (!((pp_pol) && (p_act)))
		return(FALSE);

	while (!found) {
		/*
		 * Have to reset arg, as match_rd_or_list() consumes 
		 * all of the IDRP_RDPATH_SET elements.
		 */
		pp_pol_tmp = pp_pol;
		if (match_rdset (pp_pol_tmp, p_act)) {
			found = TRUE;
		}

		/* Eat it, whether or not it matched. */
		CONSUME_ACTUAL(p_act);

		/* 
		 * If it hasn't been found and there is nowhere else 
		 * to look, declare a lack of victory...
		 */
		if (!found && (!p_act->p_path))
				return(FALSE);
	}

	/* 
	 * Reset argument variable to correct value, to communicate changed 
	 * policy path to the outside world. 
	 */
	*pp_pol = *pp_pol_tmp;

	/* The only way to reach this code. */
	return(TRUE);
}

boolean
scan_for_rdi_in_or_list __PF2(pp_pol, policy_rdpath **,
	    	   	      p_act, policy_actual_path_tracking *)
{
	boolean found = FALSE;
	policy_rdpath **pp_pol_tmp;
	policy_actual_path_tracking *p_act_tmp;
	int i;

	if (!((pp_pol) && (p_act)))
		return(FALSE);

	/* For each member of the OR list... */
	while ((*pp_pol) && (*pp_pol)->status == IDRP_RDPATH_OR) {
		/* Try to match this member against the entire actual path. */
		while (!found) {
			/*
			 * Have to reset arg, as match_rd_or_list() consumes 
			 * all of the IDRP_RDPATH_SET elements.
			 */
			pp_pol_tmp = pp_pol;
			if (match_rd_or_list(pp_pol_tmp, p_act_tmp)) {
				found = TRUE;
			}

			/* Eat it, whether or not it matched. */
			CONSUME_ACTUAL(p_act_tmp);

			/* 
		  	 * If it hasn't been found and there is nowhere else 
		 	 * to look, escape this while loop, reset the "actual"
			 * path, and look for another member of the OR list...
		         */
		        if (!found && (!p_act->p_path)) {
				/* Reset the actual path and continue. */
				p_act_tmp = p_act;
				break;
			}
		}

		/* Go to next element of policy. */
		ADVANCE_POLICY(pp_pol);
	}


	if (found) {
		/* 
	 	 * Reset argument variable to correct value, to communicate 
		 * changed policy and actual paths to the outside world. 
	 	 */
		*pp_pol = *pp_pol_tmp;
		*p_act = *p_act_tmp;
	}

	return(found);
}

void
rdpath_adv_free __PF1(p_rdpath, struct _policy_rdpath *)
{
    	struct _policy_rdpath *p_path, *p_elem;  

	for (p_elem = p_rdpath, p_path = p_elem->p_next; 
	     p_elem;
	     p_elem = p_path, p_path = ((p_path) ? p_path->p_next : p_path)) {

	     IDRP_MEM_FIT_FREE(p_elem);
	} 

	return;
}
