/*
 * mep.c
 *
 *  Created on: 25 Mar 2015
 *      Author: Niall.Donovan
 */

#include <api_oam_sdk/common/error_codes/inc/mr_error_codes.h>
#include <api_oam_sdk/common/types/inc/mr_api_types.h>
#include <api_oam_sdk/continuity_check/src/cc_types.h>
#include <api_oam_sdk/link_trace/src/link_trace_types.h>
#include <api_oam_sdk/loopback/src/lb_support.h>
#include <api_oam_sdk/loopback/src/lb_types.h>
#include <api_oam_sdk/maintenance_association/inc/ma.h>
#include <api_oam_sdk/maintenance_domain/inc/md.h>
#include <api_oam_sdk/mep/inc/mep.h>
#include <api_oam_sdk/metroam_hal/inc/mr_hal.h>
#include <api_oam_sdk/notifications/inc/egl_notifications.h>
#include <api_oam_sdk/performance_monitoring/inc/pm_dm.h>
#include <api_oam_sdk/performance_monitoring/inc/pm_lm.h>
#include <api_oam_sdk/rmep_db/src/rmep_types.h>
#include <api_oam_sdk/service_evc/src/svc_evc_types.h>
#include <api_oam_sdk/system/inc/system.h>
#include <api_oam_sdk/table_access/inc/table_access.h>
#include <api_oam_sdk/table_config/inc/table_config.h>
#include <string.h>

#include "egl_assert.h"
#include "egl_log.h"

static mepEntry_t	mepArray[MAX_NUM_MEPS];
static uint16_t		log_handle				= 0;		// handle used to log messages for this module
static osMutex_t	mepListLock				= NULL;
static bool			mepInitialised			= false;
static lMepNum_t	s_lMepCount				= 0;

#if 1
	#define LOCK_MEP_LIST(timeout)	;//lockMDBranch(0,timeout)	//osRecursiveMutexTake(mepListLock,timeout)
	#define UNLOCK_MEP_LIST()		;//unlockMDBranch(0)		//osRecursiveMutexGive(mepListLock)
#else
	#define LOCK_MEP_LIST(timeout)	osRecursiveMutexTake(mepListLock,timeout)
	#define UNLOCK_MEP_LIST()		osRecursiveMutexGive(mepListLock)

	void lockMepBranch(lMepNum_t lMepNum, uint32_t timeout)
	{
		LOCK_MEP_LIST(timeout);
	}

	void unlockMepBranch(lMepNum_t lMepNum)
	{
		//UNLOCK_MD_BRANCH(_mdMibIndexToMdNum(mdIndex),timeout);
		UNLOCK_MEP_LIST();
	}
#endif
/*
 * @brief	This sets the mepEntry values to their default
 *
 * In most cases this should only be done with mepLock enabled
 * to ensure thread safe operation
 */
static void setMepDefaults(mepEntry_t *mepEntry)
{
	memset(mepEntry,0,sizeof(mepEntry_t));
	mepEntry->mepCtxt.mepNum 			= INVALID_MEP_NUM;
	mepEntry->mepInfo.direction			= down;
	mepEntry->mepInfo.iFace				= wanPort;
	mepEntry->mepMefInfo.operNextIndex	= 1;
	mepEntry->mepMefInfo._lmSingleEndedResponder	= false;
	mepEntry->mepMefInfo.slmSingleEndedResponder	= true;
	mepEntry->mepMefInfo.dmSingleEndedResponder		= true;
}

/*
 * @brief Clears the Mep list
 *
 * This should be called to initialise the MA list.
 * Any MAs on the list will be lost.
 */
static void clearMepList(void)
{
	uint16_t	k;

	// Initialise all entries in the MA array to defaults
	for (k = 0; k < MAX_NUM_MEPS; k++)
	{
		setMepDefaults(&mepArray[k]);
	}
	s_lMepCount = 0;
	DO_LOG_DEBUG(log_handle,"Initialised the MEP List");
}

/*
 * @brief	Called at device startup to initialise MA module
 *
 * Safe to call repeatedly.
 */
void mepInit(void)
{
	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("MEP");

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

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

		DO_LOG_DEBUG(log_handle,"Preparing the MEP List for first use");
		LOCK_MEP_LIST(0);
		clearMepList();
		rMepDBInit();
		fngInit();
		ccInit();
		lbInit();
		//VR Skip - Netlink dependency
		//linkTraceInit();
		//pmInit();
		UNLOCK_MEP_LIST();
		mepInitialised = true;
	}
}

/*
 * @brief Convert a mepNum into a mepId
 */
mepId_t _mepNumToMepId(lMepNum_t mepNum, maNum_t *parentMaNum)
{
	mepId_t theMepId = 0;

	LOCK_MEP_LIST(0);
	if (mepArray[mepNum].mepCtxt.inUse)
	{
		theMepId = mepArray[mepNum].mepCtxt.mepId;
		if (parentMaNum != NULL)
		{
			*parentMaNum = mepArray[mepNum].mepCtxt.parentMaNum;
		}
		UNLOCK_MEP_LIST();
		return (theMepId);
	}
	UNLOCK_MEP_LIST();
	return (0);				//MepNum not in use
}

/*
 * @brief Convert a mepId into a mepNum
 */
static lMepNum_t mepIdToMepNumNoLocking(mdIndex_t mdIndex, maIndex_t maIndex, mepId_t mepId)
{
	mepEntry_t	*mepEntryPtr;
	lMepNum_t	lMepNum = -1;

	// Start searching valid lMeps at the head of the list
	mepEntryPtr = TAILQ_FIRST(_getMepListHead(mdIndex,maIndex));
	while (mepEntryPtr != NULL)
	{
		if (mepEntryPtr->mepCtxt.mepId == mepId)
		{
			lMepNum = mepEntryPtr->mepCtxt.mepNum;
			return (lMepNum);	//found it
		}
		mepEntryPtr = TAILQ_NEXT(mepEntryPtr,ll);
	}
	return -1;	// mepId not found
}

/*
 * @brief Convert a mepId into a mepNum
 */
lMepNum_t _mepIdToMepNum(mdIndex_t mdIndex, maIndex_t maIndex, mepId_t mepId)
{
	lMepNum_t lMepNum;

	LOCK_MEP_LIST(0);
	lMepNum = mepIdToMepNumNoLocking(mdIndex, maIndex, mepId);
	UNLOCK_MEP_LIST();
	return lMepNum;
}

/*
 * @brief Read the next entry in the Local Mep List
 *
 * @param[in] mdIndex of the parent MD
 * @param[in] maIndex of the parent MA
 * @param[in] prevLMepId of previous entry or 0
 * @return	mepId of the next entry or 0 if no more entries
 *
 * Read the next entry in the lMep List. If prevLMepId is 0 it
 * returns the mepId of the first Mep entry or 0 if there are
 * no Mep entries. If a valid prevLMepId is provided it returns
 * the mepId of the next Mep in the list.
 */
mepId_t lMepReadList(mdIndex_t mdIndex, maIndex_t maIndex, mepId_t prevLMepId)
{
	lMepNum_t 	nextMepNum;
	mepId_t		theMepId;

	// Validate the MA and MD Index before using it
	if(!maIsMaMibIdxValid(mdIndex, maIndex))
	{
		DO_LOG_DEBUG(log_handle,"Operation rejected due to invalid MD (%d) or MA (%d) Index ",mdIndex, maIndex);
		return (0);
	}

	LOCK_MEP_LIST(0);
	if (prevLMepId == 0)
	{
		if (!TAILQ_EMPTY(_getMepListHead(mdIndex, maIndex)))
		{
			theMepId = TAILQ_FIRST(_getMepListHead(mdIndex, maIndex))->mepCtxt.mepId;
			UNLOCK_MEP_LIST();
			return theMepId;
		}
	}
	else
	{
		if (mepIdIsValid(prevLMepId))
		{
			nextMepNum = mepIdToMepNumNoLocking(mdIndex, maIndex, prevLMepId);
			if (nextMepNum != -1)
			{
				if (TAILQ_NEXT(&mepArray[nextMepNum],ll) != NULL)
				{
					theMepId = TAILQ_NEXT(&mepArray[nextMepNum],ll)->mepCtxt.mepId;
					UNLOCK_MEP_LIST();
					return theMepId;
				}
			}
		}
	}
	UNLOCK_MEP_LIST();
	return 0;
}


/*
 * @brief Test if the supplied Local MEPID is valid
 */
static bool isMepIdValid(mdIndex_t mdIndex, maIndex_t maIndex, mepId_t lMepId, mepNum_t *mepNum)
{
	mepNum_t	theMepNum;

	if(!maIsMaMibIdxValid(mdIndex, maIndex))
	{
MEP_MEPID_INVALID:
		DO_LOG_DEBUG(log_handle,"The MEP ID (%lu.%lu.%d) was invalid",mdIndex, maIndex, lMepId);
		return false;
	}

	if (!mepIdIsValid(lMepId))
	{
		goto MEP_MEPID_INVALID;
	}

	// Test is the lMepId known and get it's associated mepNum
	theMepNum = mepIdToMepNumNoLocking(mdIndex, maIndex, lMepId);
	if (theMepNum != -1)		// If the MEPID is found
	{
		if (mepNum != NULL)
		{
			*mepNum = theMepNum;
		}
		return(true);
	}
	goto MEP_MEPID_INVALID;
}

