/*******************************************************************************
 * (c) Copyright 2015 Microsemi Corporation.  All rights reserved.
 *
 *
 *
 * SVN $Revision: $
 * SVN $Date: $
 */
#include <api_oam_sdk/common/types/inc/linked_list.h>
#include <api_oam_sdk/metroam_hal/inc/mr_hal.h>
#include <api_oam_sdk/policer_mef10p2/inc/policer_mef10p2.h>
#include <api_oam_sdk/service_bwp/src/svc_bwp_types.h>
#include <api_oam_sdk/service_evc/src/svc_evc_types.h>
#include <api_oam_sdk/service_interface/src/svc_iface_types.h>
#include <api_oam_sdk/service_l2cp/src/svc_l2cp_types.h>
#include <api_oam_sdk/service_uni/inc/svc_uni.h>
#include <api_oam_sdk/system/inc/system.h>
#include <api_oam_sdk/table_access/inc/table_access.h>
#include <api_oam_sdk/table_access/inc/table_access_constants.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "egl_types.h"
#include "egl_log.h"
#include "egl_assert.h"
#include "osal.h"

static evcEntry_t		evcArray[MAX_NUM_EVCS];
static uint16_t			log_handle				= 0;		// handle used to log messages for this module
static osMutex_t		evcListLock				= NULL;
static bool				evcInitialised			= false;
static evcList_t		evcList;


uint16_t	g_mapList[MR_MAX_VLANS];
uint8_t		g_vLanList[MR_MAX_VLANS];

#define LOCK_EVC_LIST(timeout)	osRecursiveMutexTake(evcListLock,timeout)
#define UNLOCK_EVC_LIST()		osRecursiveMutexGive(evcListLock)

/*
 * This function is used to change the lowest level severity to log for
 * this module. Values of eLOG_LEVEL_DEBUG (lowest) to eLOG_LEVEL_ERROR
 * can be selected. Levels with greater severity than eLOG_LEVEL_ERROR
 * will default to eLOG_LEVEL_ERROR since severity of eLOG_LEVEL_ERROR
 * and higher are non maskable.
 */
void setEvcLogLevel(log_level_t logLevel)
{
	setModuleLogLevel(log_handle,logLevel);
}

/*
 * @brief	This sets the evcEntry values to their default
 *
 * In most cases this should only be done with evcLock enabled
 * to ensure thread safe operation
 */
static void setEvcDefaults(evcEntry_t *evcEntry)
{
	memset(evcEntry,0,sizeof(evcEntry_t));
	evcEntry->evcCtxt.evcNum 			= INVALID_EVC_NUM;
	evcEntry->cfg.evcServiceType		= evcType_pointToPoint;
	evcEntry->cfg.ceVlanCosPreservation	= preserve;
	evcEntry->cfg.ceVlanIdPreservation	= preserve;
	evcEntry->cfg.unicastDelivery		= dt_unconditional;
	evcEntry->cfg.broadcastDelivery		= dt_unconditional;
	evcEntry->cfg.multicastDelivery		= dt_unconditional;
	evcEntry->cfg.mtuSize				= 1522;
	evcEntry->cfg.adminState			= state_unlocked;

	evcEntry->uniCfg[wanPort].evcPerUniCfg.ceVlanMap[0] 	= 0;
	evcEntry->uniCfg[wanPort].evcPerUniCfg.serviceType		= st_epl;
	evcEntry->uniCfg[wanPort].evcUniCfg.evcUniType			= eu_unknown;

	evcEntry->uniCfg[lanPort].evcPerUniCfg.ceVlanMap[0] 	= 0;
	evcEntry->uniCfg[lanPort].evcPerUniCfg.serviceType		= st_epl;
	evcEntry->uniCfg[lanPort].evcUniCfg.evcUniType			= eu_unknown;
}

/*
 * @brief Clears the EVC list
 *
 * This should be called to initialise the EVC list.
 * Any EVCs on the list will be lost.
 */
static void clearEvcList(void)
{
	uint16_t	k;

	evcList.evcNextIndex	= 1;
	evcList.evcCount		= 0;

	TAILQ_INIT(&evcList.llEvcHead);

	// Initialise all entries in the EVC store to defaults
	for (k = 0; k < MAX_NUM_EVCS; k++)
	{
		setEvcDefaults(&evcArray[k]);
	}
	DO_LOG_DEBUG(log_handle,"Initialised the EVC List");
}

/*
 * @brief	Called at device startup to initialise EVC module
 *
 * Safe to call repeatedly.
 */
void evcInit(void)
{
	int				idx;
	uint32_t		row;

	log_init();	// Initialises the logging system if it's not already initialised

	// Get a log handle if we don't already have one
	if (log_handle == 0)
		log_handle = log_register("EVC");

	// Create a mutex to protect the EVC list if we don't already have one
	if (evcListLock == NULL)
	{
		evcListLock = osRecursiveMutexCreate();
		ASSERT_NOT_NULL(evcListLock);
	}

	// If not already initialised clear the EVC list for first use.
	if (!evcInitialised)
	{
		// Set the default logging level to NOTICE
		setModuleLogLevel(log_handle, eLOG_LEVEL_NOTICE);

		DO_LOG_DEBUG(log_handle,"Preparing the EVC List for first use");
		LOCK_EVC_LIST(0);
		clearEvcList();
		UNLOCK_EVC_LIST();
		evcInitialised = true;

		// The Flow num tables must be initialised as the RAM powers up with Random content
		for (idx=0;idx<MAX_NUM_EVCS;idx++)
		{
			for (row=0;row<8;row++)
			{
				// Raw memory writes are used as alternative to setFlowTableDefaults to speed up initialisation
				mrHalRamWrite((idx * 8) + row + FLOW_NUM_TBL_PORT_0_BA, VLAN_INFO_RAM_SEL, FLOW_NUM_TBL_FLOW_NUM_MASK + FLOW_NUM_TBL_COL_AWARE_MASK + FLOW_NUM_TBL_COL_FWD_MASK + (row * 2));
				mrHalRamWrite((idx * 8) + row + FLOW_NUM_TBL_PORT_1_BA, VLAN_INFO_RAM_SEL, FLOW_NUM_TBL_FLOW_NUM_MASK + FLOW_NUM_TBL_COL_AWARE_MASK + FLOW_NUM_TBL_COL_FWD_MASK + (row * 2));
			}
		}
	}
}

/*
 * @brief Convert a EVC MIB index into an evcNum
 */
