/*******************************************************************************
 * (c) Copyright 2015 Microsemi Corporation.  All rights reserved.
 *
 *
 *
 * SVN $Revision: $
 * SVN $Date: $
 */

#include <api_oam_sdk/common/error_codes/inc/mr_error_codes.h>
#include <api_oam_sdk/continuity_check/inc/cc.h>
#include <api_oam_sdk/fault_notification_generator/inc/fng.h>
#include <api_oam_sdk/maintenance_association/src/ma_types.h>
#include <api_oam_sdk/maintenance_domain/src/md_types.h>
#include <api_oam_sdk/mep/src/mep_types.h>
#include <api_oam_sdk/system/inc/system.h>
#include <api_oam_sdk/table_access/inc/table_access.h>
#include <string.h>
#include <syslog.h>

#include "egl_types.h"

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

static maEntry_t	maArray[MAX_NUM_MA];
static uint16_t		log_handle				= 0;		// handle used to log messages for this module
static osMutex_t	maListLock				= NULL;
static bool			maInitialised			= false;

static const uint32_t ccmIntervalToMillisec[8] = {0,4,10,100,1000,10000,60000,600000};

#define LOCK_MA_LIST(timeout)	;//lockMDBranch(0,timeout)	//osRecursiveMutexTake(maListLock,timeout)
#define UNLOCK_MA_LIST()		;//unlockMDBranch(0)		//osRecursiveMutexGive(maListLock)
extern uint8_t       g_ccm_interval;

/*
 * @brief	This sets the MAEntry values to their default
 *
 * In most cases this should only be done with maLock enabled
 * to ensure thread safe operation
 */
static void setMaDefaults(maEntry_t *maEntry)
{
	memset(maEntry,0,sizeof(maEntry_t));
	maEntry->maCtxt.maNum 					= INVALID_MA_NUM;
	maEntry->maNetInfo.ccmInterval			= g_ccm_interval;
	maEntry->maCompInfo.mhfCreation			= ma_defMHFdefer;
	maEntry->maCompInfo.mhfIdPermission		= ma_sendIdDefer;
	maEntry->maCompInfo.primarySelectorType	= ss_vlanId;
	maEntry->maCompInfo.tagType				= VLAN_NONE;
	maEntry->maMEFInfo.maMEFCompInfo.connectivityStatusInterval	= (3.5) * (maEntry->maNetInfo.ccmInterval); // 3.5 x ccmInterval (in milliseconds)
	//maEntry->maMEFInfo.maMEFCompInfo.portStatusTlvIncluded		= true;		// Leave these false unless Y1731 MEG is True
	//maEntry->maMEFInfo.maMEFCompInfo.interfaceStatusTlvIncluded	= true;		// Leave these false unless Y1731 MEG is True
	maEntry->maMEFInfo.maMEFNetInfo.megIdFormat					= ma_characterString;
}

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

	// Initialise all entries in the MA array to defaults
	for (k = 0; k < MAX_NUM_MA; k++)
	{
		setMaDefaults(&maArray[k]);
	}

	DO_LOG_DEBUG(log_handle,"Initialised the MA List");
}

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

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

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

		DO_LOG_DEBUG(log_handle,"Preparing the MA List for first use");
		LOCK_MA_LIST(0);
		clearMaList();
		UNLOCK_MA_LIST();
		maInitialised = true;
	}
}

/*
 * @brief Convert an maNum into a MA MIB index
 */
maIndex_t _maNumToMibIndex(maNum_t maNum, mdNum_t *parentMdNum)
{
	maIndex_t maIndex  = 0;
	LOCK_MA_LIST(0);
	if (maArray[maNum].maCtxt.inUse)
	{
		maIndex = maArray[maNum].maCtxt.mibIdx;
		if (parentMdNum != NULL)
		{
			*parentMdNum = maArray[maNum].maCtxt.parentMdNum;
		}
	}
	UNLOCK_MA_LIST();
	return (maIndex);
}

/*
 * @brief Convert a MA MIB index into an maNum
 */
static maNum_t	mibIndexToMaNumNoLocking(mdIndex_t mdIndex, maIndex_t maIndex)
{
	maEntry_t	*maEntryPtr;
	maNum_t		maNum = -1;

	// Start searching valid MAs at the head of the list
	maEntryPtr = TAILQ_FIRST(_getMaListHead(mdIndex));
	while (maEntryPtr != NULL)
	{
		if (maEntryPtr->maCtxt.mibIdx == maIndex)
		{
			maNum = maEntryPtr->maCtxt.maNum;
			return (maNum);	//found it
		}
		maEntryPtr = TAILQ_NEXT(maEntryPtr,ll);
	}
	return -1;	// maIndex not found
}

/*
 * @brief Convert a MA MIB index into an maNum
 */
maNum_t	_maMibIndexToMaNum(mdIndex_t mdIndex, maIndex_t maIndex)
{
	maNum_t maNum;

	LOCK_MA_LIST(0);
	maNum = mibIndexToMaNumNoLocking(mdIndex, maIndex);
	UNLOCK_MA_LIST();
	return maNum;
}

/*
 * @brief Read the next entry in the MA List
 */
maIndex_t maReadList(mdIndex_t mdIndex, maIndex_t prevMaIndex)
{
	maNum_t 	nextMaNum;
	maIndex_t	theMaMibIdx;

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

	LOCK_MA_LIST(0);
	if (prevMaIndex == 0)
	{
		if (!(TAILQ_EMPTY(_getMaListHead(mdIndex))))
		{
			theMaMibIdx = TAILQ_FIRST(_getMaListHead(mdIndex))->maCtxt.mibIdx;
			UNLOCK_MA_LIST();
			return theMaMibIdx;
		}
	}
	else
	{
		nextMaNum = mibIndexToMaNumNoLocking(mdIndex, prevMaIndex);
		if (nextMaNum != -1)
		{
			if (TAILQ_NEXT(&maArray[nextMaNum],ll) != NULL)
			{
				theMaMibIdx = TAILQ_NEXT(&maArray[nextMaNum],ll)->maCtxt.mibIdx;
				UNLOCK_MA_LIST();
				return theMaMibIdx;
			}
		}
	}
	UNLOCK_MA_LIST();
	return 0;
}