/*
 * @brief Test if the supplied local MEPID is valid
 */
bool mepIsLMepIdValid(mdIndex_t mdIndex, maIndex_t maIndex, mepId_t lMepId)
{
	bool retVal;

	if (!mepIdIsValid(lMepId))
	{
		return(false);
	}
	LOCK_MEP_LIST(0);
	retVal = isMepIdValid(mdIndex, maIndex, lMepId, NULL);
	UNLOCK_MEP_LIST();
	return(retVal);
}

/*
 * @brief	Find the next free lMep Entry
 */
static mepEntry_t* getNextFreeMepEntry(void)
{
	lMepNum_t	k;

	for (k = 0; k < MAX_NUM_MEPS; k++)
	{
		if (!mepArray[k].mepCtxt.inUse)
		{
			mepArray[k].mepCtxt.mepNum = k;
			mepArray[k].mepCtxt.inUse = true;
			return (&mepArray[k]);
		}
	}
	return NULL;
}

/*
 * @brief	Add a new mepEntry into the lMep List
 */
static void addLMepToList(mdIndex_t mdIndex, maIndex_t maIndex, mepEntry_t *mepEntry)
{
	ASSERT_NOT_NULL(mepEntry);

	TAILQ_INSERT_TAIL(_getMepListHead(mdIndex,maIndex),mepEntry,ll);
}

/*
 * @brief	Remove the lMep Entry, pointed to by mepEntry, from the lMep List
 */
static void removeLMepFromList(mdIndex_t mdIndex, maIndex_t maIndex, mepEntry_t *mepEntry)
{
	ASSERT_NOT_NULL(mepEntry);

	TAILQ_REMOVE(_getMepListHead(mdIndex,maIndex),mepEntry,ll);
	setMepDefaults(mepEntry);
}

/*
 * @brief Get the primary vLan value for a MA
 *
 * This function is called if the lMeps primarySelectorIdOrNone value is 0.
 * It will query the MA for it's primary vLan and if that is set to none
 * (i.e. = 0) it will query the default vLan for the associated port.
 */
static uint16_t getPrimaryVLanForMa(mdIndex_t mdIndex, maIndex_t maIndex, iFace_t iFace)
{
	serviceSelector_t 	primarySelectorType;
	serviceSelectorId_t primarySelectorIdOrNone;
	uint16_t			thePrimaryVid;

	// **IMPORTANT** mdIndex, maIndex must be valid as no error checking is done at this level
	_maGetPrimaryVlanInfo(mdIndex, maIndex, &primarySelectorType, &primarySelectorIdOrNone);

#if 0
	if (primarySelectorIdOrNone == 0)
	{
		// the MAs primarySelectorId was none so get the default for the interface (port)
		thePrimaryVid = getPortDefaultLan(iFace);
	}
	else
#endif
	{
		switch(primarySelectorType)
		{
			case ss_vlanId:	// This is the only type we support
			case ss_isid:		// All others will default to same as vlanId
			case ss_tesid:
			case ss_segid:
				thePrimaryVid = primarySelectorIdOrNone;
		}
	}
	return (thePrimaryVid);
}

/*
 * @brief Get the primary Vid for a specified local MEP. Do not lock the MEP context list
 */
static uint16_t getPrimaryVidForMepNoLocking(mdIndex_t mdIndex, maIndex_t maIndex, mepId_t lMepId)
{
	lMepNum_t	lMepNum;

	lMepNum = mepIdToMepNumNoLocking(mdIndex, maIndex, lMepId);
	if (mepArray[lMepNum].mepInfo.primaryVid == 0)
	{
		return(getPrimaryVLanForMa(mdIndex, maIndex, mepArray[lMepNum].mepInfo.iFace));
	}
	return(mepArray[lMepNum].mepInfo.primaryVid);
}

/*
 * @brief Get the primary Vid for a specified local MEP
 */
uint16_t getPrimaryVidForMep(mdIndex_t mdIndex, maIndex_t maIndex, mepId_t lMepId)
{
	uint16_t	primayVid;

	LOCK_MEP_LIST(0);
	EGL_ASSERT(isMepIdValid(mdIndex, maIndex, lMepId, NULL),1);
	primayVid = getPrimaryVidForMepNoLocking(mdIndex, maIndex, lMepId);
	UNLOCK_MEP_LIST();
	return(primayVid);
}

/*
 * @brief Find out if the selected Local MEP is active (admin state)
 */
bool _mepIsActive(mdIndex_t mdIndex, maIndex_t maIndex, mepId_t lMepId)
{
	lMepNum_t	lMepNum;

	lMepNum = _mepIdToMepNum(mdIndex, maIndex, lMepId);
	if (lMepNum >= 0)
	{
		return(mepArray[lMepNum].mepInfo.active);
	}
	return(false);		// MEP doesn't exist!
}

/*
 * @brief Create a local MEP on the specified MA
 */