static evcNum_t	evcIndexToEvcNumNoLocking(evcIndex_t evcIndex)
{
	evcEntry_t	*evcEntryPtr;
	evcNum_t		evcNum = -1;

	// Start searching valid EVCs at the head of the list
	evcEntryPtr = TAILQ_FIRST(&evcList.llEvcHead);
	while (evcEntryPtr != NULL)
	{
		if (evcEntryPtr->evcCtxt.evcIndex == evcIndex)
		{
			evcNum = evcEntryPtr->evcCtxt.evcNum;
			return (evcNum);	//found it
		}
		evcEntryPtr = TAILQ_NEXT(evcEntryPtr,ll);
	}
	return -1;	// evcIndex not found
}

#if 0
/*
 * @brief Convert a EVC MIB index into an evcNum
 */
evcNum_t	_evcIndexToEvcNum(evcIndex_t evcIndex)
{
	evcNum_t evcNum;

	LOCK_EVC_LIST(0);
	evcNum = evcIndexToEvcNumNoLocking(evcIndex);
	UNLOCK_EVC_LIST();
	return evcNum;
}
#endif

/*
 * @brief Read the next entry in the EVC List
 *
 * @param[in] evcIndex of previous entry or 0
 * @return	evcIndex of the next entry or 0 if no more entries
 *
 * Read the next entry in the EVC List. If prevEvcIndex is 0 it
 * returns the evcIndex of the first EVC entry or 0 if there are
 * no EVC entries. If a valid evcIndex is provided it returns
 * the evcIndex of the next EVC in the list.
 */
evcIndex_t svcEvcReadList(evcIndex_t prevEvcIndex)
{
	evcNum_t 	nextEvcNum;
	evcIndex_t	theIndex;

	LOCK_EVC_LIST(0);
	if (prevEvcIndex == 0)
	{
		if (!TAILQ_EMPTY(&evcList.llEvcHead))
		{
			theIndex = TAILQ_FIRST(&evcList.llEvcHead)->evcCtxt.evcIndex;
			UNLOCK_EVC_LIST();
			return theIndex;
		}
	}
	else
	{
		nextEvcNum = evcIndexToEvcNumNoLocking(prevEvcIndex);
		if (nextEvcNum != -1)
		{
			if (TAILQ_NEXT(&evcArray[nextEvcNum],ll) != NULL)
			{
				theIndex = TAILQ_NEXT(&evcArray[nextEvcNum],ll)->evcCtxt.evcIndex;
				UNLOCK_EVC_LIST();
				return theIndex;
			}
		}
	}
	UNLOCK_EVC_LIST();
	return 0;
}

/*
 * @brief	Find the next free EVC Entry
 */
static evcEntry_t* getNextFreeEvcEntry(void)
{
	evcNum_t	k;

	LOCK_EVC_LIST(0);
	for (k = 0; k < MAX_NUM_EVCS; k++)
	{
		if (!evcArray[k].evcCtxt.inUse)
		{
			evcArray[k].evcCtxt.evcNum = k;
			evcArray[k].evcCtxt.inUse = true;
			UNLOCK_EVC_LIST();
			return (&evcArray[k]);
		}
	}
	return NULL;
}

/*
 * @brief	Add a new evcEntry into the EVC List
 */
static void addEvcToList(evcEntry_t *evcEntry)
{
	ASSERT_NOT_NULL(evcEntry);

	TAILQ_INSERT_TAIL(&evcList.llEvcHead,evcEntry,ll);
	evcList.evcCount++;

	// The EVC index can be specified by the user. So
	// it is necessary to keep the evcNextIndex aligned
	if (evcList.evcNextIndex <= evcEntry->evcCtxt.evcIndex)
	{
		evcList.evcNextIndex = evcEntry->evcCtxt.evcIndex + 1;
	}
}

/*
 * @brief	Remove the EVC entry, pointed to by evcEntry, from the EVC List
 */
static void removeEvcFromList(evcEntry_t *evcEntry)
{
	ASSERT_NOT_NULL(evcEntry);

	TAILQ_REMOVE(&evcList.llEvcHead,evcEntry,ll);
	setEvcDefaults(evcEntry);
	evcList.evcCount--;
}


/*
 * @brief Returns the next available EVC index
 */
evcIndex_t	svcEvcGetNextIndex(void)
{
	return evcList.evcNextIndex;
}

/*
 * @brief Test if the supplied EVC Index is valid
 */
static bool isEvcIndexValid(evcIndex_t evcIndex, evcNum_t *evcNum)
{
	evcNum_t	theEvcNum;

	//if ((evcIndex >= evcList.evcNextIndex))
	if ((evcIndex == 0)) // || (evcIndex >= evcList.evcNextIndex))
	{
EVC_INDEX_INVALID:
		DO_LOG_DEBUG(log_handle,"The EVC Index (%lu) was invalid",evcIndex);
		return false;
	}

	// Test is the evcIndex known and get it's associated evcNum
	theEvcNum = evcIndexToEvcNumNoLocking(evcIndex);
	if (theEvcNum != -1)		// If the EVC index is found
	{
		if (evcNum != NULL)
		{
			*evcNum = theEvcNum;
		}
		return(true);
	}
	goto EVC_INDEX_INVALID;
}

/*
 * @brief Clears the filter bit from all VLans on a port if that VLan is not explicitly blocked at the interface
 */
static void blockAllEvcVlans(iFace_t iFace)
{
	uint16_t			mgntVlan;
	uint16_t			v;
	uint32_t			data;

	mgntVlan = getMgntVlan();

	// Update the Vlan Table for this interface to block all Vlans, except the mgntVlan
	readRawVlanPortTbl(0, &data, iFace);
	// It's not already filtered so block it
	data |= VLAN_INFO_RAM_VLAN_FILT_MASK;
	writeRawVlanPortTbl(0, data, iFace);
	for (v=1; v < MR_MAX_VLANS; v++)
	{
		// Read the current EVC table entry
		readRawVlanPortTbl(v, &data, iFace);
		if ((data & VLAN_INFO_RAM_VLAN_FILT_MASK) == 0)
		{
			// It's not already being filtered...
			if (v != mgntVlan)
			{
				// Block all VLans except the mgntVlan!
				data |= VLAN_INFO_RAM_VLAN_FILT_MASK;
				writeRawVlanPortTbl(v, data, iFace);
			}
		}
	}
}

/*
 * @brief Create a new service EVC.
 */