/*
 * @brief	Find the next free MA Entry
 */
static maEntry_t* getNextFreeMaEntry(void)
{
	maNum_t	k;

	for (k = 0; k < MAX_NUM_MA; k++)
	{
		if (!maArray[k].maCtxt.inUse)
		{
			maArray[k].maCtxt.maNum = k;
			maArray[k].maCtxt.inUse = true;
			return (&maArray[k]);
		}
	}
	return NULL;
}

/*
 * @brief tests that the formatting of the supplied MA name is valid
 */
static bool isMANFLInvalid(char name[45+1],maNameType_t maType)
{
	int     k;

	switch(maType)
	{
		case ma_primaryVid:
		{
			// TODO: Find out what PRIMARYVID format is!
			break;
		}
		case ma_characterString:
		{
			// The restriction here is that the string must be less than 45 bytes and should not contain ascii characters 0-31
			if ((strlen(name) > 43) || (strlen(name) == 0))
			{
				DO_LOG_DEBUG(log_handle,"The MA format is characterString (%d), but length (%d) is not >1 and <= 43",maType, strlen(name));
				return(true);
			}

			for (k=0;k<strlen(name);k++)
			{
				if (name[k] < 32)
				{
					DO_LOG_DEBUG(log_handle,"The MA format is characterString (%d), but the string contains illegal characters",maType);
					return(true);
				}
			}
			break;
		}
		case ma_unsignedInt16:
		{
			// 2 byte integer
			break;
		}
		case ma_rfc2685VpnId:
		{
			// The global VPN Identifier format is:
			// 3 octet VPN authority Organizationally Unique Identifier followed by
			// 4 octet VPN index identifying VPN according to OUI
			break;
		}
		case ma_ituY1731_32:
		{
			if ((strlen(name) > 13) || (strlen(name) < 8))
			{
				DO_LOG_DEBUG(log_handle,"The MA format is Y1731 (ICC based) (%d), but length (%d) is not >= 8 and <= 13",maType, strlen(name));
				return(true);
			}
			break;
		}
		default:
		{
			DO_LOG_DEBUG(log_handle,"Unknown MA Name type (%d) provided",maType);
			return(true);
		}
	}
	return(false);
}

/*
 * @brief test is the MA name already in use
 */
static bool isMANameInUse(mdIndex_t mdIndex, char* maName, maNameType_t type)
{
	maEntry_t	*maEntryPtr;
	bool		compareAsStr;
	uint8_t		compareLen;

	// Start searching valid MAs from the head of the list
	maEntryPtr = TAILQ_FIRST(_getMaListHead(mdIndex));
	while (maEntryPtr != NULL)
	{
		if (maEntryPtr->maNetInfo.type == type)
		{
			switch(type)
			{
				case ma_primaryVid:
					compareLen = 2;
					compareAsStr = false;
					break;
				case ma_characterString:
					compareLen = 0;
					compareAsStr = true;
					break;
				case ma_unsignedInt16:
					compareLen = 2;
					compareAsStr = false;
					break;
				case ma_rfc2685VpnId:
					compareLen = 7;
					compareAsStr = false;
					break;
				case ma_ituY1731_32:
					compareLen = 0;
					compareAsStr = true;
					break;
				default:
					compareLen = 0;
					compareAsStr = true;
			}
			// The types match check the data
			if (compareAsStr)
			{
				if (strcmp(maEntryPtr->maNetInfo.name,maName) == 0)
				{
					return true;
				}
			}
			else
			{
				if (memcmp(maEntryPtr->maNetInfo.name,maName,compareLen) == 0)
				{
					return true;
				}
			}
		}
		maEntryPtr = TAILQ_NEXT(maEntryPtr,ll);
	}
	return false;	// MA name not in use
}

/*
 * @brief	Add a new maEntry into the MA List
 */
static void addMaToList(mdIndex_t mdIndex, maEntry_t *maEntry)
{
	struct llMaHead_s *llMaHeadPtr;
	ASSERT_NOT_NULL(maEntry);

	llMaHeadPtr = _getMaListHead(mdIndex);
	TAILQ_INSERT_TAIL(llMaHeadPtr,maEntry,ll);
	_mdIncNextMaIdx(mdIndex, maEntry->maCtxt.mibIdx);
}

/*
 * @brief	Remove the MA entry, pointed to by maEntry, from the MA List
 */
static void removeMaFromList(mdIndex_t mdIndex, maEntry_t *maEntry)
{
	ASSERT_NOT_NULL(maEntry);

	TAILQ_REMOVE(_getMaListHead(mdIndex),maEntry,ll);
	setMaDefaults(maEntry);
}

static bool isVlanValid(uint16_t *vLanList)
{
	uint8_t	k = 0;
	if (vLanList == NULL)
	{
		return false;
	}
	while (vLanList[k] != 0)
	{
		if (vLanList[k] > 4095)
		{
			return false;
		}
		// TODO: add further tests to validate vlans
		k++;
		// Stop when we reach the limit of the list
		if (k == MAX_NUM_VLANS_PER_MA)
		{
			return false;
		}
	}
	if (k == 0)
	{
		return false;
	}
	return(true);
}


/*
 * @brief Generate a MAID from the MD and MA names
 *
 */
