blob: ec9b5bba5000010909c7db5f333d81fe9e80fca9 [file] [log] [blame]
/*
*
* Copyright 2019-2020 NXP
* SPDX-License-Identifier: Apache-2.0
*/
/**
*
* @par Description
* This file implements the high-level APDU handling of the SM module.
* @par History
* 1.0 31-march-2014 : Initial version
* 1.1 10-april-2019 : Removed compile time choice 'USE_MALLOC_FOR_APDU_BUFFER'
*
*****************************************************************************/
#include <stddef.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#if defined(SSS_USE_FTR_FILE)
#include "fsl_sss_ftr.h"
#else
#include "fsl_sss_ftr_default.h"
#endif
#include "sm_apdu.h"
// #include "ax_api.h"
#include "scp.h"
#include "nxLog_hostLib.h"
#include "nxEnsure.h"
static void ReserveLc(apdu_t * pApdu);
static void SetLc(apdu_t * pApdu, U16 lc);
static void AddLe(apdu_t * pApdu, U16 le);
#if SSS_HAVE_A71CH_SIM
/* Send session ID in trans-receive */
static U8 session_Tlv[7];
static U8 gEnableEnc = 0;
#endif
static U8 sharedApduBuffer[MAX_APDU_BUF_LENGTH];
#ifdef TGT_A71CH
#if ( (APDU_HEADER_LENGTH + APDU_STD_MAX_DATA + 1) >= MAX_APDU_BUF_LENGTH )
#error "Ensure MAX_APDU_BUF_LENGTH is big enough"
#endif
#endif // TGT_A71CH
/**
* Associates a memory buffer with the APDU buffer.
*
* By default (determined at compile time) the buffer is not allocated with each call, but a reference
* is made to a static data structure.
*
* \param[in,out] pApdu APDU buffer
* \returns always returns 0
*/
U8 AllocateAPDUBuffer(apdu_t *pApdu)
{
ENSURE_OR_GO_EXIT(pApdu != NULL);
// In case of e.g. TGT_A7, pApdu is pointing to a structure defined on the stack
// so pApdu->pBuf contains random data
pApdu->pBuf = sharedApduBuffer;
exit:
return 0;
}
/**
* Clears the previously referenced APDU buffer.
*
* In case the buffer was effectively malloc'd by ::AllocateAPDUBuffer it will also be freed.
*
* \param[in,out] pApdu APDU buffer
* \return Always returns 0
*/
U8 FreeAPDUBuffer(apdu_t * pApdu)
{
ENSURE_OR_GO_EXIT(pApdu != NULL);
if (pApdu->pBuf)
{
U16 nClear = (pApdu->rxlen > MAX_APDU_BUF_LENGTH) ? MAX_APDU_BUF_LENGTH : pApdu->rxlen;
memset(pApdu->pBuf, 0, nClear);
pApdu->pBuf = 0;
}
exit:
return 0;
}
/**
* Sets up the command APDU header.
* \param[in,out] pApdu APDU buffer
* \param[in] extendedLength Indicates if command/response have extended length. Either ::USE_STANDARD_APDU_LEN or ::USE_EXTENDED_APDU_LEN
* \return offset in APDU buffer after the header
*/
U8 SetApduHeader(apdu_t * pApdu, U8 extendedLength)
{
U8 ret = 0;
// pApdu->edc = eEdc_NoErrorDetection;
ENSURE_OR_GO_EXIT(pApdu != NULL);
pApdu->pBuf[0] = pApdu->cla;
pApdu->pBuf[1] = pApdu->ins;
pApdu->pBuf[2] = pApdu->p1;
pApdu->pBuf[3] = pApdu->p2;
pApdu->extendedLength = extendedLength;
pApdu->hasData = false;
pApdu->lcLength = 0;
pApdu->lc = 0;
pApdu->hasLe = false;
// No LC yet
pApdu->offset = APDU_OFFSET_LC;
// adapt length
pApdu->buflen = pApdu->offset;
// Set rxlen to default value
pApdu->rxlen = 0;
ret = (U8)(pApdu->offset);
exit:
return ret;
}
#if SSS_HAVE_A71CH_SIM
/**
* Creates session TLV from session ID. Session ID is retrieved as response to auth command.
* \param[in] sessionId
*/
void set_SessionId_Tlv(U32 sessionId)
{
session_Tlv[0] = 0xBE;
session_Tlv[1] = 0xBE;
session_Tlv[2] = 0x04;
session_Tlv[3] = (U8)(sessionId >> 24);
session_Tlv[4] = (U8)(sessionId >> 16);
session_Tlv[5] = (U8)(sessionId >> 8);
session_Tlv[6] = (U8)(sessionId >> 0);
gEnableEnc = sessionId !=0 ? 1:0;
}
#endif
/**
* In the final stage before sending the APDU cmd one needs to update the values of lc (and le).
* \param[in,out] pApdu APDU buffer
* \param[in] lc
*/
void smApduAdaptLc(apdu_t *pApdu, U16 lc)
{
SetLc(pApdu, lc);
}
/**
* In the final stage before sending the APDU cmd one needs to update the values of le (and lc).
* \param[in,out] pApdu APDU buffer
* \param[in] le
*/
void smApduAdaptLe(apdu_t *pApdu, U16 le)
{
AddLe(pApdu, le);
}
/**
* In the final stage before sending the APDU cmd one needs to update the values of lc and le.
* \param[in,out] pApdu APDU buffer
* \param[in] lc
* \param[in] le
*/
void smApduAdaptLcLe(apdu_t *pApdu, U16 lc, U16 le)
{
SetLc(pApdu, lc);
AddLe(pApdu, le);
}
/**
* Reserves bytes for the LC in the command APDU and updated the pApdu data structure to match.
* Must be called once in case the APDU cmd has a command data section.
* \pre pApdu->hasData has been set.
* \param[in,out] pApdu APDU buffer
*/
static void ReserveLc(apdu_t * pApdu)
{
ENSURE_OR_GO_EXIT(pApdu != NULL);
pApdu->lcLength = 0;
ENSURE_OR_GO_EXIT(pApdu->hasData != 0);
if (pApdu->extendedLength) {
pApdu->lcLength = 3;
}
else {
pApdu->lcLength = 1;
}
pApdu->offset += pApdu->lcLength;
pApdu->buflen += pApdu->lcLength;
exit:
return;
}
/**
* Sets the LC value in the command APDU.
* @pre ReserveLc(...) has been called or there is no command data section
* @param[in,out] pApdu APDU buffer
* @param[in] lc LC value to be set
*/
static void SetLc(apdu_t * pApdu, U16 lc)
{
ENSURE_OR_GO_EXIT(pApdu != NULL);
ENSURE_OR_GO_EXIT((pApdu->lcLength != 0) || (pApdu->hasData == 0));
// NOTE:
// pApdu->lcLength was set to its proper value in a call to ReserveLc(...)
if (pApdu->hasData) {
if (pApdu->extendedLength) {
pApdu->lc = lc;
// pApdu->lcLength = 3;
pApdu->pBuf[APDU_OFFSET_LC] = 0x00;
pApdu->pBuf[APDU_OFFSET_LC + 1] = (U8)(lc >> 8);
pApdu->pBuf[APDU_OFFSET_LC + 2] = (U8)(lc & 0xFF);
}
else {
pApdu->lc = lc;
// pApdu->lcLength = 1;
pApdu->pBuf[APDU_OFFSET_LC] = (U8)(lc & 0xFF);
}
}
else {
pApdu->lcLength = 0;
}
exit:
return;
}
/**
* Adds the LE value to the command APDU.
* @param pApdu [IN/OUT] APDU buffer
* @param le [IN] LE
* @return
*/
static void AddLe(apdu_t * pApdu, U16 le)
{
ENSURE_OR_GO_EXIT(pApdu != NULL);
pApdu->hasLe = true;
pApdu->le = le;
if (pApdu->extendedLength) {
if (pApdu->hasData) {
pApdu->pBuf[pApdu->offset] = (U8)(le >> 8);
pApdu->pBuf[pApdu->offset + 1] = (U8)(le & 0xFF);
pApdu->leLength = 2;
}
else {
pApdu->pBuf[pApdu->offset] = 0x00;
pApdu->pBuf[pApdu->offset + 1] = (U8)(le >> 8);
pApdu->pBuf[pApdu->offset + 2] = (U8)(le & 0xFF);
pApdu->leLength = 3;
}
}
else {
// regular length
pApdu->pBuf[pApdu->offset] = (U8)(le & 0xFF);
pApdu->leLength = 1;
}
pApdu->offset += pApdu->leLength;
pApdu->buflen += pApdu->leLength;
exit:
return;
}
#if 0
/**
* @function AddTlvItem
* @description Adds a Tag-Length-Value structure to the command APDU.
* @param pApdu [IN/OUT] APDU buffer.
* @param tag [IN] tag; either a 1-byte tag or a 2-byte tag
* @param dataLength [IN] length of the Value
* @param pValue [IN] Value
* @return SW_OK or ERR_BUF_TOO_SMALL
*/
U16 AddTlvItem(apdu_t * pApdu, U16 tag, U16 dataLength, const U8 *pValue)
{
U8 msbTag = tag >> 8;
U8 lsbTag = tag & 0xff;
// If this is the first tag added to the buffer, we needs to ensure
// the correct offset is used writing the data. This depends on
// whether the APDU is a standard or an extended APDU.
if (pApdu->hasData == 0)
{
pApdu->hasData = 1;
ReserveLc(pApdu);
}
// Ensure no buffer overflow will occur before writing any data to buffer
{
U32 xtraData = 0;
U32 u32_Offset = (U32)(pApdu->offset);
xtraData = 1;
// Tag
if (msbTag != 0x00)
{
// 2-byte tag
xtraData++;
}
// Length
if (dataLength <= 0x7f)
{
// 1-byte length
xtraData++;
}
else if (dataLength <= 0xff)
{
// 2-byte length
xtraData += 2;
}
else
{
// 3-byte length
xtraData += 3;
}
xtraData += dataLength;
// Can we still add 'xtraData' to internal buffer without buffer overwrite?
if ( (u32_Offset + xtraData) > MAX_APDU_BUF_LENGTH)
{
// Bufferflow would occur
return ERR_BUF_TOO_SMALL;
}
}
// Tag
if (msbTag != 0x00)
{
// 2-byte tag
pApdu->pBuf[pApdu->offset++] = msbTag;
}
pApdu->pBuf[pApdu->offset++] = lsbTag;
// Length
if (dataLength <= 0x7f)
{
// 1-byte length
pApdu->pBuf[pApdu->offset++] = (U8) dataLength;
pApdu->lc += 2 + dataLength;
}
else if (dataLength <= 0xff)
{
// 2-byte length
pApdu->pBuf[pApdu->offset++] = 0x81;
pApdu->pBuf[pApdu->offset++] = (U8) dataLength;
pApdu->lc += 3 + dataLength;
}
else
{
// 3-byte length
pApdu->pBuf[pApdu->offset++] = 0x82;
pApdu->pBuf[pApdu->offset++] = dataLength >> 8;
pApdu->pBuf[pApdu->offset++] = dataLength & 0xff;
pApdu->lc += 4 + dataLength;
}
// Value
memcpy(&pApdu->pBuf[pApdu->offset], pValue, dataLength);
pApdu->offset += dataLength;
// adapt length
pApdu->buflen = pApdu->offset;
return SW_OK;
}
/**
* AddStdCmdData
* \deprecated Use ::smApduAppendCmdData instead
*/
U16 AddStdCmdData(apdu_t * pApdu, U16 dataLen, const U8 *data)
{
pApdu->hasData = 1;
ReserveLc(pApdu);
pApdu->lc += dataLen;
// Value
memcpy(&pApdu->pBuf[pApdu->offset], data, dataLen);
pApdu->offset += dataLen;
// adapt length
pApdu->buflen = pApdu->offset;
return pApdu->offset;
}
/**
* @function ParseResponse
* @description Parses a received Tag-Length-Value structure (response APDU).
* @param pApdu [IN] APDU buffer
* @param expectedTag [IN] expected tag; either a 1-byte tag or a 2-byte tag
* @param pLen [IN,OUT] IN: size of buffer provided; OUT: length of the received Value
* @param pValue [OUT] received Value
* @return status
*/
U16 ParseResponse(apdu_t *pApdu, U16 expectedTag, U16 *pLen, U8 *pValue)
{
U16 tag = 0;
U16 rv = ERR_GENERAL_ERROR;
int foundTag = 0;
U16 bufferLen = *pLen;
*pLen = 0;
if (pApdu->rxlen < 2) /* minimum: 2 byte for response */
{
return ERR_GENERAL_ERROR;
}
else
{
/* check status returned is okay */
if ((pApdu->pBuf[pApdu->rxlen - 2] != 0x90) || (pApdu->pBuf[pApdu->rxlen - 1] != 0x00))
{
return ERR_GENERAL_ERROR;
}
else // response okay
{
pApdu->offset = 0;
do
{
U16 len = 0;
// Ensure we don't parse beyond the APDU Response Data
if (pApdu->offset >= (pApdu->rxlen -2)) { break; }
/* get the tag (see ISO 7816-4 annex D); limited to max 2 bytes */
if ((pApdu->pBuf[pApdu->offset] & 0x1F) != 0x1F) /* 1 byte tag only */
{
tag = (pApdu->pBuf[pApdu->offset] & 0x00FF);
pApdu->offset += 1;
}
else /* tag consists out of 2 bytes */
{
tag = (pApdu->pBuf[pApdu->offset] << 8) + pApdu->pBuf[pApdu->offset + 1];
pApdu->offset += 2;
}
// Ensure we don't parse beyond the APDU Response Data
if (pApdu->offset >= (pApdu->rxlen -2)) { break; }
// tag is OK
/* get the length (see ISO 7816-4 annex D) */
if ((pApdu->pBuf[pApdu->offset] & 0x80) != 0x80)
{
/* 1 byte length */
len = (pApdu->pBuf[pApdu->offset++] & 0x00FF);
}
else
{
/* length consists of 2 or 3 bytes */
U8 additionalBytesForLength = (pApdu->pBuf[pApdu->offset++] & 0x7F);
if (additionalBytesForLength == 1)
{
len = pApdu->pBuf[pApdu->offset];
pApdu->offset += 1;
}
else if (additionalBytesForLength == 2)
{
len = (pApdu->pBuf[pApdu->offset] << 8) + pApdu->pBuf[pApdu->offset + 1];
pApdu->offset += 2;
}
else
{
return ERR_GENERAL_ERROR;
}
}
// Ensure we don't parse beyond the APDU Response Data
if (pApdu->offset >= (pApdu->rxlen -2)) { break; }
if (tag == expectedTag)
{
// copy the value
if ( (len > 0) && (bufferLen >= len) )
{
*pLen = len;
memcpy(pValue, &pApdu->pBuf[pApdu->offset], *pLen);
rv = SW_OK;
foundTag = 1;
break;
}
else
{
rv = ERR_BUF_TOO_SMALL;
break;
}
}
// update the offset
pApdu->offset += len;
} while (!foundTag);
}
}
return rv;
}
#endif // TGT_A71CH
/**
* Add or append data to the body of a command APDU.
* WARNING:
* - Bufferoverflow fix not applied for SSS_HAVE_A71CH_SIM
* WARNING for non-TGT_A71CH cases :
* - TGT_A71CL: This function must only be called once in case pApdu->txHasChkSum is set
*/
U16 smApduAppendCmdData(apdu_t *pApdu, const U8 *data, U16 dataLen)
{
U16 rv = ERR_GENERAL_ERROR;
ENSURE_OR_GO_EXIT(pApdu != NULL);
ENSURE_OR_GO_EXIT(data != NULL);
#ifdef TGT_A71CH
// The maximum amount of data payload depends on (whichever is smaller)
// - STD-APDU (MAX=255 byte) / EXTENDED-APDU (MAX=65536 byte)
// - size of pApdu->pBuf (MAX_APDU_BUF_LENGTH)
// Standard Length APDU's:
// There is a pre-processor macro in place that ensures 'pApdu->pBuf' is of sufficient size
// Extended Length APDU's (not used by A71CH):
// APDU payload restricted by buffersize of 'pApdu->pBuf'
U16 maxPayload_noLe;
if (pApdu->extendedLength) {
maxPayload_noLe = MAX_APDU_BUF_LENGTH - EXT_CASE4_APDU_OVERHEAD;
}
else {
maxPayload_noLe = APDU_HEADER_LENGTH + APDU_STD_MAX_DATA;
}
#endif // TGT_A71CH
#ifdef TGT_A71CL
U16 maxPayload_noLe;
maxPayload_noLe = MAX_APDU_BUF_LENGTH - EXT_CASE4_APDU_OVERHEAD;
if (pApdu->txHasChkSum == 1) {
maxPayload_noLe -= pApdu->txChkSumLength;
}
#endif // TGT_A71CL
// If this is the first commmand data section added to the buffer, we needs to ensure
// the correct offset is used writing the data. This depends on
// whether the APDU is a standard or an extended APDU.
if (pApdu->hasData == 0)
{
pApdu->hasData = 1;
ReserveLc(pApdu);
}
#if SSS_HAVE_A71CH_SIM
if (gEnableEnc)
{
pApdu->lc += (dataLen + sizeof(session_Tlv));
//add SessionId_Tlv
memcpy(&pApdu->pBuf[pApdu->offset], session_Tlv, sizeof(session_Tlv));
pApdu->offset += sizeof(session_Tlv);
}
else
#endif // SSS_HAVE_A71CH_SIM
{
pApdu->lc += dataLen;
}
#ifdef TGT_A71CL
/* add for cl */
if (pApdu->txHasChkSum == 1) {
pApdu->lc += pApdu->txChkSumLength;
pApdu->pBuf[pApdu->offset - 1] = (U8)pApdu->lc;
}
#endif // TGT_A71CL
// Value
#if defined(TGT_A71CH) || defined(TGT_A71CL)
if (dataLen <= (maxPayload_noLe - pApdu->offset))
{
memcpy(&pApdu->pBuf[pApdu->offset], data, dataLen);
pApdu->offset += dataLen;
}
else
{
return ERR_INTERNAL_BUF_TOO_SMALL;
}
#else // defined(TGT_A71CH) || defined(TGT_A71CL)
memcpy(&pApdu->pBuf[pApdu->offset], data, dataLen);
pApdu->offset += dataLen;
#endif // defined(TGT_A71CH) || defined(TGT_A71CL)
// adapt length
pApdu->buflen = pApdu->offset;
rv = pApdu->offset;
exit:
return rv;
}
/**
* Gets the Status Word from the APDU.
* @param[in] pApdu Pointer to the APDU.
* @param[in,out] pIsOk IN: Pointer to the error indicator, allowed to be NULL; OUT: Points to '1' in case SW is 0x9000
* @return Status Word or ::ERR_COMM_ERROR
*/
U16 smGetSw(apdu_t *pApdu, U8 *pIsOk)
{
U16 sw = ERR_API_ERROR;
U16 offset;
ENSURE_OR_GO_EXIT(pApdu != NULL);
ENSURE_OR_GO_EXIT(pIsOk != NULL);
if (pApdu->rxlen >= 2)
{
offset = pApdu->rxlen - 2;
sw = (pApdu->pBuf[offset] << 8) + pApdu->pBuf[offset + 1];
if (sw == SW_OK)
{
*pIsOk = 1;
}
else
{
*pIsOk = 0;
}
}
else
{
sw = ERR_COMM_ERROR;
*pIsOk = 0;
}
exit:
return sw;
}
/**
* verify crc checksum.
* \param[in] pApdu APDU buffer
* \param[in] dataLen data length to be use for crc caluate
* \return offset in APDU buffer after the header
*/
#if defined(TGT_A71CL)
static U8 smVerifyCrc(apdu_t *pApdu, U16 dataLen)
{
U16 crc = 0;
U16 recvCrc = 0;
ENSURE_OR_GO_EXIT(pApdu != NULL);
//FIXME: Where is the definition for below function?
//crc = CL_CalCRC(&pApdu->pBuf[pApdu->offset], (U32)dataLen, 0xFFFF);
recvCrc = *(U16*)&pApdu->pBuf[pApdu->offset + dataLen];
if (crc != recvCrc) {
return 0;
} else {
return 1;
}
exit:
return 0;
}
#endif
/**
* Retrieve the response data of the APDU response, in case the status word matches ::SW_OK
*/
U16 smApduGetResponseBody(apdu_t *pApdu, U8 *buf, U16 *bufLen)
{
U16 tailInfoLen = 2;
U16 rv = ERR_GENERAL_ERROR;
ENSURE_OR_GO_EXIT(pApdu != NULL);
if (pApdu->rxlen < 2) /* minimum: 2 byte for response */
{
*bufLen = 0;
return ERR_GENERAL_ERROR;
}
else
{
/* check status returned is okay */
if (((pApdu->pBuf[pApdu->rxlen - 2] != 0x90) || (pApdu->pBuf[pApdu->rxlen - 1] != 0x00)) &&
(pApdu->pBuf[pApdu->rxlen -2] != 0x63) &&
(pApdu->pBuf[pApdu->rxlen - 2] != 0x95)) {
*bufLen = 0;
return ERR_GENERAL_ERROR;
}
else // response okay
{
pApdu->offset = 0;
#if defined(TGT_A71CL)
if (pApdu->rxHasChkSum == 1) {
tailInfoLen += pApdu->rxChkSumLength;
}
#endif
if ((pApdu->rxlen - tailInfoLen) > *bufLen)
{
*bufLen = 0;
return ERR_BUF_TOO_SMALL;
}
else
{
*bufLen = pApdu->rxlen - tailInfoLen;
#if defined(TGT_A71CL)
if (pApdu->rxHasChkSum == 1) {
if (smVerifyCrc(pApdu, *bufLen)) {
memcpy(buf, &(pApdu->pBuf[pApdu->offset]), *bufLen);
} else {
return ERR_CRC_CHKSUM_VERIFY;
}
}
else
#endif
{
if (*bufLen) {
memcpy(buf, &(pApdu->pBuf[pApdu->offset]), *bufLen);
}
}
}
}
}
rv = SW_OK;
exit:
return rv;
}
#ifdef TGT_A71CL
/**
* In the final stage before sending the APDU cmd one needs to update checksum value.
* \param[in,out] pApdu APDU buffer
* \param[in] chksum
*/
U16 smApduAdaptChkSum(apdu_t *pApdu, U16 chkSum)
{
U16 rv = ERR_GENERAL_ERROR;
// assert(pApdu->txHasChkSum == 1);
// U16 tmpchkSum = (chkSum >> 8)|(chkSum << 8);
ENSURE_OR_GO_EXIT(pApdu != NULL);
if (pApdu->txHasChkSum) {
memcpy(&pApdu->pBuf[pApdu->offset], &chkSum, pApdu->txChkSumLength);
}
pApdu->buflen += pApdu->txChkSumLength;
pApdu->offset += pApdu->txChkSumLength;
rv = pApdu->offset;
exit:
return rv;
}
#endif
bool smApduGetArrayBytes(char *str, size_t *len, uint8_t *buffer, size_t buffer_len)
{
if ((strlen(str) % 2) != 0) {
LOG_E("Invalid length");
return false;
}
*len = strlen(str) / 2;
if (buffer_len < *len)
{
LOG_E("Insufficient buffer size\n");
*len = 0;
return false;
}
char *pos = str;
for (size_t count = 0; count < *len; count++) {
if (sscanf(pos, "%2hhx", &buffer[count]) < 1) {
*len = 0;
return false;
}
pos += 2;
}
return true;
}
bool smApduGetTxRxCase(uint8_t *apdu, size_t apduLen, size_t* data_offset, size_t *dataLen, apduTxRx_case_t *apdu_case)
{
*data_offset = 0;
*dataLen = 0;
*apdu_case = APDU_TXRX_CASE_INVALID;
//Invalid apdu
if (apduLen < 4)
{
LOG_E("Wrong APDU format\n");
return false;
}
//Case 1
if (apduLen == 4)
{
*apdu_case = APDU_TXRX_CASE_1;
return true;
}
//Case 2S
else if (apduLen == 5)
{
*apdu_case = APDU_TXRX_CASE_2;
return true;
}
else
{
size_t byte5 = apdu[4] & 0xFF;
if (byte5 != 0x0)
{
if (apduLen == 5 + byte5)
{
//case 3S
*apdu_case = APDU_TXRX_CASE_3;
*data_offset = 5;
*dataLen = byte5;
}
else if (apduLen == 6 + byte5)
{
//case 4S
*apdu_case = APDU_TXRX_CASE_4;
*data_offset = 5;
*dataLen = byte5;
}
else
{
LOG_E("Wrong APDU format\n");
return false;
}
}
else if (apduLen == 7)
{
//case 2E
*apdu_case = APDU_TXRX_CASE_2E;
}
else if (apduLen < 7)
{
LOG_E("Wrong APDU format\n");
return false;
}
else
{
size_t len = ((apdu[5] & 0xFF) << 8) | (apdu[6] & 0xFF);
if (apduLen == 7 + len) {
//case 3E
*apdu_case = APDU_TXRX_CASE_3E;
*data_offset = 7;
*dataLen = len;
}
else if (apduLen == 9 + len) {
//Case 4E
*apdu_case = APDU_TXRX_CASE_4E;
*data_offset = 7;
*dataLen = len;
}
else
{
LOG_E("Wrong APDU format\n");
return false;
}
}
}
return true;
}