Refs #618. Added support for splitting the XCP seed/key over multiple packets to make larger seed/key values possible, specifically on CAN.

git-svn-id: https://svn.code.sf.net/p/openblt/code/trunk@630 5dc33758-31d5-4daf-9ae8-b24bf3d40d73
This commit is contained in:
Frank Voorburg 2018-10-19 20:22:27 +00:00
parent 6345a5433d
commit 5ababf911d
5 changed files with 283 additions and 120 deletions

View File

@ -81,7 +81,7 @@ static uint16_t XcpLoaderGetOrderedWord(uint8_t const * data);
static bool XcpLoaderSendCmdConnect(void); static bool XcpLoaderSendCmdConnect(void);
static bool XcpLoaderSendCmdGetStatus(uint8_t * session, uint8_t * protectedResources, static bool XcpLoaderSendCmdGetStatus(uint8_t * session, uint8_t * protectedResources,
uint16_t * configId); uint16_t * configId);
static bool XcpLoaderSendCmdGetSeed(uint8_t resource, uint8_t * seed, uint8_t * seedLen); static bool XcpLoaderSendCmdGetSeed(uint8_t resource, uint8_t mode, uint8_t * seed, uint8_t * seedLen);
static bool XcpLoaderSendCmdUnlock(uint8_t const * key, uint8_t keyLen, static bool XcpLoaderSendCmdUnlock(uint8_t const * key, uint8_t keyLen,
uint8_t * protectedResources); uint8_t * protectedResources);
static bool XcpLoaderSendCmdSetMta(uint32_t address); static bool XcpLoaderSendCmdSetMta(uint32_t address);
@ -316,10 +316,16 @@ static bool XcpLoaderStart(void)
if ( (protectedResources & XCPPROTECT_RESOURCE_PGM) != 0) if ( (protectedResources & XCPPROTECT_RESOURCE_PGM) != 0)
{ {
uint8_t availableResources = 0; uint8_t availableResources = 0;
uint8_t seed[XCPLOADER_PACKET_SIZE_MAX-2] = { 0 }; uint8_t seed[256] = { 0 };
uint8_t seedLen = 0; uint8_t *seedPtr = &seed[0];
uint8_t key[XCPLOADER_PACKET_SIZE_MAX-2] = { 0 }; uint8_t seedTotalLen = 0;
uint8_t keyLen = 0; uint8_t seedRemainingLen = 0;
uint8_t key[256] = { 0 };
uint8_t keyTotalLen = 0;
uint8_t keyRemainingLen = 0;
uint8_t keyCurrentLen = 0;
uint8_t *keyPtr = &key[0];
/* Make sure the XCP protection module contains an unlock algorithm for the /* Make sure the XCP protection module contains an unlock algorithm for the
* programming resource. * programming resource.
*/ */
@ -340,22 +346,41 @@ static bool XcpLoaderStart(void)
result = false; result = false;
} }
} }
/* Request the seed for unlocking the programming resources. */ /* Request (first part of) the seed for unlocking the programming resources. */
if (result) if (result)
{ {
if (!XcpLoaderSendCmdGetSeed(XCPPROTECT_RESOURCE_PGM, seed, &seedLen)) if (!XcpLoaderSendCmdGetSeed(XCPPROTECT_RESOURCE_PGM, 0, seedPtr, &seedRemainingLen))
{ {
result = false; result = false;
} }
else
{
/* store the total seed length */
seedTotalLen = seedRemainingLen;
}
}
/* Check if more parts of the seed need to be requested. */
if (result)
{
while (seedRemainingLen > (xcpMaxDto - 2))
{
/* update the seed pointer for the next part */
seedPtr += (xcpMaxDto - 2);
if (!XcpLoaderSendCmdGetSeed(XCPPROTECT_RESOURCE_PGM, 1, seedPtr, &seedRemainingLen))
{
result = false;
break;
}
}
} }
/* Only continue with resource unlock operation if not already unlocked, which /* Only continue with resource unlock operation if not already unlocked, which
* is indicated by a seed length of 0. * is indicated by a seed length of 0.
*/ */
if ( (result) && (seedLen > 0) ) if ( (result) && (seedTotalLen > 0) )
{ {
/* Compute the key using the XCP protection module. */ /* Compute the key using the XCP protection module. */
if (!XCPProtectComputeKeyFromSeed(XCPPROTECT_RESOURCE_PGM, seedLen, seed, if (!XCPProtectComputeKeyFromSeed(XCPPROTECT_RESOURCE_PGM, seedTotalLen, seed,
&keyLen, key)) &keyTotalLen, key))
{ {
result = false; result = false;
} }
@ -363,16 +388,38 @@ static bool XcpLoaderStart(void)
if (result) if (result)
{ {
uint8_t currentlyProtectedResources = 0; uint8_t currentlyProtectedResources = 0;
/* Send the key to unlock the resource. */
if (!XcpLoaderSendCmdUnlock(key, keyLen, &currentlyProtectedResources)) /* Initialize remaining length */
keyRemainingLen = keyTotalLen;
/* Send the key to unlock the resource */
while (keyRemainingLen > 0)
{ {
result = false; /* Determine how many key bytes are about to be sent. */
} keyCurrentLen = keyRemainingLen;
/* Double-check that the programming resource is now unlocked. */ if (keyCurrentLen > (xcpMaxCto - 2))
else if ((currentlyProtectedResources & XCPPROTECT_RESOURCE_PGM) != 0) {
{ keyCurrentLen = (xcpMaxCto - 2);
/* Programming resource unlock operation failed. */ }
result = false; /* The the (possible partial) unlock command. */
if (!XcpLoaderSendCmdUnlock(keyPtr, keyRemainingLen, &currentlyProtectedResources))
{
result = false;
break;
}
/* Update key pointer and the remaining length */
keyRemainingLen -= keyCurrentLen;
keyPtr += keyCurrentLen;
/* Check if the key was now completely sent. */
if (keyRemainingLen == 0)
{
/* Double-check that the programming resource is now unlocked. */
if ((currentlyProtectedResources & XCPPROTECT_RESOURCE_PGM) != 0)
{
/* Programming resource unlock operation failed. */
result = false;
}
}
} }
} }
} }
@ -825,14 +872,17 @@ static bool XcpLoaderSendCmdGetStatus(uint8_t * session, uint8_t * protectedReso
/************************************************************************************//** /************************************************************************************//**
** \brief Sends the XCP Get Seed command. ** \brief Sends the XCP Get Seed command.
** \param resource The resource to unlock (XCPPROTECT_RESOURCE_xxx). ** \param resource The resource to unlock (XCPPROTECT_RESOURCE_xxx).
** \param mode 0 for the first part of the seed, 1 for the remaining part.
** \param seed Pointer to byte array where the received seed is stored. ** \param seed Pointer to byte array where the received seed is stored.
** \param seedLen Length of the seed in bytes. ** \param seedLen Length of the seed in bytes.
** \return True if successful, false otherwise. ** \return True if successful, false otherwise.
** **
****************************************************************************************/ ****************************************************************************************/
static bool XcpLoaderSendCmdGetSeed(uint8_t resource, uint8_t * seed, uint8_t * seedLen) static bool XcpLoaderSendCmdGetSeed(uint8_t resource, uint8_t mode, uint8_t * seed,
uint8_t * seedLen)
{ {
bool result = false; bool result = false;
uint8_t currentSeedLen;
tXcpTransportPacket cmdPacket; tXcpTransportPacket cmdPacket;
tXcpTransportPacket resPacket; tXcpTransportPacket resPacket;
@ -857,12 +907,7 @@ static bool XcpLoaderSendCmdGetSeed(uint8_t resource, uint8_t * seed, uint8_t *
result = true; result = true;
/* Prepare the command packet. */ /* Prepare the command packet. */
cmdPacket.data[0] = XCPLOADER_CMD_GET_SEED; cmdPacket.data[0] = XCPLOADER_CMD_GET_SEED;
/* Always use mode 0 because only seeds up to 48-bit are supported currently. cmdPacket.data[1] = mode;
* This fits in 6-bytes, making it work with the all currently supported transport
* layers. CAN is the limiting one, because the max packet length is 8-bytes for
* this transport layer.
*/
cmdPacket.data[1] = 0;
cmdPacket.data[2] = resource; cmdPacket.data[2] = resource;
cmdPacket.len = 3; cmdPacket.len = 3;
/* Send the packet. */ /* Send the packet. */
@ -886,20 +931,18 @@ static bool XcpLoaderSendCmdGetSeed(uint8_t resource, uint8_t * seed, uint8_t *
/* Extract and store the seed. */ /* Extract and store the seed. */
if (result) if (result)
{ {
/* Make sure the seed length is valid. */ /* Store the seed length. */
if (resPacket.data[1] > (xcpMaxCto - 2)) *seedLen = resPacket.data[1];
/* Determine the number of seed bytes in the current response */
currentSeedLen = *seedLen;
if (currentSeedLen > (xcpMaxCto - 2))
{ {
result = false; currentSeedLen = (xcpMaxCto - 2);
} }
else /* Store the seed bytes. */
for (uint8_t idx = 0; idx < currentSeedLen; idx++)
{ {
/* Store the seed length. */ seed[idx] = resPacket.data[idx + 2];
*seedLen = resPacket.data[1];
/* Store the seed. */
for (uint8_t idx = 0; idx < *seedLen; idx++)
{
seed[idx] = resPacket.data[idx + 2];
}
} }
} }
} }
@ -921,6 +964,7 @@ static bool XcpLoaderSendCmdUnlock(uint8_t const * key, uint8_t keyLen,
uint8_t * protectedResources) uint8_t * protectedResources)
{ {
bool result = false; bool result = false;
uint8_t keyCurrentLen;
tXcpTransportPacket cmdPacket; tXcpTransportPacket cmdPacket;
tXcpTransportPacket resPacket; tXcpTransportPacket resPacket;
@ -928,23 +972,29 @@ static bool XcpLoaderSendCmdUnlock(uint8_t const * key, uint8_t keyLen,
assert(xcpSettings.transport != NULL); assert(xcpSettings.transport != NULL);
assert(key != NULL); assert(key != NULL);
assert(keyLen > 0); assert(keyLen > 0);
assert(keyLen <= (xcpMaxCto - 2));
assert(protectedResources != NULL); assert(protectedResources != NULL);
/* Only continue with a valid transport layer and parameters. */ /* Only continue with a valid transport layer and parameters. */
if ( (xcpSettings.transport != NULL) && (key != NULL) && (keyLen > 0) && if ( (xcpSettings.transport != NULL) && (key != NULL) && (keyLen > 0) &&
(keyLen <= (xcpMaxCto - 2)) && (protectedResources != NULL) ) /*lint !e774 */ (protectedResources != NULL) ) /*lint !e774 */
{ {
/* Init the result value to okay and only set it to error when a problem occurred. */ /* Init the result value to okay and only set it to error when a problem occurred. */
result = true; result = true;
/* Prepare the command packet. */ /* Prepare the command packet. */
cmdPacket.data[0] = XCPLOADER_CMD_UNLOCK; cmdPacket.data[0] = XCPLOADER_CMD_UNLOCK;
cmdPacket.data[1] = keyLen; cmdPacket.data[1] = keyLen;
for (uint8_t idx = 0; idx < keyLen; idx++) /* Determine number of key bytes for the packet. */
keyCurrentLen = keyLen;
if (keyCurrentLen > (xcpMaxCto - 2))
{
keyCurrentLen = xcpMaxCto - 2;
}
/* Copy key bytes. */
for (uint8_t idx = 0; idx < keyCurrentLen; idx++)
{ {
cmdPacket.data[idx + 2] = key[idx]; cmdPacket.data[idx + 2] = key[idx];
} }
cmdPacket.len = keyLen + 2; cmdPacket.len = keyCurrentLen + 2;
/* Send the packet. */ /* Send the packet. */
if (!xcpSettings.transport->SendPacket(&cmdPacket, &resPacket, if (!xcpSettings.transport->SendPacket(&cmdPacket, &resPacket,
xcpSettings.timeoutT1)) xcpSettings.timeoutT1))

Binary file not shown.

View File

@ -499,6 +499,21 @@
#error "BOOT_XCP_PACKET_RECEIVED_HOOK must be 0 or 1" #error "BOOT_XCP_PACKET_RECEIVED_HOOK must be 0 or 1"
#endif #endif
#ifndef BOOT_XCP_SEED_MAX_LEN
#define BOOT_XCP_SEED_MAX_LEN (64)
#endif
#if (BOOT_XCP_SEED_MAX_LEN <= 0)
#error "BOOT_XCP_SEED_MAX_LEN must be > 0"
#endif
#ifndef BOOT_XCP_KEY_MAX_LEN
#define BOOT_XCP_KEY_MAX_LEN (64)
#endif
#if (BOOT_XCP_KEY_MAX_LEN <= 0)
#error "BOOT_XCP_KEY_MAX_LEN must be > 0"
#endif
#endif /* PLAUSIBILITY_H */ #endif /* PLAUSIBILITY_H */
/*********************************** end of plausibility.h *****************************/ /*********************************** end of plausibility.h *****************************/

View File

@ -915,68 +915,120 @@ static void XcpCmdBuildCheckSum(blt_int8u *data)
static void XcpCmdGetSeed(blt_int8u *data) static void XcpCmdGetSeed(blt_int8u *data)
{ {
blt_int8u resourceOK; blt_int8u resourceOK;
/* made seed buffer static to lower stack load */
/* init resource check variable as if an illegal resource is requested */ static blt_int8u seedBuffer[XCP_SEED_MAX_LEN];
resourceOK = 0; static blt_int8u seedRemainderLen = 0;
static blt_int8u *seedCurrentPtr;
/* check if calibration/paging resource is requested for seed/key and make static blt_bool sequenceInProgress = BLT_FALSE;
* sure this is the only requested resource blt_int8u seedCurrentLen;
*/
if (((data[2] & XCP_RES_CALPAG) > 0) && ((data[2] & ~XCP_RES_CALPAG) == 0))
{
resourceOK = 1;
}
/* check if programming resource is requested for seed/key and make
* sure this is the only requested resource
*/
if (((data[2] & XCP_RES_PGM) > 0) && ((data[2] & ~XCP_RES_PGM) == 0))
{
resourceOK = 1;
}
/* check if data acquisition resource is requested for seed/key and make
* sure this is the only requested resource
*/
if (((data[2] & XCP_RES_DAQ) > 0) && ((data[2] & ~XCP_RES_DAQ) == 0))
{
resourceOK = 1;
}
/* check if data stimulation resource is requested for seed/key and make
* sure this is the only requested resource
*/
if (((data[2] & XCP_RES_STIM) > 0) && ((data[2] & ~XCP_RES_STIM) == 0))
{
resourceOK = 1;
}
/* now process the resource validation */
if (resourceOK == 0)
{
XcpSetCtoError(XCP_ERR_OUT_OF_RANGE);
return;
}
/* store resource for which the seed/key sequence is started */
xcpInfo.s_n_k_resource = data[2];
/* set packet id to command response packet */ /* set packet id to command response packet */
xcpInfo.ctoData[0] = XCP_PID_RES; xcpInfo.ctoData[0] = XCP_PID_RES;
/* request the seed from the application */ /* validate requested resource in case the mode flag equals 0 */
xcpInfo.ctoData[1] = XcpGetSeed(xcpInfo.s_n_k_resource, &xcpInfo.ctoData[2]); if (data[1] == 0)
/* seed cannot be longer than XCP_CTO_PACKET_LEN-2 */
if (xcpInfo.ctoData[1] > (XCP_CTO_PACKET_LEN-2))
{ {
/* seed length is too long */ /* init resource check variable as if an illegal resource is requested */
XcpSetCtoError(XCP_ERR_OUT_OF_RANGE); resourceOK = 0;
return;
/* check if calibration/paging resource is requested for seed/key and make
* sure this is the only requested resource
*/
if (((data[2] & XCP_RES_CALPAG) > 0) && ((data[2] & ~XCP_RES_CALPAG) == 0))
{
resourceOK = 1;
}
/* check if programming resource is requested for seed/key and make
* sure this is the only requested resource
*/
if (((data[2] & XCP_RES_PGM) > 0) && ((data[2] & ~XCP_RES_PGM) == 0))
{
resourceOK = 1;
}
/* check if data acquisition resource is requested for seed/key and make
* sure this is the only requested resource
*/
if (((data[2] & XCP_RES_DAQ) > 0) && ((data[2] & ~XCP_RES_DAQ) == 0))
{
resourceOK = 1;
}
/* check if data stimulation resource is requested for seed/key and make
* sure this is the only requested resource
*/
if (((data[2] & XCP_RES_STIM) > 0) && ((data[2] & ~XCP_RES_STIM) == 0))
{
resourceOK = 1;
}
/* now process the resource validation */
if (resourceOK == 0)
{
XcpSetCtoError(XCP_ERR_OUT_OF_RANGE);
return;
}
/* check if the resource is already unlocked */
if ((xcpInfo.protection & data[2]) == 0)
{
/* set the seed length to 0 to indicate that the resource is already unlocked */
xcpInfo.ctoData[1] = 0;
/* set packet length */
xcpInfo.ctoLen = 2;
/* no need to continue processing */
return;
}
/* store resource for which the seed/key sequence is started */
xcpInfo.s_n_k_resource = data[2];
} }
/* process the mode flag. 0 is first part of the seed, 1 is remainder of the seed */
if (data[1] == 0)
{
/* set flag that a seed reading sequence is now in progress */
sequenceInProgress = BLT_TRUE;
/* obtain the seed and store it in the buffer */
seedRemainderLen = XcpGetSeed(xcpInfo.s_n_k_resource, seedBuffer);
/* protect against buffer overrun */
ASSERT_RT(seedRemainderLen <= XCP_SEED_MAX_LEN);
/* set seed pointer */
seedCurrentPtr = &seedBuffer[0];
}
/* seed remainder is requested */
else
{
/* this is only allowed if a sequence is in progress */
if (sequenceInProgress == BLT_FALSE)
{
/* invalid sequence */
XcpSetCtoError(XCP_ERR_SEQUENCE);
/* reset seed/key resource variable for possible next unlock */
xcpInfo.s_n_k_resource = 0;
return;
}
}
/* determine number of seed bytes that fit in the first response */
seedCurrentLen = seedRemainderLen;
if (seedCurrentLen > (XCP_CTO_PACKET_LEN-2))
{
seedCurrentLen = XCP_CTO_PACKET_LEN-2;
}
/* store the first part of the seed in the response */
CpuMemCopy((blt_addr)(&xcpInfo.ctoData[2]), (blt_addr)seedCurrentPtr, seedCurrentLen);
xcpInfo.ctoData[1] = seedRemainderLen;
/* update control variables */
seedRemainderLen -= seedCurrentLen;
seedCurrentPtr += seedCurrentLen;
/* reset sequence flag at the end of the sequence */
if (seedRemainderLen == 0)
{
sequenceInProgress = BLT_FALSE;
}
/* set packet length */ /* set packet length */
xcpInfo.ctoLen = xcpInfo.ctoData[1] + 2; xcpInfo.ctoLen = seedCurrentLen + 2;
} /*** end of XcpCmdGetSeed ***/ } /*** end of XcpCmdGetSeed ***/
@ -989,41 +1041,82 @@ static void XcpCmdGetSeed(blt_int8u *data)
****************************************************************************************/ ****************************************************************************************/
static void XcpCmdUnlock(blt_int8u *data) static void XcpCmdUnlock(blt_int8u *data)
{ {
/* key cannot be longer than XCP_CTO_PACKET_LEN-2 */ /* made key buffer static to lower stack load */
if (data[1] > (XCP_CTO_PACKET_LEN-2)) static blt_int8u keyBuffer[XCP_KEY_MAX_LEN];
static blt_int8u keyPreviousRemainder = 0;
static blt_int8u keyTotalLen = 0;
static blt_int8u *keyCurrentPtr;
static blt_int8u keyReceivedLen = 0;
blt_int8u keyCurrentLen;
/* verify that the key will actually fit in the buffer */
if (data[1] > XCP_KEY_MAX_LEN)
{ {
/* key is too long incorrect */ /* reset previous remainder for the next loop iteration */
XcpSetCtoError(XCP_ERR_SEQUENCE); keyPreviousRemainder = 0;
/* key is too long */
XcpSetCtoError(XCP_ERR_OUT_OF_RANGE);
/* reset seed/key resource variable for possible next unlock */
xcpInfo.s_n_k_resource = 0;
return; return;
} }
/* verify the key */ /* is this the start of a key reception? the first unlock message contains the total
if (XcpVerifyKey(xcpInfo.s_n_k_resource, &data[2], data[1]) == 0) * length of the key and subsequent messages the remainder length. if the received
* length is >= than the previously received remainder, it must be the reception
* start of a new key.
*/
if (data[1] >= keyPreviousRemainder)
{ {
/* invalid key so inform the master and do a disconnect */ /* store the total length of the key */
XcpSetCtoError(XCP_ERR_ACCESS_LOCKED); keyTotalLen = data[1];
/* initialize pointer to key reception buffer */
keyCurrentPtr = &keyBuffer[0];
/* reset number of received key bytes */
keyReceivedLen = 0;
/* indicate that the xcp connection is disconnected */
xcpInfo.connected = 0;
/* enable resource protection */
XcpProtectResources();
return;
} }
/* store length / remainder for checking during the next iteration */
/* key correct so unlock the resource */ keyPreviousRemainder = data[1];
xcpInfo.protection &= ~xcpInfo.s_n_k_resource; /* determine how many key bytes were received */
keyCurrentLen = data[1];
/* reset seed/key resource variable for possible next unlock */ if (keyCurrentLen > (XCP_CTO_PACKET_LEN-2))
xcpInfo.s_n_k_resource = 0; {
keyCurrentLen = XCP_CTO_PACKET_LEN-2;
}
/* store the received key bytes to the buffer */
CpuMemCopy((blt_addr)keyCurrentPtr, (blt_addr)(&data[2]), keyCurrentLen);
/* update control variables */
keyCurrentPtr += keyCurrentLen;
keyReceivedLen += keyCurrentLen;
/* check if the entire key was received */
if (keyReceivedLen >= keyTotalLen)
{
/* reset previous remainder for the next loop iteration */
keyPreviousRemainder = 0;
/* verify the key */
if (XcpVerifyKey(xcpInfo.s_n_k_resource, keyBuffer, keyTotalLen) == 0)
{
/* invalid key so inform the master and do a disconnect */
XcpSetCtoError(XCP_ERR_ACCESS_LOCKED);
/* indicate that the xcp connection is disconnected */
xcpInfo.connected = 0;
/* reset seed/key resource variable for possible next unlock */
xcpInfo.s_n_k_resource = 0;
/* enable resource protection */
XcpProtectResources();
return;
}
/* key correct so unlock the resource */
xcpInfo.protection &= ~xcpInfo.s_n_k_resource;
/* reset seed/key resource variable for possible next unlock */
xcpInfo.s_n_k_resource = 0;
}
/* set packet id to command response packet */ /* set packet id to command response packet */
xcpInfo.ctoData[0] = XCP_PID_RES; xcpInfo.ctoData[0] = XCP_PID_RES;
/* report the current resource protection */ /* report the current resource protection */
xcpInfo.ctoData[1] = xcpInfo.protection; xcpInfo.ctoData[1] = xcpInfo.protection;
/* set packet length */ /* set packet length */
xcpInfo.ctoLen = 2; xcpInfo.ctoLen = 2;
} /*** end of XcpCmdUnlock ***/ } /*** end of XcpCmdUnlock ***/

View File

@ -53,10 +53,10 @@
#if (BOOT_CPU_BYTE_ORDER_MOTOROLA > 0) #if (BOOT_CPU_BYTE_ORDER_MOTOROLA > 0)
/** \brief XCP byte ordering according to the Motorola (big-endian). */ /** \brief XCP byte ordering according to the Motorola (big-endian). */
#define XCP_MOTOROLA_FORMAT (0x01) #define XCP_MOTOROLA_FORMAT (0x01)
#else #else
/** \brief XCP byte ordering according to the Intel (little-endian). */ /** \brief XCP byte ordering according to the Intel (little-endian). */
#define XCP_MOTOROLA_FORMAT (0x00) #define XCP_MOTOROLA_FORMAT (0x00)
#endif #endif
/** \brief Enable (=1) or disable (=0) support for the calibration resource. This is /** \brief Enable (=1) or disable (=0) support for the calibration resource. This is
@ -240,6 +240,11 @@
/** \brief Use user defined algorithm. */ /** \brief Use user defined algorithm. */
#define XCP_CS_USER (0xff) #define XCP_CS_USER (0xff)
/** \brief Maximum number of bytes of a seed for the seed/key security feature. */
#define XCP_SEED_MAX_LEN (BOOT_XCP_SEED_MAX_LEN)
/** \brief Maximum number of bytes of a key for the seed/key security feature. */
#define XCP_KEY_MAX_LEN (BOOT_XCP_KEY_MAX_LEN)
/**************************************************************************************** /****************************************************************************************
* Function prototypes * Function prototypes