void generateMaid(mdName_t mdName, mdNameType_t mdNameType, maName_t maName, maNameType_t maNameType, maid_t  maid)
{
	int idx = 0;
	size_t	mdNameLength;
	size_t	maNameLength;

	memset(maid,0,48);

	// 1st byte in the MAID is the mdType, copy it straight in.
	maid[idx] = mdNameType;
	idx++;

	if (mdNameType != md_none)
	{
		// There is an MD Name present...so add it to the Maid

		switch(mdNameType)
		{
			case md_macaddressAndUInt:
			{
				// 2nd byte in the MAID is the name length
				maid[idx] = 8; // The MAC address is 6 bytes and the integer is 2 bytes
				idx++;
				memcpy(&maid[idx],&mdName[0],8);
				idx += 8;
				mdNameLength = 8;
				break;
			}
			case md_dnsLikeName:
			case md_characterString:
			//case md_y1731:
			{
				// 2nd byte in the MAID is the name length
				mdNameLength  = strlen(mdName);
				if (mdNameLength > 43)
				{
					DO_LOG_ERROR(log_handle,"generateMaid(). invalid MD name length. Max allowed 43, got %d",mdNameLength);
					mdNameLength = 43;
				}
				//ASSERT(mdNameLength <= 43,ERR_GENERATING_MAID);		//

				maid[idx] = mdNameLength;
				idx++;
				memcpy(&maid[idx],&mdName[0],mdNameLength);
				idx += mdNameLength;
				break;
			}
			default:
			{
				DO_LOG_ERROR(log_handle,"generateMaid(). invalid/unsupported MD name type. %d to %d supported, but got %d",md_none, md_characterString, mdNameType);
				//ASSERT(0,ERR_INVALID_MD_NTL);
			}
		}
	}
	else
	{
		mdNameLength  = 0;		// mdNameType == md_none
	}

	// Insert the ma Type byte
	maid[idx] = maNameType;
	idx++;
	switch(maNameType)
	{
		case ma_primaryVid:		// TODO: not sure of the length of PRIMARYVID, find out and add code.
		case ma_unsignedInt16:
		{
			if ((mdNameLength + 2 + ((mdNameType == md_none) ? 3:4)) > 48)
			{
				DO_LOG_ERROR(log_handle,"generateMaid(). the Length of MD name + MA name + length fields () is > 48. MAID is invalid",(mdNameLength + 2 + ((mdNameType == md_none) ? 3:4)));
			}
			//ASSERT((mdNameLength + 2 + ((mdNameType == md_none) ? 3:4)) <= 48,ERR_GENERATING_MAID);
			// 2nd byte in the MAID is the name length
			maid[idx] = 2; // The int16 is 2
			idx++;
			memcpy(&maid[idx],&maName[0],2);
			idx += 2;
			break;
		}
		case ma_rfc2685VpnId:
		{
			if ((mdNameLength + 7 + ((mdNameType == md_none) ? 3:4)) > 48)
			{
				DO_LOG_ERROR(log_handle,"generateMaid(). the Length of MD name + MA name + length fields () is > 48. MAID is invalid",(mdNameLength + 7 + ((mdNameType == md_none) ? 3:4)));
			}
			//ASSERT((mdNameLength + 7 + ((mdNameType == md_none) ? 3:4)) <= 48,ERR_GENERATING_MAID);

			// 2nd byte in the MAID is the name length
			maid[idx] = 7; // The VPN ID is 7 bytes
			idx++;
			memcpy(&maid[idx],&maName[0],7);
			idx += 7;
			break;
		}
		case ma_ituY1731_32:
		case ma_characterString:
		{
			// 2nd byte in the MAID is the name length
			if (maNameType == ma_ituY1731_32)
			{
				maNameLength = 13;					// if ma_ituY1731_32 then the name is always 13 characters in length. only 7 may be valid but will be null padded to 13.
			}
			else
			{
				maNameLength  = strlen(maName);
			}
			if ((mdNameLength + maNameLength + ((mdNameType == md_none) ? 3:4)) > 48)
			{
				DO_LOG_ERROR(log_handle,"generateMaid(). the Length of MD name + MA name + length fields () is > 48. MAID is invalid",(mdNameLength + maNameLength + ((mdNameType == md_none) ? 3:4)));
			}
			//ASSERT((mdNameLength + maNameLength + ((mdNameType == md_none) ? 3:4)) <= 48,ERR_GENERATING_MAID);

			maid[idx] = maNameLength;
			idx++;
			memcpy(&maid[idx],&maName[0],maNameLength);
			idx += maNameLength;
			break;
		}
	}
}

/*
 * @brief	Create a new Maintenance Association (MA)
 *
 */