int lMepCreate(mdIndex_t mdIndex, maIndex_t maIndex, mepId_t mepId, mepDir_t direction, iFace_t iFace)
{
	lMepNum_t		lMepNum;
	mepEntry_t		*mepEntryPtr;
	maNetInfo_t		maNetInfo;
	uint32_t		data;
	mdMibInfo_t 	mdMibInfo;
	simpleMaid_t	maid;
	int				retVal;
	uint8_t			k;
	mepId_t			mepIdList[MAX_MEPIDS_PER_MA+1]; // +1 because MAX_MEPIDS_PER_MA + 1 end of list value (=0)
	maCompInfo_t	maCompInfo;
	maMEFInfo_t		maMEFInfo;
	uint16_t		vLanList[MAX_NUM_MEPS+1];
	uint16_t		priVlan;
	ccmTlv_t		ccmTlv;
	vlanTblEntry_t	vlanTblEntry;

	if(!maIsMaMibIdxValid(mdIndex,maIndex))
	{
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid MA Index (%lu.%lu) ",mdIndex, maIndex);
		ts_printf("MEP:Operation rejected due to invalid MA Index (%d.%d) ",mdIndex, maIndex);
		return (1);
	}
#ifdef DEBUG_PRINT_L1_1
	ts_printf("MEP:maIsMaMibIdxValid (%d.%d) ",mdIndex, maIndex);
#endif
	if (!mepIdIsValid(mepId))
	{
		goto LMEP_CREATE_MEPID_INVALID;
	}
	if (!_maIsMepIdOnList(mdIndex, maIndex,mepId))
	{
LMEP_CREATE_MEPID_INVALID:
		DO_LOG_INFO(log_handle,"Operation rejected because MEPID (%lu.%lu.%d) is not in the MA's list of MEPIDs",mdIndex, maIndex, mepId);
		ts_printf("MEP:Operation rejected because MEPID (%d.%d.%d) is not in the MA's list of MEPIDs",mdIndex, maIndex, mepId);

		return (3);
	}
#ifdef DEBUG_PRINT_L1_1
	ts_printf("MEP:Operation ok because MEPID (%d.%d.%d) is in the MA's list of MEPIDs",mdIndex, maIndex, mepId);
#endif
	if (direction == up)
	{
		DO_LOG_INFO(log_handle,"Operation rejected because up MEPs are not supported");
		ts_printf("MEP:Operation rejected because up MEPs are not supported\n\r");

		return (-2);
	}
#ifdef DEBUG_PRINT_L1_1
	ts_printf("MEP:Operation ok because up MEPs are  supported\n\r");
#endif
	LOCK_MEP_LIST(0);

	// We need some info about the MD and MA so read then
	maRead(mdIndex, maIndex, &maNetInfo, &maCompInfo, &maMEFInfo, vLanList, mepIdList, NULL);
	mdRead(mdIndex, &mdMibInfo, NULL);

	// By default the first Vlan on the MA's Vlan List is the local MEPs Primary Vid
	priVlan = vLanList[0];
	if (priVlan == 0)
	{
		// if the MAs primary VID is zero...then CCMs should be sent as un-tagged but the MEP lookup will use the PVID
		priVlan = getPortDefaultLan(iFace);
	}

	// Scan the existing Local MEPs on this MA and do some rule checking...
	mepEntryPtr = TAILQ_FIRST(_getMepListHead(mdIndex,maIndex));
	while (mepEntryPtr != NULL)
	{
		// Check if a MEP with ID = mepId already exists on this MA
		if (mepEntryPtr->mepCtxt.mepId == mepId)
		{
			UNLOCK_MEP_LIST();
			ts_printf("MEP:Operation rejected because a MEP with ID %d.%d.%d already exists on the MA\n\r",mdIndex, maIndex, mepId);
			DO_LOG_INFO(log_handle,"Operation rejected because a MEP with ID %d.%d.%d already exists on the MA",mdIndex, maIndex, mepId);
			return (2);
		}
		ts_printf("MEP:Operation ok because a MEP with ID %d.%d.%d does not exist on the MA\n\r",mdIndex, maIndex, mepId);

		// Check if a MEP with the same port and Vid already exist on this MA
		if ((mepEntryPtr->mepInfo.primaryVid == priVlan) && (mepEntryPtr->mepInfo.iFace == iFace))
		{
			UNLOCK_MEP_LIST();
			ts_printf("MEP:Operation rejected due to the existence of a MEP with same port(%d), level(%d), VID(%d) and direction(down)", iFace, mdMibInfo.level, priVlan);

			//DO_LOG_INFO(log_handle,"Operation rejected due to the existence of a MEP with same port(%d), level(%d), VID(%d) and direction(down)", iFace, mdMibInfo.level, priVlan);
			return (4);
		}
		mepEntryPtr = TAILQ_NEXT(mepEntryPtr,ll);
	}
#ifdef DEBUG_PRINT_L1_1
	ts_printf("MEP:Operation ok due to the no existence of a MEP with same port(%d), level(%d), VID(%d) and direction(down)", iFace, mdMibInfo.level, priVlan);
#endif
	mepEntryPtr = getNextFreeMepEntry();
	if (mepEntryPtr != NULL)
	{
		// initialise the Remote MEP list Head
		TAILQ_INIT(&mepEntryPtr->llRMepHead);

		// initialise the PM Delay and Loss list Heads
		TAILQ_INIT(&mepEntryPtr->llDmSvcHead);
		TAILQ_INIT(&mepEntryPtr->llLmSvcHead);

		mepEntryPtr->mepInfo.primaryVid = vLanList[0];
		mepEntryPtr->mepInfo.iFace		= iFace;

		lMepNum = mepEntryPtr->mepCtxt.mepNum;
		generateMaid(mdMibInfo.name, mdMibInfo.type,maNetInfo.name, maNetInfo.type, maid);
		retVal = addLocalMep(
				lMepNum,
				mepId,
				priVlan,
				mdMibInfo.level,
				maNetInfo.ccmInterval,
				iFace,
				maid,
				&mepEntryPtr->mepCtxt.rxLMepAddr);

		readVlanTbl(priVlan, &vlanTblEntry, iFace);

		if (retVal != 0)
		{
			setMepDefaults(mepEntryPtr);
			UNLOCK_MEP_LIST();
			DO_LOG_INFO(log_handle,"Operation rejected due to the existence of a MEP with same port, level, VID and direction");
			ts_printf("MEP:Operation rejected due to the existence of a MEP with same port, level, VID and direction");

			return (4);
		}
#ifdef DEBUG_PRINT_L2
		ts_printf("MEP:Operation ok due to no existence of a MEP with same port, level, VID and direction");
#endif
		mepEntryPtr->mepCtxt.mepId = mepId;

		// To facilitate re-verse lookup save the parent MAs HW index
		mepEntryPtr->mepCtxt.parentMaNum = _maMibIndexToMaNum(mdIndex, maIndex);

		// configure the TLVs that should be in the CCM...
		// First create a empty ccmTlv structure
		memset(&ccmTlv,0,sizeof(ccmTlv_t));
		if (maMEFInfo.maMEFCompInfo.interfaceStatusTlvIncluded)
		{
			ccmTlv.isTlv.present = true;
			ccmTlv.isTlv.value = sysGetInterfaceStatus(iFace);
		}

		if (maMEFInfo.maMEFCompInfo.portStatusTlvIncluded)
		{
			ccmTlv.psTlv.present = true;
			ccmTlv.psTlv.value = sysGetPortStatus(iFace);
		}

		if (maCompInfo.mhfIdPermission != ma_sendIdNone)
		{
			// TODO: Sender ID TLV NOT supported in this version
		}
#ifdef DEBUG_PRINT_L1
		ts_printf("Adding CCM service to the MEP - In progress...");
#endif
		// Automatically add a ccmService to the MEP
		ccmAdd(lMepNum,
				mepId,
				priVlan,		// Find out the vLan to use (may be the port's default vLan)
				mepEntryPtr->mepCcInfo.ccmLtmPriority,
				(vLanList[0] == 0 ? VLAN_NONE : _maGetTagType(mdIndex, maIndex)),	// If the MA's vlan is 0, send CCMs as un-tagged.
				mdMibInfo.level,
				maNetInfo.ccmInterval,
				iFace,
				&ccmTlv);

		// Ensure that the numMeps value in the NUM_MEPS_REG_ADDR is equal or greater than the actual number of MEPs configured
		mrHalRegRead(NUM_MEPS_REG_ADDR,&data);
		if (((data & NUM_MEPS_REG_LMEP_NUM_MASK) >> NUM_MEPS_REG_LMEP_NUM_OFFSET) < s_lMepCount )
		{
			// If the number of configured MEPs is greater than the set value in this register
			// then update the register or that MEP will not be fully operational
			data &= ~(NUM_MEPS_REG_LMEP_NUM_MASK | NUM_MEPS_REG_RMEP_NUM_MASK);
			data |= ((((uint32_t)s_lMepCount) << NUM_MEPS_REG_LMEP_NUM_OFFSET) & NUM_MEPS_REG_LMEP_NUM_MASK);
			data |= ((((uint32_t)s_lMepCount) << NUM_MEPS_REG_RMEP_NUM_OFFSET) & NUM_MEPS_REG_RMEP_NUM_MASK);
			mrHalRegWrite(NUM_MEPS_REG_ADDR,data);
		}
		s_lMepCount++;	// update the count of Local MEPs

		// The local MEP must be added to the local MEP list before remote MEPs can be added
		addLMepToList(mdIndex, maIndex, mepEntryPtr);

		// Now that the Local MEP has been created, add the remote MEPs...
		k=0;
		while (mepIdList[k] != 0)
		{
			// Create a remote MEP for all mepIds in the MA except the one that is the local MEP
			if (mepIdList[k] != mepId)
			{
				if ((MAX_NUM_RMEPS - rMepDBGetRMepCount()) > 0)
				{
					// if there is space for the rMep add it
					rMepDBAdd(mdIndex, maIndex, mepId, mepIdList[k]);
					// The default state for new Remote MEPs is Active
					// However the default state for a new Local MEP is inactive
					// so none of the remote MEPs will be enabled until the
					// local MEP active property is set true.
					rMepSetActive(mdIndex, maIndex, mepId, mepIdList[k]);
#ifdef DEBUG_PRINT_L2
					ts_printf(">> Added remote MEP %d to local MEP %d database", mepIdList[k], mepId);
#endif
				}
				else
				{
					// if we can't fit the remote MEPs we have no choice but to report
					// that the local MEP could no be created due to lack of resources
					ccmRemove(lMepNum);
					deleteLocalMep(priVlan,mdMibInfo.level,lMepNum,mepId,iFace);
					removeLMepFromList(mdIndex, maIndex, mepEntryPtr);
					ts_printf("MEP:goto LMEP_CREATE_INSUFFICIENT_RESOURCES\n\r");
					goto LMEP_CREATE_INSUFFICIENT_RESOURCES;
				}
			}
			k++;
			EGL_ASSERT(k<MAX_MEPIDS_PER_MA+1,1);
		}
	}
	else
	{
LMEP_CREATE_INSUFFICIENT_RESOURCES:
		UNLOCK_MEP_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected due to insufficient resources");
		ts_printf("MEP:Operation rejected due to insufficient resources\n\r");
		return (-1);
	}
	UNLOCK_MEP_LIST();
#ifdef DEBUG_PRINT_L2
	DO_LOG_NOTICE(log_handle,"Created Local MEP with ID %lu.%lu.%d",mdIndex, maIndex, mepId);
	ts_printf("MEP:Created Local MEP with ID %d.%d.%d",mdIndex, maIndex, mepId);
#endif
	retVal = 9;
	return retVal;
	//return (9); // Operation accepted.
}

/*
 * @brief Delete the local MEP with MEPID equal to lMepId
 */