int svcEvcCreate(evcIndex_t evcIndex, evcCfg_t *evcCfg)
{
	evcEntry_t	*evcEntryPtr;
	iFace_t		uni;

	LOCK_EVC_LIST(0);

	// Validate the evcIndex value provided
	if ((evcIndex == 0) || isEvcIndexValid(evcIndex,NULL))
	{
		// If the EVC index provided is valid then that index is already in use and cannot be re-used!
		// 0 is an invalid index.
		UNLOCK_EVC_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid EVC Index (%lu)",evcIndex);
		return (-1);
	}

	// Validate the evcCfg is not NULL
	if (evcCfg == NULL)
	{
		UNLOCK_EVC_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected on EVC %lu due to null pointer for evcCfg value",evcIndex);
		return (-2);
	}

	//Begin testing the fields of the entry...
	// Test the EVC type
	if ((evcCfg->evcServiceType > evcType_rootedMultipoint) || (evcCfg->evcServiceType < evcType_pointToPoint))
	{
		UNLOCK_EVC_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected on EVC %lu due to invalid EVC type (%d)",evcIndex,evcCfg->evcServiceType);
		return(1); //Operation rejected
	}

	// Test the mtuSize
	if ((evcCfg->mtuSize > MAX_MTU_SIZE) || (evcCfg->mtuSize < 1522))
	{
		UNLOCK_EVC_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected on EVC %lu due to invalid mtuSize (%d)",evcIndex,evcCfg->mtuSize);
		return(2); //Operation rejected
	}

	// Test the adminState
	if ((evcCfg->adminState > state_unlocked) || (evcCfg->adminState < state_locked))
	{
		UNLOCK_EVC_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected on EVC %lu due to invalid adminState (%d)",evcIndex,evcCfg->adminState);
		return(4); //Operation rejected
	}

	if (evcCfg->l2cpGrpIndex != 0)
	{
		// l2cpGrpIndex == 0 is not a valid group index in the L2CP table
		// but it is valid here to indicate no L2CP group is assigned
		// If l2cpGrpIndex != 0 then validate the index...
		if (!svcL2cpGrpIsL2cpGrpIndexValid(evcCfg->l2cpGrpIndex) )
		{
			UNLOCK_EVC_LIST();
			DO_LOG_INFO(log_handle,"Operation rejected on EVC %lu due to invalid l2cpGrpIndex (%d)",evcIndex,evcCfg->l2cpGrpIndex);
			return(3); //Operation rejected
		}
	}

	evcEntryPtr = getNextFreeEvcEntry();
	if (evcEntryPtr != NULL)
	{
		// Set the mibIdx to the one provided and update the other values
		evcEntryPtr->evcCtxt.evcIndex = evcIndex;

		// Save the config to context
		memcpy(&evcEntryPtr->cfg, evcCfg, sizeof(evcCfg_t));

		// add the new EVC to the list
		addEvcToList(evcEntryPtr);

		if (evcList.evcCount == 1)
		{
			// Once there is one or more EVCs defined, all vlans (except mgntVlan), on all ports must be blocked/filtered
			for (uni = wanPort; uni <= lanPort; uni++)
			{
				blockAllEvcVlans(uni);
			}
		}
	}
	else
	{
		UNLOCK_EVC_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected due to insufficient resources");
		return (-3);
	}
	UNLOCK_EVC_LIST();
	DO_LOG_NOTICE(log_handle,"Created EVC with index = %lu",evcIndex);
	return (5); // Operation accepted.
}

static void deleteUniFromEvc(iFace_t uni, evcIndex_t evcIndex, evcNum_t	evcNum)
{
	evcUniCfg_t		evcUniCfg;
	uint16_t		v;
	uint16_t		listLen;
	vlanTblEntry_t	vlanTblEntry;
	evcTblEntry_t	evcTblEntry;
	bool			mepOnEvc;

	// First check what UNIs are on this EVC, then 'release' the Vlans for each UNI
	//ASSERT(svcEvcUniRead(uni, evcIndex, &evcUniCfg) == 2,1);
	if (svcEvcUniRead(uni, evcIndex, &evcUniCfg) != 2)
	{
		DO_LOG_CRITICAL(log_handle,"Failed to read data from EVC-UNI uni=%d evc=%d during deleteUniFromEvc -function failed",uni,evcIndex);
		return;
	}

	if (evcArray[evcNum].uniCfg[uni].evcUniCfg.evcUniType != eu_unknown)
	{
		// This UNI is associated with the EVC. Start removing it...
		mepOnEvc = false;
		// Get the list of VLans on the evc-per-uni
		getListFromStringMap(evcArray[evcNum].uniCfg[uni].evcPerUniCfg.ceVlanMap,g_mapList,0,4095,&listLen);
		for (v = 0; v < listLen; v++)
		{
			// For every VLan in the evc-per-uni release the EVC
			readVlanTbl(g_mapList[v], &vlanTblEntry, uni);

			if(vlanTblEntry.evcIdx != evcNum)
			{
				DO_LOG_CRITICAL(log_handle,"On iface %d; evcIdx in VlanTable[%d] = %lu, expected %lu - deleteUniFromEvc failed",uni,g_mapList[v],vlanTblEntry.evcIdx, evcNum);

				// Ensure that the evcIdx value is the expected one
				//ASSERT(vlanTblEntry.evcIdx == evcNum,1);		// TODO: remove after testing.
			}

			vlanTblEntry.evcIdx				= PORT_ASSIGNED_EVC;
			vlanTblEntry.vlanFilt			= true;						// this is set true because, if any EVCs exist all unassigned vlans should be blocked
			vlanTblEntry.vLanFiltRestore	= vlanTblEntry.vlanFilt;
			writeVlanTbl(g_mapList[v], &vlanTblEntry, uni);
		}
		// Reset the EVC table to defaults
		readEvcTbl(evcNum, &evcTblEntry, uni);
		evcTblEntry.cosDscp			= (uint8_t) false;
		evcTblEntry.usePcpEvcCol	= (uint8_t) false;
		evcTblEntry.pushTagType		= pushCtag;
		evcTblEntry.tagAction		= ta_noAction;
		evcTblEntry.txVid			= 0;
		evcTblEntry.cosPreserve		= (uint8_t) true;
		writeEvcTbl(evcNum, &evcTblEntry, uni);
		// Clear the flowNum table back to it's default - blocking state
		setFlowTableDefaults(evcNum, uni, false);
	}
}