int maCreate(mdIndex_t mdIndex, maIndex_t maIndex, char* maName, maNameType_t type, uint16_t *vLanList, tagType_t tagType, bool y1731Compliant)
{
	maEntry_t		*maEntryPtr;
	mdMibInfo_t 	mdMibInfo;
	uint16_t		k;

	if(!mdIsMdMibIdxValid(mdIndex))
	{
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid MD Index (%lu)",mdIndex);
		return (1);
	}
	// Read the MD info as we will need it - MDMibId has already been tested to be valid
	mdRead(mdIndex, &mdMibInfo, NULL);

	// Validate the maIndex value provided
	if ((maIndex == 0) || (mibIndexToMaNumNoLocking(mdIndex, maIndex) != -1)) // || (maIndex > mdMibInfo.maNextMibIdx))
	{
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid MA Index (%lu.%lu)",mdIndex, maIndex);
		return (-1);
	}
	// Test the MA name format
	if (isMANFLInvalid(maName,type))
	{
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid Maintenance Association Name");
		return(2);
	}
	LOCK_MA_LIST(0);
	// Check that the MA Name is unique
	if (isMANameInUse(mdIndex, maName, type))
	{
		UNLOCK_MA_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected due to duplicate Maintenance Association Name");
		return(3);
	}
#ifdef DEBUG_PRINT_L1_1
	ts_printf("MA:no duplicate  ma name\n\r");
#endif
	// Check the VLAN - we don't care about the vLanList if tagType is VLAN_NONE
	if ((!isVlanValid(vLanList) && (tagType != VLAN_NONE)) || ((vLanList == NULL) && (tagType != VLAN_NONE)))
	{
		UNLOCK_MA_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid or No VLANs");
		ts_printf("MA: Operation rejected due to invalid or No VLANs\n\r");
		return(5);
	}
	if ((tagType < VLAN_NONE) || (tagType > STAG_TYPE))
	{
		UNLOCK_MA_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid Tag Type (%d)",tagType);
		return(-2);
	}
	if (y1731Compliant)
	{
		// If this is to be a y1731 compliant MEG then verify that
		// the MD name type of the parent MD is md_none and MA name type is ma_ituY1731_32

		if (mdMibInfo.type != md_none)
		{
			UNLOCK_MA_LIST();
			DO_LOG_INFO(log_handle,"Operation rejected due to incorrect MD Name Type (%d). For y1731 it must be md_none (1)",mdMibInfo.type);
			return(-3);
		}
		if (type != ma_ituY1731_32)
		{
			UNLOCK_MA_LIST();
			DO_LOG_INFO(log_handle,"Operation rejected due to incorrect MA Name Type (%d). For y1731 it must be ma_ituY1731_32 (32)",type);
			return(-4);
		}
	}
	maEntryPtr = getNextFreeMaEntry();
	if (maEntryPtr != NULL)
	{
		memcpy(maEntryPtr->maNetInfo.name,maName,sizeof(maName_t));
		maEntryPtr->maNetInfo.type = type;

		// Set the mibIdx to the one provided
		maEntryPtr->maCtxt.mibIdx = maIndex;

		// Save the tag type used on the MA
		maEntryPtr->maCompInfo.tagType = tagType;

		// Add the vlan(s) to the MAs vLan list if they are provided and the tagType is NOT 'none'
		if ((vLanList != NULL) && (tagType != VLAN_NONE))
		{
			k = 0;
			while (vLanList[k] != 0)
			{
				maEntryPtr->vLanList[k] = vLanList[k];
				maEntryPtr->maCompInfo.numVids++;
				k++;
				// Stop when we reach the limit of the list
				if (k == MAX_NUM_VLANS_PER_MA)
				{
					break;
				}
			}
			maEntryPtr->vLanList[k] = 0;		// terminate the list
			maEntryPtr->maCompInfo.primarySelectorIdOrNone = maEntryPtr->vLanList[0];
		}
		else
		{
			maEntryPtr->vLanList[0] = 0;		// Empty vLan List
			maEntryPtr->maCompInfo.primarySelectorIdOrNone = 0;
		}

		// initialise the Local MEP list Head
		TAILQ_INIT(&maEntryPtr->llMepHead);

		// To facilitate re-verse lookup save the parent MDs HW index
		maEntryPtr->maCtxt.parentMdNum = _mdMibIndexToMdNum(mdIndex);

		if (y1731Compliant)
		{
			// Set the MEF parameters
			maEntryPtr->maMEFInfo.maMEFNetInfo.y1731Compliant				= true;
			maEntryPtr->maMEFInfo.maMEFNetInfo.megIdFormat					= type;
			maEntryPtr->maMEFInfo.maMEFNetInfo.megLevel 					= mdMibInfo.level;
			maEntryPtr->maMEFInfo.maMEFCompInfo.portStatusTlvIncluded		= true;
			maEntryPtr->maMEFInfo.maMEFCompInfo.interfaceStatusTlvIncluded	= true;
			maEntryPtr->maMEFInfo.maMEFCompInfo.connectivityStatusInterval	= 3.5 * ccmIntervalToMillisec[maEntryPtr->maNetInfo.ccmInterval];
		}

		// add the new MA to the list
		addMaToList(mdIndex, maEntryPtr);
	}
	else
	{
		UNLOCK_MA_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected due to insufficient resources");
		ts_printf("MA: insufficient res\n\r");
		return (-5);
	}
	UNLOCK_MA_LIST();
#ifdef DEBUG_PRINT_L2
	ts_printf("MA: Created MA with index = %d.%d",mdIndex, maIndex);
	DO_LOG_NOTICE(log_handle,"Created MA with index = %lu.%lu",mdIndex, maIndex);
#endif
	return (9); // Operation accepted.
}



/*
 * @brief Test if the supplied MA Index is valid
 */
static bool isMaMibIdxValid(mdIndex_t mdIndex, maIndex_t maIndex, maNum_t *maNum)
{
	maNum_t	theMaNum;

	if(!mdIsMdMibIdxValid(mdIndex))
	{
		DO_LOG_DEBUG(log_handle,"The MD MIB Index (%lu) was invalid",mdIndex);
		return false;
	}

	mdMibInfo_t mdMibInfo;
	mdRead(mdIndex, &mdMibInfo,NULL);
	if ((maIndex == 0) || (maIndex >= mdMibInfo.maNextMibIdx))
	{
MA_MIB_IDX_INVALID:
		DO_LOG_DEBUG(log_handle,"The MA MIB Index (%lu.%lu) was invalid",mdIndex, maIndex);
		return false;
	}

	// Test is the maIndex known and get it's associated maNum
	theMaNum = mibIndexToMaNumNoLocking(mdIndex, maIndex);
	if (theMaNum != -1)		// If the MA index is found
	{
		if (maNum != NULL)
		{
			*maNum = theMaNum;
		}
		return(true);
	}
	goto MA_MIB_IDX_INVALID;
}

/*
 * @brief Test if the supplied MA Index is valid
 */
bool maIsMaMibIdxValid(mdIndex_t mdIndex, maIndex_t maIndex)
{
	bool retVal;

	LOCK_MA_LIST(0);
	retVal = isMaMibIdxValid(mdIndex, maIndex, NULL);
	UNLOCK_MA_LIST();
	return(retVal);
}

/*
 * @brief	Delete the MA with the supplied MD and MA MIB index
 */