int lMepDelete(mdIndex_t mdIndex, maIndex_t maIndex, mepId_t lMepId)
{
	mepId_t		rMepId;
	mepId_t		rMepIdToDelete;
	lMepNum_t	lMepNum;
	pmIndex_t	pmIndex;
	pmIndex_t	pmIndexToDel;
	uint16_t	primaryVid;

	// Validate all the Indices
	if(!mepIsLMepIdValid(mdIndex, maIndex, lMepId))
	{
		DO_LOG_INFO(log_handle,"Operation rejected because nonexistent MEP %lu.%lu.%d",mdIndex, maIndex, lMepId);
		return (1);
	}

	LOCK_MEP_LIST(0);
	lMepNum = mepIdToMepNumNoLocking(mdIndex, maIndex, lMepId);
	if (lMepNum == -1)
	{
		UNLOCK_MEP_LIST();
		DO_LOG_ERROR(log_handle,"lMepDelete() failed because local MEP %lu.%lu.%d could not be found",mdIndex, maIndex, lMepId);
		return (1);
	}
	//ASSERT(lMepNum != -1,1);

	if (mepArray[lMepNum].mepInfo.active)
	{
		// If the MEP is enabled then disable it before removing it.
		mepArray[lMepNum].mepInfo.active = false;

		// Turn off the XConnSM and RMepErrSM
		ccDisableLMep(lMepNum);

		// Turn off all associated RMepSM
		rMepId = rMepReadList(mdIndex, maIndex,lMepId,0);
		while(rMepId != 0)
		{
			rMepDisable(mdIndex, maIndex, lMepId, rMepId);

			// Get the next MEP
			rMepId = rMepReadList(mdIndex, maIndex, lMepId, rMepId);
		}

		// turn off the CCM transmit
		cciDisable(lMepNum);
	}

	// Stop and Release any services associated with this local MEP...
	// Services that are attached to Local MEP are:
	// CC - Action = Stop and remove CC service
	// LB - Action = if LB is assigned to this MEP release it
	// LT - Action = if LT is assigned to this MEP release it
	// PM - Action = if SL or LM is assigned to this MEP release it
	ccmRemove(lMepNum);
	lbAbort(mdIndex, maIndex, lMepId);

	// find and delete all PM Sessions associated with this Local MEP...
	// Delete any Loss Sessions first...
	//VR Missing function definition
	/*pmIndex = lossMeasurementReadList(mdIndex, maIndex, lMepId, 0);
	while(pmIndex != 0)
	{
		pmIndexToDel = pmIndex;
		// Get the next Loss Session index
		pmIndex = lossMeasurementReadList(mdIndex, maIndex, lMepId, pmIndex);
		lossMeasurementDelete(mdIndex, maIndex, lMepId, pmIndexToDel);
	}

	// Delete any Delay Sessions...
	pmIndex = delayMeasurementReadList(mdIndex, maIndex, lMepId, 0);
	while(pmIndex != 0)
	{
		pmIndexToDel = pmIndex;
		// Get the next Loss Session index
		pmIndex = delayMeasurementReadList(mdIndex, maIndex, lMepId, pmIndex);
		delayMeasurementDelete(mdIndex, maIndex, lMepId, pmIndexToDel);
	}
	 */
	// find and delete all Remote MEPs associated with this Local MEP
	rMepId = rMepReadList(mdIndex, maIndex, lMepId, 0);
	while(rMepId != 0)
	{
		rMepIdToDelete = rMepId;
		// Get the next MEPID. BEFORE deleting the MEP
		rMepId = rMepReadList(mdIndex, maIndex, lMepId, rMepId);

		// Remove the Remote MEP
		rMepDBRemove(mdIndex, maIndex, lMepId, rMepIdToDelete);
	}

	mdMibInfo_t mdMibInfo;
	mdRead(mdIndex, &mdMibInfo, NULL);

	mepInfo_t	mepInfo;
	lMepRead(mdIndex, maIndex,lMepId,&mepInfo,NULL,NULL,NULL,NULL,NULL,NULL);

	primaryVid = getPrimaryVidForMepNoLocking(mdIndex, maIndex, lMepId);
	// Delete this Local MEP
	deleteLocalMep(
		primaryVid == 0 ? getPortDefaultLan(mepInfo.iFace): primaryVid,
		mdMibInfo.level,
		lMepNum,
		lMepId,
		mepInfo.iFace);

	// The MEP is deleted so make sure to release the FNG context
	UNLOCK_MEP_LIST();
	fngReleaseSM(lMepNum);
	LOCK_MEP_LIST(0);

	removeLMepFromList(mdIndex, maIndex, &mepArray[lMepNum]);
	s_lMepCount--;	// update the count of Local Meps

	UNLOCK_MEP_LIST();
	DO_LOG_NOTICE(log_handle,"Deleted MEP with ID %lu.%lu.%d (lmNum=%d)",mdIndex, maIndex, lMepId, lMepNum);
	return(2);
}

/*
 * Check the context value for the link status of all remote MEPs in the specified Local MEP
 * and determine the connectivity status.
 * Note this function does no locking.
 */
mepConnStatus_t	_lMepGetConnStatus(mdIndex_t mdIndex, maIndex_t maIndex, mepId_t lMepId)
{
	rMepDBint_t rMepDBIntInfo;
	mepId_t		rMepId;
	rMepNum_t	connErrOrInactiveCnt	= 0;
	rMepNum_t	rMepCount	= 0;

	// Check the CCM status of all remote MEPs...
	rMepId = rMepReadList(mdIndex, maIndex,lMepId,0);
	while(rMepId != 0)
	{
		_rMepGetIntInfo(mdIndex, maIndex,lMepId,rMepId,&rMepDBIntInfo);

		if (!rMepDBIntInfo.linkIsUp)
		{
			// Note: this count assumes inactive/disabled MEPs have their link down.
			connErrOrInactiveCnt++;
		}
		rMepCount++;

		// Get the next MEP
		rMepId = rMepReadList(mdIndex, maIndex, lMepId, rMepId);
	}

	// Compare the connectivity error count to the number of Remote Meps...
	if ((rMepCount > 0) && (connErrOrInactiveCnt == 0))
	{
		// There are MEPs and none are disabled or have a connectivity error
		return connActive;
	}
	else if ((rMepCount > 0) && (connErrOrInactiveCnt < rMepCount))
	{
		//There are MEPs and some, but not all, have connectivity errors.
		return connPartiallyActive;
	}
	else
	{
		return connInactive;
	}
}

/*
 * Check the context value for the Receive RDI status of all remote MEPs in the specified Local MEP
 * and determine if any are set.
 * Note this function does no locking.
 */
bool	_lMepIsAnyRxRdiSet(mdIndex_t mdIndex, maIndex_t maIndex, mepId_t lMepId)
{
	rMepDBint_t rMepDBIntInfo;
	mepId_t		rMepId;
	rMepNum_t	rdiSetCnt	= 0;
	rMepNum_t	rMepCount	= 0;

	// Check the CCM status of all remote MEPs...
	rMepId = rMepReadList(mdIndex, maIndex,lMepId,0);
	while(rMepId != 0)
	{
		_rMepGetIntInfo(mdIndex, maIndex,lMepId,rMepId,&rMepDBIntInfo);

		if ((rMepDBIntInfo.rxRdiIsSet) && (rMepDBIntInfo.rMepIsActive))
		{
			rdiSetCnt++;
		}
		if (rMepDBIntInfo.rMepIsActive)
		{
			// Only count active Remote MEPS
			rMepCount++;
		}

		// Get the next MEP
		rMepId = rMepReadList(mdIndex, maIndex, lMepId, rMepId);
	}
	// If the count of remote MEPs with RDI set is 0 then no RDI is set
	if (rdiSetCnt == 0)
	{
		return false;
	}
	else
	{
		// At least one RDI is set in a remote MEP
		return true;
	}
}

/*
 *
 */
bool	_lMepAreAllPSNotUpOrAnyISNotUp(mdIndex_t mdIndex, maIndex_t maIndex, mepId_t lMepId)
{
	rMepDBint_t rMepDBIntInfo;
	mepId_t		rMepId;
	bool		allPSNotUp	= true;
	bool		anyISNotUp	= false;

	// Check the CCM status of all remote MEPs...
	rMepId = rMepReadList(mdIndex, maIndex,lMepId,0);
	while(rMepId != 0)
	{
		_rMepGetIntInfo(mdIndex, maIndex,lMepId,rMepId,&rMepDBIntInfo);

		if ((rMepDBIntInfo.lastRxPSValue == psUp) || (rMepDBIntInfo.lastRxPSValue == psNoPortStatusTLV))
		{
			// At least one remote MEP has a PS value of up or doesn't have a PS TLV!
			allPSNotUp = false;
		}
		if ((rMepDBIntInfo.lastRxISValue != isUp) && (rMepDBIntInfo.lastRxISValue != isNoInterfaceStatusTLV))
		{
			// At least one remote MEP has an IS value of down!
			anyISNotUp	= true;
		}

		// Get the next MEP
		rMepId = rMepReadList(mdIndex, maIndex, lMepId, rMepId);
	}
	// If the count of remote MEPs with RDI set is 0 then no RDI is set
	if (allPSNotUp || anyISNotUp)
	{
		return true;
	}
	return false;
}

/*
 * @brief Read the information for the selected MEP
 */