void restoreInterfaceFilteredVlans(iFace_t iFace, uint16_t *filtList, uint16_t listLen)
{
	uint16_t			k,v;
	uint32_t			data;
	uint16_t			mgntVlan;

	mgntVlan = getMgntVlan();

	memset(g_vLanList,0,MR_MAX_VLANS);
	for (k=0; k < listLen; k++)
	{
		// only set values in the g_vLanList at the indices in the filtList
		g_vLanList[filtList[k]] = 1;
	}

	// Update the Vlan Table for this interface based on the new VLan list...
	// Treat vlan 0 separately because when mgntVlan is disabled it has a value of 0
	readRawVlanPortTbl(0, &data, iFace);
	if (g_vLanList[0] == 0)
	{
		// Vlan 0 should not be blocked
		data &= ~(VLAN_INFO_RAM_VLAN_FILT_MASK + VLAN_INFO_RAM_VLAN_FILT_RESTORE_MASK);
	}
	else
	{
		data |= (VLAN_INFO_RAM_VLAN_FILT_MASK + VLAN_INFO_RAM_VLAN_FILT_RESTORE_MASK);
	}
	writeRawVlanPortTbl(0, data, iFace);
	// update VLans from 1 to max...
	for (v=1; v < MR_MAX_VLANS; v++)
	{
		// Read the current EVC table entry
		readRawVlanPortTbl(v, &data, iFace);
		if (data & VLAN_INFO_RAM_VLAN_FILT_MASK)
		{
			// This Vlan (v) is currently being filtered
			if (g_vLanList[v] == 0)
			{
				// It's not in the filtered vlan list so un-block it
				data &= ~(VLAN_INFO_RAM_VLAN_FILT_MASK + VLAN_INFO_RAM_VLAN_FILT_RESTORE_MASK);
				writeRawVlanPortTbl(v, data, iFace);
			}
		}
		else
		{
			// This Vlan (v) is currently NOT being filtered
			if ((g_vLanList[v] == 1) && (v != mgntVlan))			// Never block the mgntVlan!
			{
				// It's in the filtered vlan list for this interface so block it
				data |= (VLAN_INFO_RAM_VLAN_FILT_MASK + VLAN_INFO_RAM_VLAN_FILT_RESTORE_MASK);
				writeRawVlanPortTbl(v, data, iFace);
			}
		}
	}
}

/*
 * @brief Clears the filter bit from all VLans on a port if that VLan is not explicitly blocked at the interface
 */
static void unblockNonFilteredEvcVlans(iFace_t iFace)
{
	svcIfaceEglCfg_t	svcIfaceEglCfg;
	uint16_t			listLen;

	// It should be save to call this function, since only reason to fail is invalid iFace value!
	EGL_ASSERT(svcIfaceRead(iFace, NULL, &svcIfaceEglCfg, NULL) == 2,1);

	// It should be safe to parse the vLanFilterList as it was validated when configured...but assert if something goes wrong
	EGL_ASSERT(getListFromStringMap(svcIfaceEglCfg.vLanFilterList,g_mapList,0,4095,&listLen) == true,1);

	restoreInterfaceFilteredVlans(iFace,g_mapList,listLen);
}

/**
 * @brief	Delete the EVC with the supplied evcIndex.
 *
 */
int svcEvcDelete(evcIndex_t evcIndex)
{
	evcNum_t		evcNum;
	iFace_t			uni;

	LOCK_EVC_LIST(0);
	// Test is the evcIndex known and get it's associated evcNum
	if (!isEvcIndexValid(evcIndex, &evcNum))
	{
		UNLOCK_EVC_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid or nonexistent EVC (index = %lu)",evcIndex);
		return (1);	//Operation rejected due to invalid or nonexistent Maintenance Domain
	}

	// Remove all UNIs from the EVC
	for (uni = wanPort; uni <= lanPort; uni++)
	{
		deleteUniFromEvc(uni,evcIndex,evcNum);
	}

	removeEvcFromList(&evcArray[evcNum]);

	if (evcList.evcCount == 0)
	{
		// If there are no EVCs left in the system then un-block all blocked
		// Vlans on both ports, except those on the interface Filter list
		for (uni = wanPort; uni <= lanPort; uni++)
		{
			unblockNonFilteredEvcVlans(uni);
		}
		// If all EVCs are removed then the policer can be disabled.
		polEnPolicing(disable);
	}
	UNLOCK_EVC_LIST();
	DO_LOG_NOTICE(log_handle,"Deleted EVC with index = %lu",evcIndex);
	return (2); // Operation accepted
}

/*
 * @brief Read the service EVC configuration.
 */
int svcEvcRead(evcIndex_t evcIndex, evcCfg_t *evcCfg, evcStatus_t *evcStatus)
{
	evcNum_t		evcNum;

	LOCK_EVC_LIST(0);
	// Test is the evcIndex known and get it's associated evcNum
	if (!isEvcIndexValid(evcIndex, &evcNum))
	{
		UNLOCK_EVC_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid or nonexistent EVC index (%lu)",evcIndex);
		return (1);
	}

	if (evcCfg != NULL)
	{
		memcpy(evcCfg, &evcArray[evcNum].cfg, sizeof(evcCfg_t));
	}

	if (evcStatus != NULL)
	{
		evcStatus->maxMtuSize = MAX_MTU_SIZE;
		evcStatus->maxNumUni = 2;							// TODO: review value later
		evcStatus->operationalState = evcOpSt_enabled;
	}

	UNLOCK_EVC_LIST();
	DO_LOG_DEBUG(log_handle,"Read info for EVC (%lu)",evcIndex);
	return (2); // Operation accepted
}

/**
 * @brief	Write a new value to the selected service EVC parameter.
 */