int maDelete(mdIndex_t mdIndex, maIndex_t maIndex)
{
	maNum_t		maNum;
	mepId_t		mepId;
	mepId_t		mepIdToDelete;

	LOCK_MA_LIST(0);
	// Test is the maIndex known and get it's associated maNum - Also validates MD Index
	if (!isMaMibIdxValid(mdIndex, maIndex, &maNum))
	{
		UNLOCK_MA_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid or nonexistent Maintenance Association (%lu.%lu)",mdIndex, maIndex);
		return (1);	//Operation rejected due to invalid or nonexistent Maintenance Domain
	}

	// find and delete all MEPs associated with this MA
	mepId = lMepReadList(mdIndex, maIndex,0);
	while(mepId != 0)
	{
		mepIdToDelete = mepId;
		// Get the next MEP, BEFORE deleting the previous one!
		mepId = lMepReadList(mdIndex, maIndex, mepId);

		if (lMepDelete(mdIndex, maIndex, mepIdToDelete) != 2)
		{
			UNLOCK_MA_LIST();
			// TODO: see if this can happen and if it can should the error be higher severity?
			DO_LOG_CRITICAL(log_handle,"Failed to delete MEP with ID = %lu.%lu.%d",mdIndex, maIndex, mepIdToDelete);
			return (-1); // Not all child objects were deleted
		}

	}

	removeMaFromList(mdIndex, &maArray[maNum]);
	UNLOCK_MA_LIST();
	DO_LOG_NOTICE(log_handle,"Deleted MA with index = %lu.%lu",mdIndex, maIndex);
	return (2); // Operation accepted
}

/*
 * @brief	Read the information for the MA	selected by mdIndex and maIndex
 */
int maRead(mdIndex_t mdIndex, maIndex_t maIndex, maNetInfo_t *maNetInfo, maCompInfo_t *maCompInfo, maMEFInfo_t *maMEFInfo, uint16_t *vLanList, mepId_t *mepIdList, networkAddress_t *alarmNetAddress)
{
	maNum_t		maNum;
	uint16_t	k;

	LOCK_MA_LIST(0);
	// Test is the maIndex known and get it's associated maNum - Also validates MD Index
	if (!isMaMibIdxValid(mdIndex, maIndex, &maNum))
	{
		UNLOCK_MA_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid or nonexistent Maintenance Association (%lu.%lu)",mdIndex, maIndex);
		return (1);	//Operation rejected due to invalid or nonexistent Maintenance Domain
	}

	if (maNetInfo != NULL)
	{
		// If a pointer is provided then copy the value
		memcpy(maNetInfo, &maArray[maNum].maNetInfo,sizeof(maNetInfo_t));
	}
	if (maCompInfo != NULL)
	{
		// If a pointer is provided then copy the value
		memcpy(maCompInfo, &maArray[maNum].maCompInfo,sizeof(maCompInfo_t));
		// The vlan list is only copied if maCompInfo is copied since the numVids is in that data struct
		if (vLanList != NULL)
		{
			// If a pointer is provided then copy the list
			k = 0;
			while (maArray[maNum].vLanList[k] != 0)		// Last value in list is 0
			{
				vLanList[k] = maArray[maNum].vLanList[k];
				k++;
			}
			vLanList[k] = 0;							// terminate the list
		}
	}

	if (maMEFInfo != NULL)
	{
		// If a pointer is provided then copy the value
		memcpy(maMEFInfo, &maArray[maNum].maMEFInfo,sizeof(maMEFInfo_t));
	}

	if (mepIdList != NULL)
	{
		// If a pointer is provided then copy the list
		k = 0;
		while (maArray[maNum].mepIdList[k] != 0)		// Last value in list is 0
		{
			mepIdList[k] = maArray[maNum].mepIdList[k];
			k++;
		}
		mepIdList[k] = 0;								// terminate the list
	}

	if (alarmNetAddress != NULL)
	{
		*alarmNetAddress = maArray[maNum].alarmNetAddress;
	}
	UNLOCK_MA_LIST();
	DO_LOG_DEBUG(log_handle,"Read info for Maintenance Association (%lu.%lu)",mdIndex, maIndex);
	return (2); // Operation accepted
}

/*
 * @brief	Write a new value to the selected MA parameter
 */