int lMepRead(mdIndex_t mdIndex, maIndex_t maIndex, mepId_t lMepId,
		mepInfo_t 		*mepInfo,
		mepCcInfo_t		*mepCcInfo,
		mepLbInfo_t		*mepLbInfo,
		mepLbMefInfo_t	*mepLbMefInfo,
		mepFngInfo_t	*mepFngInfo,
		//TODO: add: mepLtInfo_t		*mepLtInfo,
		mepMefInfo_t	*mepMefInfo,
		macAddress_t	macAddress)
{
	lMepNum_t			lMepNum;
	mepId_t				rMepId;
	ccmTlvByteArray_t 	tlvByteArray;
	uint16_t			tlvLength;
	ccmTlv_t 			ccmTlv;
	int					retVal;

	if (!mepIdIsValid(lMepId))
	{
		goto LMEP_READ_MEPID_INVALID;
	}

	// Validate all the Indices
	if((!maIsMaMibIdxValid(mdIndex,maIndex)) || (!_maIsMepIdOnList(mdIndex, maIndex, lMepId)))
	{
LMEP_READ_MEPID_INVALID:
		DO_LOG_INFO(log_handle,"Operation rejected because nonexistent MEPID (%lu.%lu.%d)",mdIndex, maIndex, lMepId);
		return (1);
	}

	LOCK_MEP_LIST(0);
	lMepNum = mepIdToMepNumNoLocking(mdIndex, maIndex, lMepId);
	if(lMepNum == -1)
	{
		UNLOCK_MEP_LIST();
		goto LMEP_READ_MEPID_INVALID;
	}

	if (mepInfo != NULL)
	{
		// If a pointer is provided then copy the value
		memcpy(mepInfo, &mepArray[lMepNum].mepInfo,sizeof(mepInfo_t));
	}

	if (mepCcInfo != NULL)
	{
		// If a pointer is provided then generate the required info
		uint8_t	k = 0;
		// First we must update the active remote MEP list.
		// We've chosen to dynamically generate this list when the
		// mepCcInfo is read. The list is affected by the writable
		// parameter 'rMepIsActive' for the remote MEP.
		// This parameter is controlled by a call to rMepEnable
		// or rMepDisable. These are called if lMepWrite to
		// mepParam_mepActiveRMepListAdd or mepParam_mepActiveRMepListRemove
		// is called.
		rMepId = rMepReadList(mdIndex, maIndex,lMepId,0);
		while(rMepId != 0)
		{
			if (rMepIsActive(mdIndex, maIndex, lMepId, rMepId))
			{
				mepArray[lMepNum].mepCcInfo.activeRemoteMepList[k] = rMepId;
				k++;
			}
			// Get the next MEP
			rMepId = rMepReadList(mdIndex, maIndex, lMepId, rMepId);
		}
		// terminate the list with a 0 as the last entry
		mepArray[lMepNum].mepCcInfo.activeRemoteMepList[k] = 0;

		// Get the latest Tx Count and sequence error count
		readSeqNumInTxTempl(lMepNum, &mepArray[lMepNum].mepCcInfo.cciSentCcms, CCM_TEMPL);
		mepArray[lMepNum].mepCcInfo.cciSentCcms--;		// Sequence num is always 1 higer than number of CCMs sent
		readSeqErr(lMepNum, &mepArray[lMepNum].mepCcInfo.ccmSeqErrors);

		memcpy(mepCcInfo, &mepArray[lMepNum].mepCcInfo,sizeof(mepCcInfo_t));
	}

	if (mepLbInfo != NULL)
	{
		// If a pointer is provided then copy the value
		lbRead(mdIndex, maIndex, lMepId, mepLbInfo, mepLbMefInfo);
	}

	if (mepFngInfo != NULL)
	{
		// If a pointer is provided then read the FNG info
		fngRead( mdIndex, maIndex, lMepId, mepFngInfo);
	}

	if (mepMefInfo != NULL)
	{
		memcpy(mepMefInfo,&mepArray[lMepNum].mepMefInfo,sizeof(mepMefInfo_t));

		// TODO :  update values
		mepMefInfo->opState  = mepArray[lMepNum].mepInfo.active == true ? opStateEnabled : opStateDisabled;

		// If the MEP is enabled...
		if (mepMefInfo->opState == opStateEnabled)
		{
			// Check the CCM status of all remote MEPs...
			mepMefInfo->connectivityStatus = _lMepGetConnStatus(mdIndex, maIndex, lMepId);
		}
		else
		{
			// MEP not enabled so Connectivity is Inactive
			mepMefInfo->connectivityStatus = connInactive;
		}

		// Read the TLV values from the template to get the last sent values...
		readCfmTlvInTxTempl(lMepNum,tlvByteArray,&tlvLength,CCM_TEMPL);
		retVal = _extractTlvsFromArray(tlvByteArray,&ccmTlv);
		if (retVal != 0)
		{
			// There was an error parsing the TLV so report no TLVs
			ccmTlv.psTlv.present = false;
			ccmTlv.isTlv.present = false;
		}
		if (ccmTlv.psTlv.present)
		{
			mepMefInfo->sentPortStatus = ccmTlv.psTlv.value;
		}
		else
		{
			mepMefInfo->sentPortStatus = psNoPortStatusTLV;
		}
		if (ccmTlv.isTlv.present)
		{
			mepMefInfo->sentInterfaceStatus = ccmTlv.isTlv.value;
		}
		else
		{
			mepMefInfo->sentInterfaceStatus = isNoInterfaceStatusTLV;
		}

		//mepMefInfo->operNextIndex			= mepArray[lMepNum].mepMefInfo.operNextIndex;
		//mepMefInfo->_lmSingleEndedResponder	= mepArray[lMepNum].mepMefInfo._lmSingleEndedResponder;
		//mepMefInfo->dmSingleEndedResponder	= mepArray[lMepNum].mepMefInfo.dmSingleEndedResponder;
		//mepMefInfo->slmSingleEndedResponder	= mepArray[lMepNum].mepMefInfo.slmSingleEndedResponder;

		//mepMefInfo->rdiTransmitStatus; 	// This is kept up to date by the FNG state machine
		//mepMefInfo->lastDefectSentStatus;	// This is kept up to date by the FNG state machine
	}

	if (macAddress != NULL)
	{
		// read the MAC address for the interface associated with this MEP
		getInterfaceMAC(mepArray[lMepNum].mepInfo.iFace,macAddress);
	}

	UNLOCK_MEP_LIST();
	return (2); // Operation accepted
}

/*
 * @brief	Write a new value to the selected Local MEP parameter
 */