int svcEvcWrite(evcIndex_t evcIndex, evcWrParam_t wrParam, void *paramValue)
{
	evcNum_t		evcNum;

	LOCK_EVC_LIST(0);
	// Test is the evcIndex known and get it's associated evcNum
	if (!isEvcIndexValid(evcIndex, &evcNum))
	{
		UNLOCK_EVC_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid or nonexistent EVC index (%lu)",evcIndex);
		return (1);
	}

	if (paramValue == NULL)
	{
		UNLOCK_EVC_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected due to NULL value");
		return (3);
	}

	switch(wrParam)
	{
		case evcParam_mtuSize:
		{
			uint16_t	*mtuSizePtr = (uint16_t*)paramValue;

			//check the string length
			if (*mtuSizePtr > MAX_MTU_SIZE)
			{
				UNLOCK_EVC_LIST();
				DO_LOG_INFO(log_handle,"Operation rejected due to invalid mtuSize %d on EVC %lu",*mtuSizePtr,evcIndex);
				return (3);
			}

			if (evcArray[evcNum].cfg.mtuSize != *mtuSizePtr)
			{
				// Write the new value into the EVC
				evcArray[evcNum].cfg.mtuSize = *mtuSizePtr;
				DO_LOG_NOTICE(log_handle,"On EVC with index %lu the mtuSize value was changed to %d",evcIndex,*mtuSizePtr);
			}
			break;
		}
		case evcParam_l2cpGrpIndex:
		{
			l2cpGrpIndex_t	*l2cpGrpIndexPtr = (l2cpGrpIndex_t*)paramValue;
#if SUPPORTED_ON_EVC
			//check the string length
			if (!svcL2cpGrpIsL2cpGrpIndexValid(*l2cpGrpIndexPtr) && (*l2cpGrpIndexPtr != 0))
			{
				UNLOCK_EVC_LIST();
				DO_LOG_INFO(log_handle,"Operation rejected due to invalid or unknown L2CP group index %lu",*l2cpGrpIndexPtr);
				return (3);
			}

			if (evcArray[evcNum].cfg.l2cpGrpIndex != *l2cpGrpIndexPtr)
			{
				// Write the new value into the EVC
				evcArray[evcNum].cfg.l2cpGrpIndex = *l2cpGrpIndexPtr;
				// TODO: Add code to handle L2CP update on EVC
				DO_LOG_NOTICE(log_handle,"On EVC with index %lu the L2CP group index was changed to %d",evcIndex,*l2cpGrpIndexPtr);
			}
#else
			// We only support a value of 0, i.e. NO L2CP, on the EVC
			if(*l2cpGrpIndexPtr != 0)
			{
				UNLOCK_EVC_LIST();
				DO_LOG_INFO(log_handle,"Operation rejected due to invalid or unknown L2CP group index %lu",*l2cpGrpIndexPtr);
				return (3);
			}
#endif
			break;
		}
		default:
		{
			UNLOCK_EVC_LIST();
			DO_LOG_INFO(log_handle,"Operation rejected due to the selected managed object being Read-Only");
			return (2);
			break;
		}
	}
	UNLOCK_EVC_LIST();
	return (4); // Operation accepted

}

bool evcCheckOrCreateEvcForMep(uint16_t Vlan, iFace_t iFace)
{
	vlanTblEntry_t	vlanTblEntry;

	readVlanTbl(Vlan, &vlanTblEntry, iFace);

	// there is no EVC assigned to this Vlan if the index is the PORT_ASSIGNED_EVC
	if (vlanTblEntry.evcIdx == PORT_ASSIGNED_EVC)
	{
		return (true);
	}

	return (false);
}

void setFlowTableDefaults(evcNum_t evcNum, iFace_t iFace, bool pcpDiscard)
{
	flowNumEntry_t	flowNumEntry;
	uint8_t			idx;

	// Clear the flowNum table back to it's default - blocking state
	flowNumEntry.colAware = 1;
	flowNumEntry.colFwd = 1;
	flowNumEntry.flowNum = INVALID_FLOW_NUM;
	// If there are EVCs then pcpDiscard is set to true, if there are no EVCs it is set to false
	flowNumEntry.pcpDiscard = pcpDiscard;
	flowNumEntry.pcpEvcCol = 0;						// Green by default
	for (idx=0;idx<8;idx++)
	{
		flowNumEntry.mapPcpPrio = idx;
		writeFlowNumTbl((evcNum * 8) + idx, iFace, &flowNumEntry);
	}
}

/*
 * @brief Read the service EVC-UNI configuration for the specified interface and EVC.
 */
int svcEvcUniRead(iFace_t uni, evcIndex_t evcIndex, evcUniCfg_t *evcUniCfg)
{
	evcNum_t	evcNum;

	// Test is the iFace known and get it's associated iFace
	if (!isIFaceValid(uni))
	{
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid or nonexistent IFACE index (%lu)",uni);
		return (1);
	}

	LOCK_EVC_LIST(0);
	// Test is the evcIndex known and get it's associated evcNum
	if (!isEvcIndexValid(evcIndex, &evcNum))
	{
		UNLOCK_EVC_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid or nonexistent EVC index (%lu)",evcIndex);
		return (1);
	}

	if (evcUniCfg != NULL)
	{
		memcpy(evcUniCfg,&evcArray[evcNum].uniCfg[uni].evcUniCfg,sizeof(evcUniCfg_t));
	}

	DO_LOG_DEBUG(log_handle,"Read the configuration for EVC UNI index = %lu.%d",evcIndex,uni);
	UNLOCK_EVC_LIST();
	return (2); // Operation successful
}

/*
 * @brief	Write a new value to the selected service EVC-UNI parameter.
 */
int svcEvcUniWrite(iFace_t uni, evcIndex_t evcIndex, evcUniWrParam_t wrParam, void *paramValue)
{
	evcNum_t		evcNum;
	evcTblEntry_t	evcTblEntry;
	svcUniCfg_t		svcUniCfg;
	uint32_t		data;

	// Test is the iFace known and get it's associated iFace
	if (!isIFaceValid(uni))
	{
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid or nonexistent IFACE index (%d)",uni);
		return (1);
	}

	LOCK_EVC_LIST(0);
	// Test is the evcIndex known and get it's associated evcNum
	if (!isEvcIndexValid(evcIndex, &evcNum))
	{
		UNLOCK_EVC_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid or nonexistent EVC index (%lu)",evcIndex);
		return (1);
	}
	if (paramValue == NULL)
	{
		UNLOCK_EVC_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected for EVC-UNI (%lu.%d) due to NULL value",evcIndex,uni);
		return (3);
	}
	switch(wrParam)
	{
		case evcUniParam_evcUniType:
		{
			evcUniType_t	*evcUniTypePtr = (evcUniType_t*)paramValue;

			//check the value is valid
			if ((*evcUniTypePtr < eu_root ) || (*evcUniTypePtr > eu_unknown))
			{
				UNLOCK_EVC_LIST();
				DO_LOG_INFO(log_handle,"Operation rejected due to invalid EVC UNI Type %d on index %lu.%d",*evcUniTypePtr,evcIndex,uni);
				return (3);
			}

			if (evcArray[evcNum].uniCfg[uni].evcUniCfg.evcUniType != *evcUniTypePtr)
			{
				// Write the new value into the EVC
				if (*evcUniTypePtr == eu_unknown)	// This UNI is being removed from the EVC
				{
					deleteUniFromEvc(uni,evcIndex,evcNum);
					DO_LOG_NOTICE(log_handle,"On EVC UNI with index %lu.%d the EVC-UNI was deleted",evcIndex,uni);
				}
				else
				{
					// When the UNI type for the EVC is set to something other than 'none' the EVC table must be updated for the first time
					readEvcTbl(evcNum, &evcTblEntry, uni);
					evcTblEntry.cosPreserve = (evcArray[evcNum].cfg.ceVlanCosPreservation == preserve ? 1 : 0);
					writeEvcTbl(evcNum, &evcTblEntry, uni);

					// When creating the EVC we must determine if the associated UNI has all-to-one bundling
					svcUniRead(uni, &svcUniCfg);
					if (svcUniCfg.bundlingMultiplex == allToOne)
					{
						// set the all-to-one bit in the DEV _CFG reg for the port
						mrHalRegRead(DEV_CFG_REG_ADDR,&data);
						data |= (uni == wanPort ? DEV_CFG_REG_P0_ALL_TO_ONE_MASK : DEV_CFG_REG_P1_ALL_TO_ONE_MASK);
						mrHalRegWrite(DEV_CFG_REG_ADDR,data);
					}
					else
					{
						// clear the all-to-one bit in the DEV _CFG reg for the port
						mrHalRegRead(DEV_CFG_REG_ADDR,&data);
						data &= (uni == wanPort ? ~DEV_CFG_REG_P0_ALL_TO_ONE_MASK : ~DEV_CFG_REG_P1_ALL_TO_ONE_MASK);
						mrHalRegWrite(DEV_CFG_REG_ADDR,data);
					}

					DO_LOG_NOTICE(log_handle,"On EVC UNI with index %lu.%d the EVC-UNI type was changed to %d",evcIndex,uni,*evcUniTypePtr);
				}
				evcArray[evcNum].uniCfg[uni].evcUniCfg.evcUniType = *evcUniTypePtr;

			}
			break;
		}
		default:
		{
			UNLOCK_EVC_LIST();
			DO_LOG_INFO(log_handle,"Operation rejected due to the selected managed object being Read-Only");
			return (2);
			break;
		}
	}
	UNLOCK_EVC_LIST();
	return (4); // Operation accepted
}