int maWrite(mdIndex_t mdIndex, maIndex_t maIndex, maWrParam_t maParam, void *paramValue)
{
	maNum_t		maNum;
	uint16_t	k;
	mepId_t 	nextMepId;
	mepInfo_t 	mepInfo;
	psValue_t	psValue;
	isValue_t	isValue;
	char		buf[20];
	bool		newState;

	LOCK_MA_LIST(0);
	// Test is the maIndex known and get it's associated maNum - Also validates MD Index
	if (!isMaMibIdxValid(mdIndex, maIndex, &maNum))
	{
		UNLOCK_MA_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid or nonexistent Maintenance Association (%lu.%lu)",mdIndex, maIndex);
		return (1);	//Operation rejected due to invalid or nonexistent Maintenance Domain
	}

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


	switch(maParam)
	{
		case maParam_maMhfCreation:
		{
			maMhfCreation_t		*mhfCreationPtr = (maMhfCreation_t*)paramValue;
			if ((*mhfCreationPtr < ma_defMHFnone) || (*mhfCreationPtr > ma_defMHFdefer))
			{
				UNLOCK_MA_LIST();
				DO_LOG_INFO(log_handle,"Operation rejected due to invalid value (%d) for mhfCreation",*mhfCreationPtr);
				return (3);
			}

			if (maArray[maNum].maCompInfo.mhfCreation != *mhfCreationPtr)
			{
				// Write the new value into the MA
				maArray[maNum].maCompInfo.mhfCreation = *mhfCreationPtr;
				DO_LOG_NOTICE(log_handle,"On MA with index %lu.%lu the mhfCreation value was changed to %d",mdIndex, maIndex,*mhfCreationPtr);
			}
			break;
		}
		case maParam_maMhfIdPermission:
		{
			maMhfIdPermission_t		*mhfIdPermissionPtr = (maMhfIdPermission_t*)paramValue;
			if ((*mhfIdPermissionPtr < ma_sendIdNone) || (*mhfIdPermissionPtr > ma_sendIdDefer))
			{
				UNLOCK_MA_LIST();
				DO_LOG_INFO(log_handle,"Operation rejected due to invalid value (%d) for mhfIdPermission",*mhfIdPermissionPtr);
				return (3);
			}
			if (maArray[maNum].maCompInfo.mhfIdPermission != *mhfIdPermissionPtr)
			{
				// Write the new value into the MA
				maArray[maNum].maCompInfo.mhfIdPermission = *mhfIdPermissionPtr;
				DO_LOG_NOTICE(log_handle,"On MA with index %lu.%lu the mhfIdPermission value was changed to %d",mdIndex, maIndex,*mhfIdPermissionPtr);
			}
			break;
		}
		case maParam_maCcmInterval:
		{
			ccmInterval_t *ccmIntervalPtr = (ccmInterval_t*)paramValue;
			if ((*ccmIntervalPtr < ccmInterval300Hz) || (*ccmIntervalPtr > ccmInterval1s))
			{
				UNLOCK_MA_LIST();
				DO_LOG_INFO(log_handle,"Operation rejected due to invalid value (%d) for ccmInterval",*ccmIntervalPtr);
				return (3);
			}
			// test to see if the value actually changed
			if (maArray[maNum].maNetInfo.ccmInterval != *ccmIntervalPtr)
			{
				maArray[maNum].maNetInfo.ccmInterval = *ccmIntervalPtr;
				maArray[maNum].maMEFInfo.maMEFCompInfo.connectivityStatusInterval = 3.5 * ccmIntervalToMillisec[*ccmIntervalPtr];

				if (!TAILQ_EMPTY(&maArray[maNum].llMepHead))
				{
					// If Local MEPs exist then the interval value in the CCM template,
					// the rxLmepNum table, the TxLMepNum table and all S/M timers must be updated
					nextMepId  = lMepReadList(mdIndex, maIndex, 0);
					while (nextMepId != 0)
					{
						lMepRead(mdIndex, maIndex,nextMepId,&mepInfo,NULL,NULL,NULL,NULL,NULL,NULL);
						if (mepInfo.active)
						{
							// If the MEP was enabled - then disable it to allow the updates
							newState = false;
							lMepWrite(mdIndex, maIndex,nextMepId,mepParam_mepActive,&newState);
						}

						_lMepUpdateCcmInterval(mdIndex, maIndex,nextMepId, *ccmIntervalPtr);

						if (mepInfo.active)
						{
							// If the MEP was enabled initially - then re-enable it after the updates
							newState = true;
							lMepWrite(mdIndex, maIndex,nextMepId,mepParam_mepActive,&newState);
						}
						nextMepId = lMepReadList(mdIndex, maIndex, nextMepId);
					}
				}
				DO_LOG_NOTICE(log_handle,"On MA with index %lu.%lu the ccmInterval was changed to %s",mdIndex, maIndex,maIntervalStr[*ccmIntervalPtr]);
			}
			break;
		}
		case maParam_maFaultAlarmNetAddress:
		{
			networkAddress_t *networkAddress = (networkAddress_t*)paramValue;
			// TODO: There is no easy way to validate the network address so don't for now
			if (maArray[maNum].alarmNetAddress != *networkAddress)
			{
				maArray[maNum].alarmNetAddress = *networkAddress;
				DO_LOG_NOTICE(log_handle,"On MA with index %lu.%lu the alarmNetAddress was changed to %s",mdIndex, maIndex,printfIPAddr(*networkAddress,buf));
			}
			break;
		}
		case maParam_maMepIdListAdd:
		case maParam_maMepIdListRemove:
		{
			mepId_t *mepIdPtr = (mepId_t*)paramValue;
			bool	found = false;
			mepId_t	lMepId;

			if (!mepIdIsValid(*mepIdPtr))
			{
				goto MEP_ID_PARM_INVALID;
			}

			// check if the MEP to be removed is a local MEP
			if ((maParam == maParam_maMepIdListRemove) && (mepIsLMepIdValid(mdIndex, maIndex, *mepIdPtr)))
			{
				UNLOCK_MA_LIST();
				DO_LOG_INFO(log_handle,"Operation rejected because MEPID is ID of a local MEP (%d)",*mepIdPtr);
				return (-1);
			}

			k = 0;
			// scan across the list... (the list limit is MAX_NUM_MEPS+1 entries)
			// This While loop will find and remove any MEPIDs in the list
			while (maArray[maNum].mepIdList[k] != 0)		// Last value in list is 0 and mepIdList[MAX_RMEPS_PER_LM+1] is always 0
			{
				if (maArray[maNum].mepIdList[k] == *mepIdPtr)
				{
					found = true;
					// We found a matching mepID...
					if (maParam == maParam_maMepIdListRemove)
					{
						// if removing a mepID...

						// check are there any local MEPs and if so remove the remote MEP from them
						lMepId = lMepReadList(mdIndex, maIndex,0);
						while(lMepId != 0)
						{
							_lMepRemoveRMepDB(mdIndex, maIndex, lMepId,*mepIdPtr);
							lMepId = lMepReadList(mdIndex, maIndex,lMepId);
						}
						DO_LOG_NOTICE(log_handle,"Removed MEPID %d from the MA with index %lu.%lu",*mepIdPtr,mdIndex, maIndex);

						while (maArray[maNum].mepIdList[k] != 0)
						{
							// Erase the mepID by moving all remaining entries down one place in the list
							maArray[maNum].mepIdList[k] = maArray[maNum].mepIdList[k+1];

							// Keep scanning until the end of the list (value = 0) or the list limit is reached
							k++;
							// Check are we at the limit of the list (i.e. list is full!)
							if (k == MAX_MEPIDS_PER_MA+1)
							{
								break; // from while loop
							}
						}
						UNLOCK_MA_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, _mepIdToMepNum(mdIndex, maIndex, lMepId));
						return (4); // Operation accepted
					}
					else
					{
						// if adding a mepID...
						// The mepID is already in the list - stop scanning it'll get 're-added' at the same location
						break;
					}
				}

				// Keep scanning until the end of the list (value = 0) or the list limit is reached
				k++;
				// Check are we at the limit of the list (i.e. list is full!)
				if (k == MAX_MEPIDS_PER_MA+1)
				{
					break; // from while loop
				}
			}
			// This IF will
			if (maParam == maParam_maMepIdListAdd)
			{
				if (k < MAX_MEPIDS_PER_MA+1)
				{
					// It's possible that the MEPID was already on the list
					if (maArray[maNum].mepIdList[k] != *mepIdPtr)
					{
						// If the MEPID was not on the list and the list isn't full add the new mepID at location k
						maArray[maNum].mepIdList[k] = *mepIdPtr;
						// check are there local MEPs and if so append the remote MEP to them
						lMepId = lMepReadList(mdIndex, maIndex,0);
						while(lMepId != 0)
						{
							_lMepAppendRMepDB(mdIndex, maIndex, lMepId,*mepIdPtr);
							lMepId = lMepReadList(mdIndex, maIndex,lMepId);
						}
						DO_LOG_NOTICE(log_handle,"Added MEPID %d to the MA with index %lu.%lu",*mepIdPtr,mdIndex, maIndex);

						UNLOCK_MA_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, _mepIdToMepNum(mdIndex, maIndex, lMepId));
						return (4); // Operation accepted
					}
					else
					{
						UNLOCK_MA_LIST();
						DO_LOG_INFO(log_handle,"Operation rejected due to mepId (%d) already on the list",*mepIdPtr);
						return (3);
					}
				}
				else
				{
					goto MEP_ID_PARM_INVALID;		// List is full!
				}
			}
			else
			{
				if (!found)
				{
MEP_ID_PARM_INVALID:
					UNLOCK_MA_LIST();
					DO_LOG_INFO(log_handle,"Operation rejected due to invalid value (%d) for mepId or List Full",*mepIdPtr);
					return (3);
				}
			}
			break;
		}
		case maParam_maMEFPortStatusTlvEnable:
		{
			bool *psTlvEnPtr = (bool*)paramValue;

			// See if the value is changing
			if (maArray[maNum].maMEFInfo.maMEFCompInfo.portStatusTlvIncluded != ((bool)*psTlvEnPtr))
			{
				if (*psTlvEnPtr == false)
				{
					maArray[maNum].maMEFInfo.maMEFCompInfo.portStatusTlvIncluded = false;
				}
				else
				{
					maArray[maNum].maMEFInfo.maMEFCompInfo.portStatusTlvIncluded = true;
				}
				// If there are any local Mep(s) defined on this MA then update its TLVs
				if (!TAILQ_EMPTY(&maArray[maNum].llMepHead))
				{
					nextMepId  = lMepReadList(mdIndex, maIndex, 0);
					while (nextMepId != 0)
					{
						lMepRead(mdIndex, maIndex,nextMepId,&mepInfo,NULL,NULL,NULL,NULL,NULL,NULL);
						if (*psTlvEnPtr == false)
						{
							psValue = psNoPortStatusTLV;		// This will remove the TLV
							ccmAddUpdateTlv(_mepIdToMepNum(mdIndex, maIndex,nextMepId),portStatusTlvType,1,&psValue);
						}
						else
						{
							psValue = sysGetPortStatus(mepInfo.iFace);
							ccmAddUpdateTlv(_mepIdToMepNum(mdIndex, maIndex,nextMepId),portStatusTlvType,1,&psValue);
						}
						nextMepId = lMepReadList(mdIndex, maIndex, nextMepId);
					}
				}
				DO_LOG_NOTICE(log_handle,"On the MA with index %lu.%lu portStatusTlvIncluded was set to %s",mdIndex, maIndex, maArray[maNum].maMEFInfo.maMEFCompInfo.portStatusTlvIncluded ? "True":"False");
			}
			break;
		}
		case maParam_maMEFInterfaceStatusTlvEnable:
		{
			bool *isTlvEnPtr = (bool*)paramValue;
			// See if the value is changing
			if (maArray[maNum].maMEFInfo.maMEFCompInfo.interfaceStatusTlvIncluded != ((bool)*isTlvEnPtr))
			{
				if (*isTlvEnPtr == false)
				{
					maArray[maNum].maMEFInfo.maMEFCompInfo.interfaceStatusTlvIncluded = false;
				}
				else
				{
					maArray[maNum].maMEFInfo.maMEFCompInfo.interfaceStatusTlvIncluded = true;
				}
				// If there is a local Mep(s) defined on this MA then update its TLVs
				if (!TAILQ_EMPTY(&maArray[maNum].llMepHead))
				{
					nextMepId  = lMepReadList(mdIndex, maIndex, 0);
					while (nextMepId != 0)
					{
						lMepRead(mdIndex, maIndex,nextMepId,&mepInfo,NULL,NULL,NULL,NULL,NULL,NULL);
						if (*isTlvEnPtr == false)
						{
							isValue = isNoInterfaceStatusTLV;
							ccmAddUpdateTlv(_mepIdToMepNum(mdIndex, maIndex,nextMepId),interfaceStatusTlvType,1,&isValue);
						}
						else
						{
							isValue = sysGetInterfaceStatus(mepInfo.iFace);
							ccmAddUpdateTlv(_mepIdToMepNum(mdIndex, maIndex,nextMepId),interfaceStatusTlvType,1,&isValue);
						}
						nextMepId = lMepReadList(mdIndex, maIndex, nextMepId);
					}
				}
				DO_LOG_NOTICE(log_handle,"On the MA with index %lu.%lu interfaceStatusTlvIncluded was set to %s",mdIndex, maIndex, maArray[maNum].maMEFInfo.maMEFCompInfo.interfaceStatusTlvIncluded ? "True":"False");
			}
			break;
		}
		default:
		{
			UNLOCK_MA_LIST();
			DO_LOG_INFO(log_handle,"Operation rejected due to the selected managed object being Read-Only");
			return (2);
			break;
		}
	} // switch
	UNLOCK_MA_LIST();
	return (4); // Operation accepted
}