int lMepWrite(mdIndex_t mdIndex, maIndex_t maIndex, mepId_t lMepId, mepWrParam_t mepParam, void *paramValue)
{
	lMepNum_t			lMepNum;
	mepLbMefInfo_t   	mepLbMefInfo;
	mepLbInfo_t			mepLbInfo;

	if (!mepIdIsValid(lMepId))
	{
		goto LMEP_WRITE_MEPID_INVALID;
	}

	// Validate all the Indices
	if((!maIsMaMibIdxValid(mdIndex,maIndex)) || (!_maIsMepIdOnList(mdIndex, maIndex, lMepId)))
	{
LMEP_WRITE_MEPID_INVALID:
		DO_LOG_INFO(log_handle,"Operation rejected because nonexistent MEPID (%lu.%lu.%d)",mdIndex, maIndex, lMepId);
		return (1);
	}

	// Don't accept a null parameter pointer
	if (paramValue == NULL)
	{
		DO_LOG_INFO(log_handle,"Operation rejected due to NULL value for parameter");
		return (3);
	}

	LOCK_MEP_LIST(0);
	lMepNum = mepIdToMepNumNoLocking(mdIndex, maIndex, lMepId);
	// It is possible that this function can be called with an invalid MEP ID (netconf does it!) so just return an invalid MEPID error code.
	if (lMepNum == -1)
	{
		UNLOCK_MEP_LIST();
		goto LMEP_WRITE_MEPID_INVALID;
	}

	switch(mepParam)
	{
		case mepParam_mepPrimaryVid:			// (d)The Primary Vlan for the MEP
		{
			uint16_t	*primaryVidPtr = (uint16_t*)paramValue;

			// TODO: Not sure we should range check this as if it were a Vlan...it's really a ServiceID!
			if ((*primaryVidPtr < 0) || (*primaryVidPtr > MR_MAX_VLANS))
			{
MEP_WRITE_PRIMARY_VID_INVALID:
				UNLOCK_MEP_LIST();
				DO_LOG_INFO(log_handle,"Operation rejected due to invalid value (%d) for PrimaryVid",*primaryVidPtr);
				return (3);
			}

			if (*primaryVidPtr != 0)
			{
				// Verify that the new value is on the MA Vlan list (For this function the VLan value must be in the range 1 to 8191!)
				if ((!_maIsVLanOnList(mdIndex, maIndex, *primaryVidPtr)) )
				{
					goto MEP_WRITE_PRIMARY_VID_INVALID;
				}
			}

			// check to see if there is a change in value
			if (mepArray[lMepNum].mepInfo.primaryVid != *primaryVidPtr)
			{
#if ALLOW_MULTIPLE_VLANS_PER_MA
				oldVlan = getPrimaryVidForMepNoLocking(mdIndex, maIndex, lMepId);
				mepArray[lMepNum].mepInfo.primaryVid = *primaryVidPtr;

				// Primary Vid is changed so update the vlan table, the CCM template and any associated PM services

				// The vlan table should already have all MA vlans..so no need to modify it
				// update the vLan in the CCM txTemplate
				ccmUpdatePrimaryVlan(
						lMepNum,
						( *primaryVidPtr == 0 ? getPrimaryVLanForMa(mdIndex, maIndex, lMepId) : *primaryVidPtr),	// if new VID = 0, use MAs primary VID
						mepArray[lMepNum].mepCcInfo.ccmLtmPriority,
						_maGetTagType(mdIndex, maIndex),	//
						mepArray[lMepNum].mepInfo.iFace,
						(mepArray[lMepNum].mepInfo.active & mepArray[lMepNum].mepCcInfo.cciEnable));

				pmUpdatePrimaryVlan(
						mdIndex, maIndex, lMepId,
						( *primaryVidPtr == 0 ? getPrimaryVLanForMa(mdIndex, maIndex, lMepId) : *primaryVidPtr),	// if new VID = 0, use MAs primary VID
						_maGetTagType(mdIndex, maIndex),
						mepArray[lMepNum].mepInfo.iFace,
						(mepArray[lMepNum].mepInfo.active & mepArray[lMepNum].mepCcInfo.cciEnable));
#endif
				DO_LOG_NOTICE(log_handle,"On Local MEP index %lu.%lu.%d the primaryVid changed to %d",mdIndex, maIndex, lMepId,*primaryVidPtr);
			}
			break;
		}
		case mepParam_mepActive:				// (e)The administrative state of the MEP; true = enabled; false = disabled
		{
			bool *activePtr = (bool*)paramValue;
			mepId_t		rMepId;
			maNetInfo_t	maNetInfo;

			// check to see if there is a change in value
			if (mepArray[lMepNum].mepInfo.active != ((bool)*activePtr))
			{
				// get the necessary info from the MA
				maRead(mdIndex, maIndex, &maNetInfo, NULL, NULL, NULL, NULL, NULL);

				if (*activePtr == false)	// safer to check for false (==0)
				{
					mepArray[lMepNum].mepInfo.active = false;

					setHwLMepEnableState(mepArray[lMepNum].mepCtxt.rxLMepAddr, false);
					// Turn off the XConnSM and RMepErrSM
					ccDisableLMep(lMepNum);

					// Turn off all associated RMepSM
					rMepId = rMepReadList(mdIndex, maIndex,lMepId,0);
					while(rMepId != 0)
					{
						rMepDisable(mdIndex, maIndex, lMepId, rMepId);

						// Get the next MEP
						rMepId = rMepReadList(mdIndex, maIndex, lMepId, rMepId);
					}

					// turn off the CCM transmit
					cciDisable(lMepNum);
					UNLOCK_MEP_LIST();		// Unlock the mep here to prevent contention for the lock in the FNG SM
					// Reset the FNG state
					fngResetSM(lMepNum);
				}
				else
				{
					mepArray[lMepNum].mepInfo.active = true;

					// Turn on the XConnSM and RMepErrSM
					ccEnableLMep(lMepNum,maNetInfo.ccmInterval);

					// Turn on all associated RMepSM
					rMepId = rMepReadList(mdIndex, maIndex,lMepId,0);
					while(rMepId != 0)
					{
						if (rMepIsActive(mdIndex, maIndex, lMepId, rMepId))
						{
							// only re-enable the remote MEP if it is supposed to be enabled (active)
							rMepEnable(mdIndex, maIndex, lMepId, rMepId);
						}
						// Get the next MEP
						rMepId = rMepReadList(mdIndex, maIndex, lMepId, rMepId);
					}
#ifdef DEBUG_PRINT_L2
					ts_printf("mepArray[lMepNum].mepCcInfo.cciEnable1\n\r");
#endif

					// Only turn on the CCM transmit if it is supposed to be enabled
					if (mepArray[lMepNum].mepCcInfo.cciEnable)
					{
						cciEnable(lMepNum);
#ifdef DEBUG_PRINT_L2
						ts_printf("mepArray[lMepNum].mepCcInfo.cciEnable2\n\r");
#endif

					}
					setHwLMepEnableState(mepArray[lMepNum].mepCtxt.rxLMepAddr, true);
					UNLOCK_MEP_LIST();	// Released here because we 'return' at the end of this case branch - all to do with unlocking before calling FNG SM
				}
				// Send a Notification when the active state changes
				sendMepOpStateNotification(lMepNum, mdIndex, maIndex, lMepId, log_handle, mepArray[lMepNum].mepInfo.active, (mepArray[lMepNum].mepInfo.active ? opStateEnabled : opStateDisabled));
#ifdef DEBUG_PRINT_L2
				ts_printf("On Local MEP index %d.%d.%d the Administrative state was changed to %d",mdIndex, maIndex, lMepId,mepArray[lMepNum].mepInfo.active ? 1:0);
#endif
				return (4); // Operation accepted
			}
			break;
		}
		case mepParam_mepCciEnable:				// (g)Enable/Disable CCM generation
		{
			bool	*mepCciEnablePtr = (bool*)paramValue;
#ifdef DEBUG_PRINT_L2
			ts_printf("case mepParam_mepCciEnable1:\n\r");
#endif
			if (mepArray[lMepNum].mepCcInfo.cciEnable != *mepCciEnablePtr)
			{
				if (*mepCciEnablePtr == false)	// safer to check for false (==0)
				{
					mepArray[lMepNum].mepCcInfo.cciEnable = false;
					// Disable CCM transmit
					cciDisable(lMepNum);
				}
				else
				{
					mepArray[lMepNum].mepCcInfo.cciEnable = true;
					if (mepArray[lMepNum].mepInfo.active)
					{
						// Enable CCM transmit only if the MEP is active
					    ts_printf("case mepParam_mepCciEnable2:cciEnable(lMepNum);\n\r");
						cciEnable(lMepNum);
					}
				}
#ifdef DEBUG_PRINT_L2
				ts_printf("On Local MEP index %d.%d.%d cciEnable was changed to %d",mdIndex, maIndex, lMepId,mepArray[lMepNum].mepCcInfo.cciEnable ? 1:0);
#endif
				//DO_LOG_NOTICE(log_handle,"On Local MEP index %lu.%lu.%d cciEnable was changed to %s",mdIndex, maIndex, lMepId,mepArray[lMepNum].mepCcInfo.cciEnable ? "True":"False");
			}
			break;
		}
		case mepParam_mepCcmLtmPriority:		// (h)The Priority value to use for CCM and LTM transmit
		{
			prio_t	*ccmLtmPriorityPtr = (prio_t*)paramValue;
			if ((*ccmLtmPriorityPtr < 0) || (*ccmLtmPriorityPtr > 7))
			{
				UNLOCK_MEP_LIST();
				DO_LOG_INFO(log_handle,"Operation rejected due to invalid value (%d) for ccmLtmPriority",*ccmLtmPriorityPtr);
				return (3);
			}
			// Check for a change in value
			if (mepArray[lMepNum].mepCcInfo.ccmLtmPriority != *ccmLtmPriorityPtr)
			{
				mepArray[lMepNum].mepCcInfo.ccmLtmPriority = *ccmLtmPriorityPtr;

				// update the CCM txTemplate
				ccmUpdatePrimaryVlan(
						lMepNum,
						getPrimaryVidForMepNoLocking(mdIndex, maIndex, lMepId),
						mepArray[lMepNum].mepCcInfo.ccmLtmPriority,
						_maGetTagType(mdIndex, maIndex),
						mepArray[lMepNum].mepInfo.iFace,
						(mepArray[lMepNum].mepInfo.active & mepArray[lMepNum].mepCcInfo.cciEnable));
				// TODO: if LB is active on this MEP should I stop it and update the LB template? I think leave it to mgnt to resolve!!

				DO_LOG_NOTICE(log_handle,"On Local MEP index %lu.%lu.%d ccmLtmPriority was changed to %d",mdIndex, maIndex, lMepId,*ccmLtmPriorityPtr);
			}
			break;
		}
		case mepParam_mepLowPrDef:				// (k)Specify the lowest priority defect that is allowed generate a Fault Alarm
		{
			lowPrDef_t	*lowPrDefPtr = (lowPrDef_t*)paramValue;
			if ((*lowPrDefPtr < allDef) || (*lowPrDefPtr > noXcon))
			{
				UNLOCK_MEP_LIST();
				DO_LOG_INFO(log_handle,"Operation rejected due to invalid value (%d) for lowPrDef",*lowPrDefPtr);
				return (3);
			}
			// Check for a change in value
			if (_getFngLowPrDef(lMepNum) != *lowPrDefPtr)
			{
				_setFngLowPrDef(lMepNum, *lowPrDefPtr);
				DO_LOG_NOTICE(log_handle,"On Local MEP index %lu.%lu.%d the lowest Priority Defect level was changed to %d",mdIndex, maIndex, lMepId,*lowPrDefPtr);
			}
			break;
		}
		case mepParam_mepFngAlarmTime:			// (l)The time that defects MUST be present before a Fault Alarm is issued (default =250 => 2.5sec)
		{
			uint16_t	*fngAlarmTimePtr = (uint16_t*)paramValue;
			if ((*fngAlarmTimePtr < 250) || (*fngAlarmTimePtr > 1000))
			{
				UNLOCK_MEP_LIST();
				DO_LOG_INFO(log_handle,"Operation rejected due to invalid value (%d) for fngAlarmTime",*fngAlarmTimePtr);
				return (3);
			}
			// Check for a change in value
			if (_getFngAlarmTime(lMepNum) != *fngAlarmTimePtr)
			{
				_setFngAlarmTime(lMepNum, *fngAlarmTimePtr);
				DO_LOG_NOTICE(log_handle,"On Local MEP index %lu.%lu.%d the FNG Alarm Time Value was changed to %f seconds",mdIndex, maIndex, lMepId,(*fngAlarmTimePtr)/100.0);
			}
			break;
		}
		case mepParam_mepFngResetTime:			// (m)The time that defects MUST be absent before resetting a Fault Alarm (default =1000 => 10sec)
		{
			uint16_t	*fngResetTimePtr = (uint16_t*)paramValue;
			if ((*fngResetTimePtr < 250) || (*fngResetTimePtr > 1000))
			{
				UNLOCK_MEP_LIST();
				DO_LOG_INFO(log_handle,"Operation rejected due to invalid value (%d) for fngResetTime",*fngResetTimePtr);
				return (3);
			}
			// Check for a change in value
			if (_getFngResetTime(lMepNum) != *fngResetTimePtr)
			{
				_setFngResetTime(lMepNum, *fngResetTimePtr);
				DO_LOG_NOTICE(log_handle,"On Local MEP index %lu.%lu.%d the FNG Reset Time Value was changed to %f seconds",mdIndex, maIndex, lMepId,(*fngResetTimePtr)/100.0);
			}
			break;
		}
		case mepParam_mepActiveRMepListAdd:		// (ae) Add a remote MEP to the active remote MEP list (enable remote MEP)
		case mepParam_mepActiveRMepListRemove:	// (ae) Remove a remote MEP from the active remote MEP list (disable remote MEP)
		{
			mepId_t		*rMepIdPtr = (mepId_t*)paramValue;
			// Make sure the mepId is on the MA list and not the same as this MEPs ID
			if (!_maIsMepIdOnList(mdIndex, maIndex, *rMepIdPtr) || (*rMepIdPtr == lMepId))
			{
				UNLOCK_MEP_LIST();
				DO_LOG_INFO(log_handle,"Operation rejected due to invalid value (%d) for rMepId",*rMepIdPtr);
				return (3);
			}
			if (mepParam  == mepParam_mepActiveRMepListAdd)
			{
				if (!rMepIsActive(mdIndex, maIndex, lMepId, *rMepIdPtr))
				{
					rMepSetActive(mdIndex, maIndex, lMepId, *rMepIdPtr);
					if (mepArray[lMepNum].mepInfo.active)
					{
						// Only enable the remote MEP if the local MEP is active
						rMepEnable(mdIndex, maIndex, lMepId,*rMepIdPtr);
					}
					DO_LOG_NOTICE(log_handle,"Added MEPID %d to the Active Peer MEP List for Local MEP with ID %lu.%lu.%d",*rMepIdPtr,mdIndex, maIndex, lMepId);
				}
			}
			else
			{
				if (rMepIsActive(mdIndex, maIndex, lMepId, *rMepIdPtr))
				{
					rMepClrActive(mdIndex, maIndex, lMepId, *rMepIdPtr);
					rMepDisable(mdIndex, maIndex, lMepId,*rMepIdPtr);
					DO_LOG_NOTICE(log_handle,"Removed MEPID %d from the Active Peer MEP List for Local MEP with ID %lu.%lu.%d",*rMepIdPtr,mdIndex, maIndex, lMepId);
				}
			}

			UNLOCK_MEP_LIST();		// We must release the Lock before calling the FNG SM to avoid deadlock
			// Ask the FNG to re-evaluate its state for this local MEP after the change to Remote MEPs
			fngReEvaluateSM(mdIndex, maIndex, lMepId, lMepNum);
			return (4); // Operation accepted
			break;
		}
		case mepParam_mepLbMefInterval:
		{
			uint16_t		*intervalPtr = (uint16_t*)paramValue;

			// Read the MEP properties first as we need to know some of these and we will be updating mepLbMefInfo
			lMepRead(mdIndex, maIndex,lMepId,NULL,NULL,NULL,&mepLbMefInfo,NULL,NULL,NULL);

			if (*intervalPtr >= ((LB_BURST_REG_BURST_IBG_MASK >> LB_BURST_REG_BURST_IBG_OFFSET)/1000))
			{
				DO_LOG_INFO(log_handle,"Operation rejected due to invalid value for interval %d (min=1;max=%d)",*intervalPtr,((LB_BURST_REG_BURST_IBG_MASK >> LB_BURST_REG_BURST_IBG_OFFSET)/1000));
				return (3);
			}
			mepLbMefInfo.interval = *intervalPtr;
			lbWrite(mdIndex, maIndex,lMepId,&mepLbMefInfo);
			break;
		}
		case mepParam_mepLbMefMulticastEnable:
		{
			bool		*mcEnablePtr = (bool*)paramValue;

			// Read the MEP properties first as we need to know some of these and we will be updating mepLbMefInfo
			lMepRead(mdIndex, maIndex,lMepId,NULL,NULL,NULL,&mepLbMefInfo,NULL,NULL,NULL);

			mepLbMefInfo.multicastEnable = (*mcEnablePtr == 0 ? false : true);
			lbWrite(mdIndex, maIndex,lMepId,&mepLbMefInfo);
			break;
		}
		case mepParam_mepLbMefFrameSize:
		{
			uint16_t		*frameSizePtr = (uint16_t*)paramValue;

			// Read the MEP properties first as we need to know some of these and we will be updating mepLbMefInfo
			lMepRead(mdIndex, maIndex,lMepId,NULL,NULL,&mepLbInfo,&mepLbMefInfo,NULL,NULL,NULL);

			// The MEF Tx Information is only relevant if _txLbmDataTlvLength is 0.
			if ((mepLbInfo._txLbmDataTlvLength == 0) && ((*frameSizePtr > MAX_LBM_PKT_SIZE) || (*frameSizePtr < 64)))
			{
				DO_LOG_INFO(log_handle,"Operation rejected due to invalid value for frameSize %d (min=64;max=%d)",*frameSizePtr, MAX_LBM_PKT_SIZE);
				return (3);
			}
			mepLbMefInfo.frameSize = *frameSizePtr;
			lbWrite(mdIndex, maIndex,lMepId,&mepLbMefInfo);
			break;
		}
		default:
		{
			UNLOCK_MEP_LIST();
			DO_LOG_INFO(log_handle,"Operation rejected due to the selected managed object being Read-Only");
			return (2);
			break;
		}
	}

	UNLOCK_MEP_LIST();
	return (4); // Operation accepted
}