/*
 * @brief Read the service Evc Per UNI configuration for the specified interface and UNI.
 */
int svcEvcPerUniRead(iFace_t iFace, evcIndex_t evcIndex, evcPerUniCfg_t *svcEvcPerUniCfg, evcPerUniEglCfg_t *evcPerUniEglCfg)
{
	evcNum_t	evcNum;

	LOCK_EVC_LIST(0);
	// Test is the evcIndex known and get it's associated evcNum
	if (!isEvcIndexValid(evcIndex, &evcNum))
	{
		UNLOCK_EVC_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid or nonexistent EVC index (%lu)",evcIndex);
		return (1);
	}

	// Test is the iFace known and get it's associated iFace
	if (!isIFaceValid(iFace) || (evcArray[evcNum].uniCfg[iFace].evcUniCfg.evcUniType == eu_unknown))
	{
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid or nonexistent IFACE index (%lu)",iFace);
		return (1);
	}

	if (svcEvcPerUniCfg != NULL)
	{
		memcpy(svcEvcPerUniCfg,&evcArray[evcNum].uniCfg[iFace].evcPerUniCfg,sizeof(evcPerUniCfg_t));
	}

	if (evcPerUniEglCfg != NULL)
	{
		memcpy(evcPerUniEglCfg,&evcArray[evcNum].uniCfg[iFace].evcPerUniEglCfg,sizeof(evcPerUniEglCfg_t));
	}
	DO_LOG_DEBUG(log_handle,"Read the configuration for EVC per UNI index = %d.%lu",iFace,evcIndex);
	UNLOCK_EVC_LIST();
	return (2); // Operation successful
}

/*
 * @brief	Write a new value to the selected UNI-EVC parameter.
 */