/*
 * @brief Return a pointer to the Head of the list of local MEPs associated with this MA
 */
struct llMepHead_s* _getMepListHead(mdIndex_t mdIndex, maIndex_t maIndex)
{
	maNum_t	maNum;

	struct llMepHead_s *mepListHeadPtr;

	LOCK_MA_LIST(0);
	// Test is the mdIndex & maIndex known and get the associated maNum
	if (!isMaMibIdxValid(mdIndex, maIndex, &maNum))
	{
		UNLOCK_MA_LIST();
		DO_LOG_DEBUG(log_handle,"Invalid or nonexistent MD or MA Index (%lu.%lu)",mdIndex,maIndex);
		return (NULL);	//Operation rejected due to invalid or nonexistent Maintenance Domain
	}

	mepListHeadPtr = &maArray[maNum].llMepHead;
	UNLOCK_MA_LIST();
	return(mepListHeadPtr);
}

/*
 * @brief Check if the MEPID provided is on the MEP ID list for the selected MA
 */
bool _maIsMepIdOnList(mdIndex_t mdIndex, maIndex_t maIndex, mepId_t mepId)
{
	maNum_t		maNum;
	uint16_t	k;

	if (mepId == 0)
		return false;
	//ASSERT(mepId != 0,ERR_INVALID_MEPID);

	LOCK_MA_LIST(0);
	// Test is the mdIndex & maIndex known and get the associated maNum
	if (!isMaMibIdxValid(mdIndex, maIndex, &maNum))
	{
		UNLOCK_MA_LIST();
		DO_LOG_DEBUG(log_handle,"Invalid or nonexistent MD or MA Index (%lu.%lu)",mdIndex,maIndex);
		return false;	//Operation rejected due to invalid or nonexistent Maintenance Domain
	}

	k = 0;
	// scan across the list... (the list limit is MAX_NUM_MEPS+1 entries)
	while (maArray[maNum].mepIdList[k] != 0)		// Last value in list is 0 and mepIdList[MAX_NUM_MEPS] is always 0
	{
		if (maArray[maNum].mepIdList[k] == mepId)
		{
			UNLOCK_MA_LIST();
			return true;
		}
		// Keep scanning until the end of the list (value = 0) or the list limit is reached
		k++;
		// Check are we at the limit of the list (i.e. list is full!)
		if (k == MAX_MEPIDS_PER_MA+1)
		{
			break; // from while loop
		}
	}
	UNLOCK_MA_LIST();
	return false;
}