/*
 * @brief Append a new Remote MEP to the selected Local MEP
 *
 * This will add a new remote MEP
 */
void _lMepAppendRMepDB(mdIndex_t mdIndex, maIndex_t maIndex, mepId_t lMepId, mepId_t rMepId)
{
	if (mepIsLMepIdValid(mdIndex, maIndex, lMepId))
	{
		// The Local MEP exists, so Append a new remote MEP
		rMepDBAdd(mdIndex, maIndex, lMepId, rMepId);
		rMepSetActive(mdIndex, maIndex, lMepId, rMepId);
		if (_mepIsActive(mdIndex, maIndex, lMepId))
		{
			rMepEnable(mdIndex, maIndex, lMepId, rMepId);
		}
	}
}

/*
 * @brief Remove a Remote MEP from the selected Local MEP
 *
 * This will remove a Remote MEP
 */
void _lMepRemoveRMepDB(mdIndex_t mdIndex, maIndex_t maIndex, mepId_t lMepId, mepId_t rMepId)
{
	if (mepIsLMepIdValid(mdIndex, maIndex, lMepId))
	{
		// The Local MEP exists, so Remove the remote MEP with ID = rMepId
		rMepDBRemove(mdIndex, maIndex, lMepId, rMepId);
	}
}

/*
 * @brief Get a pointer to the Head of the remote MEP list associated with the selected Local MEP
 */
struct llRMepHead_s* _getRMepDBListHead(mdIndex_t mdIndex, maIndex_t maIndex, mepId_t lMepId)
{
	mepNum_t	lMepNum;

	struct llRMepHead_s *rMepListHeadPtr;

	LOCK_MEP_LIST(0);
	// Test is the mdIndex & maIndex known
	if (!maIsMaMibIdxValid(mdIndex,maIndex))
	{
		UNLOCK_MEP_LIST();
		DO_LOG_DEBUG(log_handle,"Invalid or nonexistent MA Index (%lu.%lu)",mdIndex,maIndex);
		return (NULL);	//Operation rejected due to invalid or nonexistent Ma or MD
	}

	lMepNum = mepIdToMepNumNoLocking(mdIndex,maIndex,lMepId);
	if (lMepNum < 0)
	{
		UNLOCK_MEP_LIST();
		DO_LOG_DEBUG(log_handle,"Invalid or nonexistent local MEPID (%lu.%lu.%d)",mdIndex,maIndex,lMepId);
		return (NULL);	//Operation rejected due to invalid or nonexistent mepID
	}

	rMepListHeadPtr = &mepArray[lMepNum].llRMepHead;
	UNLOCK_MEP_LIST();
	return(rMepListHeadPtr);
}

/*
 * @brief Get a pointer to the Head of the Loss or Delay Services list associated with the selected Local MEP
 */
struct llPmSvcHead_s* _getPmListHead(bool isDm, mdIndex_t mdIndex, maIndex_t maIndex, mepId_t lMepId)
{
	mepNum_t	lMepNum;

	struct llPmSvcHead_s *pmListHeadPtr;

	LOCK_MEP_LIST(0);
	// Test is the mdIndex & maIndex known
	if (!maIsMaMibIdxValid(mdIndex,maIndex))
	{
		UNLOCK_MEP_LIST();
		DO_LOG_DEBUG(log_handle,"Invalid or nonexistent MA Index (%lu.%lu)",mdIndex,maIndex);
		return (NULL);	//Operation rejected due to invalid or nonexistent Ma or MD
	}

	lMepNum = mepIdToMepNumNoLocking(mdIndex,maIndex,lMepId);
	if (lMepNum < 0)
	{
		UNLOCK_MEP_LIST();
		DO_LOG_DEBUG(log_handle,"Invalid or nonexistent local MEPID (%lu.%lu.%d)",mdIndex,maIndex,lMepId);
		return (NULL);	//Operation rejected due to invalid or nonexistent mepID
	}

	if (isDm)
	{
		pmListHeadPtr = &mepArray[lMepNum].llDmSvcHead;
	}
	else
	{
		pmListHeadPtr = &mepArray[lMepNum].llLmSvcHead;
	}
	UNLOCK_MEP_LIST();
	return(pmListHeadPtr);
}

/*
 * @brief Update the stored context value for the Transmit RDI
 *
 * This function needs to be called when the FNG S/M detects
 * a CCM receive error on this MEP
 */
void _mepRdiStatusUpdate(mepNum_t lMepNum, bool rdiSet)
{
	mepArray[lMepNum].mepMefInfo.rdiTransmitStatus = rdiSet;
}