int svcEvcPerUniWrite(iFace_t iFace, evcIndex_t evcIndex, evcPerUniWrParam_t wrParam, void *paramValue)
{
	evcNum_t		evcNum;
	uint16_t		listLen;
	uint16_t		k,v;
	uint16_t		mgntVlan;
	uint32_t		data;
	int				retVal;
	evcTblEntry_t	evcTblEntry;

	LOCK_EVC_LIST(0);
	// Test is the evcIndex known and get it's associated evcNum
	if (!isEvcIndexValid(evcIndex, &evcNum))
	{
		UNLOCK_EVC_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid or nonexistent EVC index (%lu)",evcIndex);
		return (1);
	}

	// Test is the iFace known and get it's associated iFace
	if (!isIFaceValid(iFace) || (evcArray[evcNum].uniCfg[iFace].evcUniCfg.evcUniType == eu_unknown))
	{
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid or nonexistent UNI index (%lu)",iFace);
		return (1);
	}

	if (paramValue == NULL)
	{
		UNLOCK_EVC_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected for EVC per UNI (%d.%lu) due to NULL value",iFace,evcIndex);
		return (3);
	}

	switch(wrParam)
	{
		case evcPerUniParam_serviceType:
		{
			serviceType_t	*serviceTypePtr = (serviceType_t*)paramValue;

			//check the service type value
			if ((*serviceTypePtr < st_epl) || (*serviceTypePtr > st_evptree))
			{
				UNLOCK_EVC_LIST();
				DO_LOG_INFO(log_handle,"Operation rejected due to invalid EVC per UNI Service Type %d on index %d.%lu",*serviceTypePtr,iFace,evcIndex);
				return (3);
			}

			if (evcArray[evcNum].uniCfg[iFace].evcPerUniCfg.serviceType != *serviceTypePtr)
			{
				// Write the new value into the EVC
				evcArray[evcNum].uniCfg[iFace].evcPerUniCfg.serviceType = *serviceTypePtr;
				DO_LOG_NOTICE(log_handle,"On EVC per UNI with index %d.%lu the Service type was changed to %d",iFace,evcIndex,*serviceTypePtr);
				// TODO: add code to deal with a change of EVC UNI service type
			}
			break;
		}
		case evcPerUniParam_ceVlanMap:
		{
			char	*ceVlanMapPtr = (char*)paramValue;
			//check the string length
			if (strlen(ceVlanMapPtr) > 256)
			{
				UNLOCK_EVC_LIST();
				DO_LOG_INFO(log_handle,"Operation rejected due to invalid string length for ceVlanMap");
				return (3);
			}

			if (strcmp(evcArray[evcNum].uniCfg[iFace].evcPerUniCfg.ceVlanMap,ceVlanMapPtr) == 0)
			{
				// No change in value detected, nothing to do
				break;
			}

			// see can we parse the string
			if (!getListFromStringMap(ceVlanMapPtr,g_mapList,0,4095,&listLen))
			{
				UNLOCK_EVC_LIST();
				DO_LOG_INFO(log_handle,"Operation rejected due to invalid string content for ceVlanMap");
				return (3);
			}
			mgntVlan = getMgntVlan();
			memset(g_vLanList,0,MR_MAX_VLANS);
			for (k=0; k < listLen; k++)
			{
				// only set values in the g_vLanList at the indices in the mapList
				g_vLanList[g_mapList[k]] = 1;

				// Ensure that the Management Vlan is NOT in the ceVlanMap list
				if (mgntVlan != 0)	// mgntVlan is valid only if not 0
				{
					if (g_mapList[k] == mgntVlan)
					{
						UNLOCK_EVC_LIST();
						DO_LOG_INFO(log_handle,"Operation rejected because the ceVlanMap contains the Management Vlan");
						return (-1);
					}
				}
			}

			// Update the Vlan Table for this interface based on the new VLan list
			for (v=0; v < MR_MAX_VLANS; v++)
			{
				// Read the current EVC table entry
				readRawVlanPortTbl(v, &data, iFace);
				if (((data & VLAN_INFO_RAM_EVC_IDX_MASK) >> VLAN_INFO_RAM_EVC_IDX_OFFSET) == evcNum)
				{
					// This Vlan (v) is currently assigned to this EVC (evcNum) (e.g it was just removed from the ceVlanMap)
					if (g_vLanList[v] == 0)
					{
						// It's not in the EVC's vlan list so un-assign it and filter this VLan
						data &= ~VLAN_INFO_RAM_EVC_IDX_MASK;
						data |= ((((uint32_t)PORT_ASSIGNED_EVC) << VLAN_INFO_RAM_EVC_IDX_OFFSET) & VLAN_INFO_RAM_EVC_IDX_MASK) | VLAN_INFO_RAM_VLAN_FILT_MASK;
						writeRawVlanPortTbl(v, data, iFace);
						//DO_LOG_DEBUG(log_handle,"On EVC UNI %d.%lu, vlan %d was removed",iFace,evcIndex,v);
					}
				}
				else
				{
					// This Vlan (v) is currently NOT assigned to this EVC (evcNum) (e.g it was just added to the ceVlanMap)
					if (g_vLanList[v] == 1)
					{
						if (((data & VLAN_INFO_RAM_EVC_IDX_MASK) >> VLAN_INFO_RAM_EVC_IDX_OFFSET) != PORT_ASSIGNED_EVC)
						{
							DO_LOG_ERROR(log_handle,"Operation rejected because the vlan %d in the ceVlanMap (%s) is already used by another service (0x%X)",v,ceVlanMapPtr, data);
							int k;
							// Not good - the table entry seems to already be in use. Wind back the current changes and throw an error.
							for (k = v-1; k >= 0; k--)
							{
								if (g_vLanList[k] == 1)
								{
									// Undo any changes - clear the evc index to the default and set the filter bit for this VLan
									data &= ~VLAN_INFO_RAM_EVC_IDX_MASK;
									data |= ((((uint32_t)PORT_ASSIGNED_EVC) << VLAN_INFO_RAM_EVC_IDX_OFFSET) & VLAN_INFO_RAM_EVC_IDX_MASK) | VLAN_INFO_RAM_VLAN_FILT_MASK;
									writeRawVlanPortTbl(k, data, iFace);
								}
							}
							UNLOCK_EVC_LIST();
							return (5);
						}
						else
						{
							// Assign this VLan to this EVC
							data &= ~VLAN_INFO_RAM_EVC_IDX_MASK;
							data |= ((((uint32_t)evcNum) << VLAN_INFO_RAM_EVC_IDX_OFFSET) & VLAN_INFO_RAM_EVC_IDX_MASK);
							if ((data & VLAN_INFO_RAM_VLAN_FILT_RESTORE_MASK) == 0)
							{
								// This vlan is not being filtered at the interface level, so clear the filter bit if it's on
								data &= ~VLAN_INFO_RAM_VLAN_FILT_MASK;
							}
							writeRawVlanPortTbl(v, data, iFace);
							//DO_LOG_DEBUG(log_handle,"On EVC UNI %d.%lu, vlan %d was added",iFace,evcIndex,v);
						}
					}
				}
			} // For

			// Write the new value into the COS
			strcpy(evcArray[evcNum].uniCfg[iFace].evcPerUniCfg.ceVlanMap,ceVlanMapPtr);
			DO_LOG_NOTICE(log_handle,"On EVC per UNI with index %d.%lu the ceVlanMap List value was changed to %s",iFace,evcIndex,ceVlanMapPtr);
			break;
		}
		case evcPerUniParam_ingressBwpGrpIndex:
		{
			bwpGrpIndex_t	*bwpGrpIndexPtr = (bwpGrpIndex_t*)paramValue;

			//check the string length
			if (*bwpGrpIndexPtr != 0)
			{
				if (!svcBwpGrpIsBwpGrpIndexValid(*bwpGrpIndexPtr))
				{
					UNLOCK_EVC_LIST();
					DO_LOG_INFO(log_handle,"Operation rejected due to invalid EVC per UNI BWP Group index %lu on index %d.%lu",*bwpGrpIndexPtr,iFace,evcIndex);
					return (3);
				}
			}

			// Update the BWP Group index if it changes or if it is set to 0.
			// The set to 0 condition must be considered because the flowNum table must be initialised (first use) even if the BWP Group Index is 0
			if (evcArray[evcNum].uniCfg[iFace].evcPerUniCfg.ingressBwpGrpIndex != *bwpGrpIndexPtr || *bwpGrpIndexPtr == 0)
			{

				// Attempt to add/update the Bwp Group for the EVC
				retVal = svcBwpGrpAddToEvc(evcNum,iFace,*bwpGrpIndexPtr);
				if (retVal == 0)
				{
					// Write the new value into the EVC
					evcArray[evcNum].uniCfg[iFace].evcPerUniCfg.ingressBwpGrpIndex = *bwpGrpIndexPtr;
					DO_LOG_NOTICE(log_handle,"On EVC per UNI with index %d.%lu the Ingress BWP Group was changed to %lu",iFace,evcIndex,*bwpGrpIndexPtr);
				}
				else
				{
					evcArray[evcNum].uniCfg[iFace].evcPerUniCfg.ingressBwpGrpIndex = *bwpGrpIndexPtr;
					DO_LOG_ERROR(log_handle,"On EVC per UNI with index %d.%lu failed to configure the BWP Group with index %lu",iFace,evcIndex,*bwpGrpIndexPtr);
				}
			}
			break;
		}
		case evcPerUniParam_outerTagVLan:
		{
			uint16_t	*outerTagVLanPtr = (uint16_t*)paramValue;
			//check the tag value value is valid
			if ((*outerTagVLanPtr > 4095))
			{
				UNLOCK_EVC_LIST();
				DO_LOG_INFO(log_handle,"On EVC per UNI with index %d.%lu an invalid value for outerTagVLan was provided %d",iFace,evcIndex,*outerTagVLanPtr);
				return (3);
			}

			if (evcArray[evcNum].uniCfg[iFace].evcPerUniEglCfg.outerTagVLan != *outerTagVLanPtr)
			{
				readEvcTbl(evcNum, &evcTblEntry, iFace);
				evcTblEntry.txVid = *outerTagVLanPtr;
				writeEvcTbl(evcNum, &evcTblEntry, iFace);
				evcArray[evcNum].uniCfg[iFace].evcPerUniEglCfg.outerTagVLan = *outerTagVLanPtr;
				DO_LOG_NOTICE(log_handle,"On EVC per UNI with index %d.%lu the outerTagVLan was changed to %d",iFace,evcIndex,*outerTagVLanPtr);

			}
			break;
		}
		case evcPerUniParam_tagAction:
		{
			tagAction_t	*tagActionPtr = (tagAction_t*)paramValue;
			//check the tag value value is valid
			if ((*tagActionPtr > ta_tagPush) || (*tagActionPtr < ta_noAction))
			{
				UNLOCK_EVC_LIST();
				DO_LOG_INFO(log_handle,"On EVC per UNI with index %d.%lu an invalid value for tagAction was provided %d",iFace,evcIndex,*tagActionPtr);
				return (3);
			}

			if (evcArray[evcNum].uniCfg[iFace].evcPerUniEglCfg.tagAction != *tagActionPtr)
			{
				readEvcTbl(evcNum, &evcTblEntry, iFace);
				evcTblEntry.tagAction = *tagActionPtr;
				writeEvcTbl(evcNum, &evcTblEntry, iFace);
				evcArray[evcNum].uniCfg[iFace].evcPerUniEglCfg.tagAction = *tagActionPtr;
				DO_LOG_NOTICE(log_handle,"On EVC per UNI with index %d.%lu the tagAction was changed to %d",iFace,evcIndex,*tagActionPtr);
			}
			break;
		}
		case evcPerUniParam_tagType:
		{
			pushTag_t	*tagTypePtr = (pushTag_t*)paramValue;
			//check the tag value value is valid
			if ((*tagTypePtr > pushStag) || (*tagTypePtr < pushCtag))
			{
				UNLOCK_EVC_LIST();
				DO_LOG_INFO(log_handle,"On EVC per UNI with index %d.%lu an invalid value for tagType was provided %d",iFace,evcIndex,*tagTypePtr);
				return (3);
			}

			if (evcArray[evcNum].uniCfg[iFace].evcPerUniEglCfg.tagAction != *tagTypePtr)
			{
				readEvcTbl(evcNum, &evcTblEntry, iFace);
				evcTblEntry.pushTagType = *tagTypePtr;
				writeEvcTbl(evcNum, &evcTblEntry, iFace);
				evcArray[evcNum].uniCfg[iFace].evcPerUniEglCfg.tagType = *tagTypePtr;
				DO_LOG_NOTICE(log_handle,"On EVC per UNI with index %d.%lu the tagType was changed to %d",iFace,evcIndex,*tagTypePtr);
			}
			break;
		}
		default:
		{
			UNLOCK_EVC_LIST();
			DO_LOG_INFO(log_handle,"Operation rejected due to the selected managed object being Read-Only");
			return (2);
			break;
		}
	}
	UNLOCK_EVC_LIST();
	return (4); // Operation accepted
}