/*
 * @brief Check if the vLan provided is on the vLan list for the selected MA
 */
bool _maIsVLanOnList(mdIndex_t mdIndex, maIndex_t maIndex, uint16_t vLan)
{
	maNum_t		maNum;
	uint16_t	k;

	if (vLan >= 4095)
		return false;
	//ASSERT(((vLan >= 0) && (vLan < 4095)),ERR_INVALID_VLAN);

	LOCK_MA_LIST(0);
	// Test is the mdIndex & maIndex known and get the associated maNum
	if (!isMaMibIdxValid(mdIndex, maIndex, &maNum))
	{
		UNLOCK_MA_LIST();
		DO_LOG_DEBUG(log_handle,"Invalid or nonexistent MD or MA Index (%lu.%lu)",mdIndex,maIndex);
		return false;	//Operation rejected due to invalid or nonexistent Maintenance Domain
	}

	k = 0;
	// scan across the list... (the list limit is MAX_NUM_MEPS+1 entries)
	while (maArray[maNum].vLanList[k] != 0)		// Last value in list is 0 and vLanList[MAX_NUM_MEPS] is always 0
	{
		if (maArray[maNum].vLanList[k] == vLan)
		{
			UNLOCK_MA_LIST();
			return true;
		}
		// Keep scanning until the end of the list (value = 0) or the list limit is reached
		k++;
		// Check are we at the limit of the list (i.e. list is full!)
		if (k == MAX_NUM_VLANS_PER_MA)
		{
			break; // from while loop
		}
	}
	UNLOCK_MA_LIST();
	return false;
}

/*
 * @brief Return the Primary vLan information for the selected MA
 */
bool _maGetPrimaryVlanInfo(mdIndex_t mdIndex, maIndex_t maIndex, serviceSelector_t *primarySelectorType, serviceSelectorId_t *primarySelectorIdOrNone)
{
	maNum_t		maNum;

	LOCK_MA_LIST(0);
	// Test is the mdIndex & maIndex known and get the associated maNum
	if (!isMaMibIdxValid(mdIndex, maIndex, &maNum))
	{
		UNLOCK_MA_LIST();
		DO_LOG_DEBUG(log_handle,"Invalid or nonexistent MD or MA Index (%lu.%lu)",mdIndex,maIndex);
		return false;	//Operation rejected due to invalid or nonexistent Maintenance Domain
	}

	*primarySelectorIdOrNone = maArray[maNum].maCompInfo.primarySelectorIdOrNone;
	*primarySelectorType = maArray[maNum].maCompInfo.primarySelectorType;
	UNLOCK_MA_LIST();
	return (true);
}

/*
 * @brief Return the tag Type assiciated with the selected MA
 */
tagType_t _maGetTagType(mdIndex_t mdIndex, maIndex_t maIndex)
{
	maNum_t		maNum;
	tagType_t	theTagType;

	LOCK_MA_LIST(0);
	// Test is the mdIndex & maIndex known and get the associated maNum
	if (!isMaMibIdxValid(mdIndex, maIndex, &maNum))
	{
		UNLOCK_MA_LIST();
		DO_LOG_DEBUG(log_handle,"Invalid or nonexistent MD or MA Index (%lu.%lu)",mdIndex,maIndex);
		return VLAN_NONE;	//Operation rejected due to invalid or nonexistent Maintenance Domain
	}

	theTagType = maArray[maNum].maCompInfo.tagType;
	UNLOCK_MA_LIST();
	return (theTagType);
}

/*
 * @brief Add/update/remove a TLV on the CCM packet for all MEPs it the MA - e.g. Port/Interface Status
 */
void _maUpdateCCMTLVs(mdIndex_t mdIndex, maIndex_t maIndex, iFace_t iFace, tlvType_t type,uint8_t length,void *value)
{
	mepId_t	lMepId;

	// Adjust the TLV for all MEPs associated with this MA
	lMepId = lMepReadList(mdIndex, maIndex,0);
	while(lMepId != 0)
	{
		_lMepUpdateCCMTLV(mdIndex, maIndex, lMepId, iFace, type, length, value);
		lMepId = lMepReadList(mdIndex, maIndex,lMepId);
	}
}

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