/*
 * @brief	Returns the full set of parent Indices for the local MEP identified by lMepNum
 */
void _lMepGetParentsIndices(lMepNum_t lMepNum, mepParentIndices_t *mepParentIndices)
{
	maNum_t		maNum;
	mdNum_t		mdNum;

	mepParentIndices->lMepId = _mepNumToMepId(lMepNum, &maNum);
	EGL_ASSERT(mepParentIndices->lMepId != 0,1);

	mepParentIndices->maIndex = _maNumToMibIndex(maNum, &mdNum);
	EGL_ASSERT(mepParentIndices->maIndex != 0,1);

	mepParentIndices->mdIndex = _mdNumToMibIndex(mdNum);
	EGL_ASSERT(mepParentIndices->mdIndex != 0,1);
}


void _mepIncrementOperNextIndex(mepNum_t lMepNum, pmIndex_t lastPmIdx)
{
	LOCK_MEP_LIST(0);

	// The MA index can be specified by the user. So
	// it is necessary to keep the mdMibInfo.maNextMibIdx aligned
	if (mepArray[lMepNum].mepMefInfo.operNextIndex <= lastPmIdx)
	{
		mepArray[lMepNum].mepMefInfo.operNextIndex = lastPmIdx + 1;
	}
	UNLOCK_MEP_LIST();
}

uint16_t getLMepCount(void)
{
	return (s_lMepCount);
}

uint16_t getRMepCount(void)
{
	return rMepDBGetRMepCount();
}

void _setMepLastDefectSentStatus(lMepNum_t lMepNum, fngDefect_t lastDefect)
{
	mepArray[lMepNum].mepMefInfo.lastDefectSentStatus = lastDefect;
}

fngDefect_t _getMepLastDefectSentStatus(lMepNum_t lMepNum)
{
	return(mepArray[lMepNum].mepMefInfo.lastDefectSentStatus);
}

void _setMepRdiTransmitStatus(lMepNum_t lMepNum, bool rdiIsSet)
{
	mepArray[lMepNum].mepMefInfo.rdiTransmitStatus = rdiIsSet;
}

/*
 * 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 setMepLogLevel(log_level_t logLevel)
{
	setModuleLogLevel(log_handle,logLevel);
}

bool _lMepNumIsValid(lMepNum_t lMepNum)
{
	if (mepArray[lMepNum].mepCtxt.inUse)
	{
		return (true);
	}
	return (false);
}

/*
 * update the templates of all Local MEPs, the rxLmepNum table, the TxLMepNum table and all S/M timers with a new interval
 */
void _lMepUpdateCcmInterval(mdIndex_t mdIndex, maIndex_t maIndex, mepId_t lMepId, ccmInterval_t newCcmInterval)
{
	lMepNum_t lMepNum;

	LOCK_MEP_LIST(0);
	lMepNum = mepIdToMepNumNoLocking(mdIndex,maIndex,lMepId);
	if (lMepNum == 0)
	{
		UNLOCK_MEP_LIST();
		DO_LOG_ERROR(log_handle,"_lMepUpdateCcmInterval() failed because local MEP %lu.%lu.%d could not be found",mdIndex, maIndex, lMepId);
		return;
	}
	//ASSERT(lMepNum >= 0,1);

	changeMepCcmInterval(lMepNum, newCcmInterval);
	UNLOCK_MEP_LIST();
}

/*
 * @brief Used to add/update the value of a TLV in the CCM for the MEP
 */
void _lMepUpdateCCMTLV(mdIndex_t mdIndex, maIndex_t maIndex, mepId_t lMepId, iFace_t iFace, tlvType_t type,uint8_t length,void *value)
{
	lMepNum_t	lMepNum;
	maMEFInfo_t	maMEFInfo;

	LOCK_MEP_LIST(0);
	lMepNum = mepIdToMepNumNoLocking(mdIndex,maIndex,lMepId);
	if (lMepNum == 0)
	{
		UNLOCK_MEP_LIST();
		DO_LOG_ERROR(log_handle,"_lMepUpdateCCMTLV() failed because local MEP %lu.%lu.%d could not be found",mdIndex, maIndex, lMepId);
		return;
	}
	//ASSERT(lMepNum >= 0,1);

	maRead(mdIndex, maIndex, NULL, NULL, &maMEFInfo, NULL, NULL, NULL);

	if (mepArray[lMepNum].mepInfo.iFace == iFace)
	{
		// Only consider updating the MEP if it's on the specified interface
		if ((type == portStatusTlvType) || (type == interfaceStatusTlvType))
		{
			if (((type == portStatusTlvType) && maMEFInfo.maMEFCompInfo.portStatusTlvIncluded) ||
					((type == interfaceStatusTlvType) && maMEFInfo.maMEFCompInfo.interfaceStatusTlvIncluded))
			{
				// Only call the function to update/add the TLV if the MEP is enabled to send the relevant TLV
				ccmAddUpdateTlv(lMepNum,type,length,value);
			}
		}
		else
		{
			// If not PS or IS we need to call the function
			ccmAddUpdateTlv(lMepNum,type,length,value);
		}
	}
	UNLOCK_MEP_LIST();
}

iFace_t _getMepInterface(lMepNum_t lMepNum)
{
	// This function assumes lMepNum is valid!
	return(mepArray[lMepNum].mepInfo.iFace);
}

/*
 * @brief read the Link trace stats info and the MEPs port MAC address
 */
int	_readMepLtInfo(mdIndex_t mdIndex, maIndex_t maIndex, mepId_t lMepId, _mepLtInfo_t *mepLtInfo, macAddress_t mepMAC)
{
	mepNum_t	lMepNum;

	// Check of the MEP indices are valid
	if (!isMepIdValid(mdIndex, maIndex, lMepId, &lMepNum))
		return (1);

	// copy the mepLtInfo
	memcpy(mepLtInfo,&mepArray[lMepNum].mepLtInfo, sizeof(_mepLtInfo_t));
	// get the port MAC for the port the MEP in on.
	getInterfaceMAC(mepArray[lMepNum].mepInfo.iFace, mepMAC);
	return (2);
}

void _incUnexpectedLtrIn(lMepNum_t lMepNum)
{
	// Make sure not to use an invalid lMepNum
	if ((lMepNum < 0) || (lMepNum >= MAX_NUM_MEPS))
		return;

	mepArray[lMepNum].mepLtInfo.unexpectedLtrIn++;
}

void _incLtmTransmitted(mdIndex_t mdIndex, maIndex_t maIndex, mepId_t lMepId)
{
	mepNum_t	lMepNum;

	// Check of the MEP indices are valid
	if (!isMepIdValid(mdIndex, maIndex, lMepId, &lMepNum))
		return;

	mepArray[lMepNum].mepLtInfo.mepLtMefInfo.ltmTransmitted++;
}

void _incLtmReceived(lMepNum_t lMepNum)
{
	// Make sure not to use an invalid lMepNum
	if ((lMepNum < 0) || (lMepNum >= MAX_NUM_MEPS))
		return;

	mepArray[lMepNum].mepLtInfo.mepLtMefInfo.ltmReceived++;
}

void _incLtrTransmitted(lMepNum_t lMepNum)
{
	// Make sure not to use an invalid lMepNum
	if ((lMepNum < 0) || (lMepNum >= MAX_NUM_MEPS))
		return;

	mepArray[lMepNum].mepLtInfo.mepLtMefInfo.ltrTransmitted++;
}

void _incLtrReceived(lMepNum_t lMepNum)
{
	// Make sure not to use an invalid lMepNum
	if ((lMepNum < 0) || (lMepNum >= MAX_NUM_MEPS))
		return;

	mepArray[lMepNum].mepLtInfo.mepLtMefInfo.ltrReceived++;
}

void _setTxLtmResult(mdIndex_t mdIndex, maIndex_t maIndex, mepId_t lMepId, bool value)
{
	mepNum_t	lMepNum;

	// Check of the MEP indices are valid
	if (!isMepIdValid(mdIndex, maIndex, lMepId, &lMepNum))
		return;

	mepArray[lMepNum].mepLtInfo.txLtmResult = value;
}
uint32_t _getNextLtmSeqNum(mdIndex_t mdIndex, maIndex_t maIndex, mepId_t lMepId)
{
	mepNum_t	lMepNum;

	// Check of the MEP indices are valid
	if (!isMepIdValid(mdIndex, maIndex, lMepId, &lMepNum))
		return 0;

	return(mepArray[lMepNum].mepLtInfo.ltmNextSeqNumber);
}

void _incNextLtmSeqNumber(mdIndex_t mdIndex, maIndex_t maIndex, mepId_t lMepId)
{
	mepNum_t	lMepNum;

	// Check of the MEP indices are valid
	if (!isMepIdValid(mdIndex, maIndex, lMepId, &lMepNum))
		return;

	mepArray[lMepNum].mepLtInfo.ltmNextSeqNumber++;
}

void _setTxLtmSeqNumber(mdIndex_t mdIndex, maIndex_t maIndex, mepId_t lMepId, uint32_t seqNum)
{
	mepNum_t	lMepNum;

	// Check of the MEP indices are valid
	if (!isMepIdValid(mdIndex, maIndex, lMepId, &lMepNum))
		return;

	mepArray[lMepNum].mepLtInfo.txLtmSeqNumber = seqNum;
}