/*
 * @brief checks to see if the specified BWP Group index is used by any valid EVC
 */
bool _evcIsBwpGrpInUse(bwpGrpIndex_t bwpGrpIndex)
{
	evcEntry_t	*evcEntryPtr;

	LOCK_EVC_LIST(0);
	// Start searching valid EVCs at the head of the list
	evcEntryPtr = TAILQ_FIRST(&evcList.llEvcHead);
	while (evcEntryPtr != NULL)
	{
		if (((evcEntryPtr->uniCfg[wanPort].evcUniCfg.evcUniType != eu_unknown) && (evcEntryPtr->uniCfg[wanPort].evcPerUniCfg.ingressBwpGrpIndex == bwpGrpIndex)) ||
				((evcEntryPtr->uniCfg[lanPort].evcUniCfg.evcUniType != eu_unknown) && (evcEntryPtr->uniCfg[lanPort].evcPerUniCfg.ingressBwpGrpIndex == bwpGrpIndex)))
		{
			UNLOCK_EVC_LIST();
			return (true);	//found it
		}
		evcEntryPtr = TAILQ_NEXT(evcEntryPtr,ll);
	}
	UNLOCK_EVC_LIST();
	return false;
}

/*
 * @brief checks to see if the specified BWP Group index is used by any valid EVC
 */
bool _evcIsL2cpGrpInUse(l2cpGrpIndex_t l2cpGrpIndex)
{
	evcEntry_t	*evcEntryPtr;

	LOCK_EVC_LIST(0);
	// Start searching valid EVCs at the head of the list
	evcEntryPtr = TAILQ_FIRST(&evcList.llEvcHead);
	while (evcEntryPtr != NULL)
	{
		if ((evcEntryPtr->cfg.l2cpGrpIndex == l2cpGrpIndex) || (evcEntryPtr->cfg.l2cpGrpIndex == l2cpGrpIndex))
		{
			UNLOCK_EVC_LIST();
			return (true);	//found it
		}
		evcEntryPtr = TAILQ_NEXT(evcEntryPtr,ll);
	}
	UNLOCK_EVC_LIST();
	return false;
}

/*
 * @brief Test if the supplied EVC Index is valid
 */
bool svcEvcIsEvcIndexValid(evcIndex_t evcIndex)
{
	bool retVal;

	LOCK_EVC_LIST(0);
	retVal = isEvcIndexValid(evcIndex, NULL);
	UNLOCK_EVC_LIST();
	return(retVal);
}

/*
 * @brief Convert an evcNum into a EVC MIB index
 */
evcIndex_t _evcNumToMibIndex(evcNum_t evcNum)
{
	evcIndex_t evcIndex  = 0;

	LOCK_EVC_LIST(0);
	if (evcArray[evcNum].evcCtxt.inUse)
	{
		evcIndex = evcArray[evcNum].evcCtxt.evcIndex;
	}
	UNLOCK_EVC_LIST();
	return (evcIndex);
}
