mirror of
https://gitlab.com/fabinfra/fabhardware/FabReader2.git
synced 2025-03-13 15:11:51 +01:00
913 lines
34 KiB
C
913 lines
34 KiB
C
|
/******************************************************************************
|
||
|
* \attention
|
||
|
*
|
||
|
* <h2><center>© COPYRIGHT 2019 STMicroelectronics</center></h2>
|
||
|
*
|
||
|
* Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License");
|
||
|
* You may not use this file except in compliance with the License.
|
||
|
* You may obtain a copy of the License at:
|
||
|
*
|
||
|
* www.st.com/myliberty
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
|
||
|
* AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY,
|
||
|
* FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.
|
||
|
* See the License for the specific language governing permissions and
|
||
|
* limitations under the License.
|
||
|
*
|
||
|
******************************************************************************/
|
||
|
|
||
|
/*
|
||
|
* PROJECT: NDEF firmware
|
||
|
* Revision:
|
||
|
* LANGUAGE: ISO C99
|
||
|
*/
|
||
|
|
||
|
/*! \file
|
||
|
*
|
||
|
* \author
|
||
|
*
|
||
|
* \brief Provides NDEF methods and definitions to access NFC Forum T3T
|
||
|
*
|
||
|
* This module provides an interface to perform as a NFC Reader/Writer
|
||
|
* to handle a Type 3 Tag T3T
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
******************************************************************************
|
||
|
* INCLUDES
|
||
|
******************************************************************************
|
||
|
*/
|
||
|
#include "ndef_poller.h"
|
||
|
#include "ndef_t3t.h"
|
||
|
#include "utils.h"
|
||
|
|
||
|
/*
|
||
|
******************************************************************************
|
||
|
* ENABLE SWITCH
|
||
|
******************************************************************************
|
||
|
*/
|
||
|
|
||
|
#ifndef RFAL_FEATURE_NFCF
|
||
|
#error " RFAL: Module configuration missing. Please enable/disable T3T module by setting: RFAL_FEATURE_NFCF "
|
||
|
#endif
|
||
|
|
||
|
//#define RFAL_FEATURE_NFCF
|
||
|
|
||
|
/*
|
||
|
******************************************************************************
|
||
|
* GLOBAL DEFINES
|
||
|
******************************************************************************
|
||
|
*/
|
||
|
#define NDEF_T3T_MAX_DEVICE 1U /*!< T3T maximum number of device for detection */
|
||
|
#define NDEF_T3T_SYSTEMCODE 0x12FCU /*!< SENSF_RES System Code for T3T TS T3T 1.0 7.1.1.1 */
|
||
|
#define NDEF_T3T_WRITEFLAG_ON 0xFU /*!< WriteFlag ON value TS T3T 1.0 7.2.2.16 */
|
||
|
#define NDEF_T3T_WRITEFLAG_OFF 0x0U /*!< WriteFlag OFF value TS T3T 1.0 7.2.2.16 */
|
||
|
#define NDEF_T3T_AREA_OFFSET 16U /*!< T3T Area starts at block #1 */
|
||
|
#define NDEF_T3T_BLOCKLEN 16U /*!< T3T block len is always 16 */
|
||
|
#define NDEF_T3T_NBBLOCKSMAX 4U /*!< T3T max nb of blocks per read/write */
|
||
|
#define NDEF_T3T_FLAG_RW 1U /*!< T3T read/write flag value */
|
||
|
#define NDEF_T3T_FLAG_RO 0U /*!< T3T read only flag value */
|
||
|
#define NDEF_T3T_SENSFRES_NFCID2 2U /*!< T3T offset of UID in SENSFRES struct */
|
||
|
#define NDEF_T3T_ATTRIB_INFO_CHECKSUM_LEN 0xEU /*!< T3T checksum len for attribute info to compute */
|
||
|
#define NDEF_T3T_ATTRIB_INFO_OFFSET_VERSION 0x0U /*!< T3T attribute info offset of version */
|
||
|
#define NDEF_T3T_ATTRIB_INFO_OFFSET_NBR 1U /*!< T3T attribute info offset of number of read */
|
||
|
#define NDEF_T3T_ATTRIB_INFO_OFFSET_NBW 2U /*!< T3T attribute info offset of number of write */
|
||
|
#define NDEF_T3T_ATTRIB_INFO_OFFSET_MAXB 3U /*!< T3T attribute info offset of MAXB */
|
||
|
#define NDEF_T3T_ATTRIB_INFO_OFFSET_FLAG_W 9U /*!< T3T attribute info offset of Write flag */
|
||
|
#define NDEF_T3T_ATTRIB_INFO_OFFSET_FLAG_RW 10U /*!< T3T attribute info offset of Read/Write flag */
|
||
|
#define NDEF_T3T_ATTRIB_INFO_OFFSET_FLAG_LN 11U /*!< T3T attribute info offset of LN field */
|
||
|
#define NDEF_T3T_ATTRIB_INFO_VERSION_1_0 0x10U /*!< T3T attribute info full version number */
|
||
|
#define NDEF_T3T_ATTRIB_INFO_BLOCK_NB 0U /*!< T3T attribute info block number */
|
||
|
#define NDEF_T3T_BLOCKNB_CONF 0x80U /*!< T3T TxRx config value for Read/Write block */
|
||
|
#define NDEF_T3T_CHECK_NB_BLOCKS_LEN 1U /*!< T3T Length of the Nb of blocks in the CHECK reply */
|
||
|
|
||
|
#ifdef RFAL_FEATURE_NFCF
|
||
|
|
||
|
/*
|
||
|
******************************************************************************
|
||
|
* GLOBAL TYPES
|
||
|
******************************************************************************
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
******************************************************************************
|
||
|
* GLOBAL MACROS
|
||
|
******************************************************************************
|
||
|
*/
|
||
|
|
||
|
#define ndefT3TisT3TDevice(device) ((device)->type == RFAL_NFC_LISTEN_TYPE_NFCF)
|
||
|
#define ndefT3TIsWriteFlagON(writeFlag) ((writeFlag) == NDEF_T3T_WRITEFLAG_ON)
|
||
|
|
||
|
/*
|
||
|
******************************************************************************
|
||
|
* LOCAL VARIABLES
|
||
|
******************************************************************************
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
******************************************************************************
|
||
|
* LOCAL FUNCTION PROTOTYPES
|
||
|
******************************************************************************
|
||
|
*/
|
||
|
static ReturnCode ndefT3TPollerReadBlocks ( ndefContext * ctx, uint16_t blockNum, uint8_t nbBlocks, uint8_t* rxBuf, uint16_t rxBufLen, uint16_t *rcvLen );
|
||
|
static ReturnCode ndefT3TPollerReadAttributeInformationBlock ( ndefContext * ctx);
|
||
|
|
||
|
#if NDEF_FEATURE_ALL
|
||
|
static ReturnCode ndefT3TPollerWriteBlocks ( ndefContext * ctx, uint16_t blockNum, uint8_t nbBlocks, const uint8_t* dataBlocks);
|
||
|
static ReturnCode ndefT3TPollerWriteAttributeInformationBlock( ndefContext * ctx);
|
||
|
#endif /* NDEF_FEATURE_ALL */
|
||
|
|
||
|
/*
|
||
|
******************************************************************************
|
||
|
* GLOBAL FUNCTIONS
|
||
|
******************************************************************************
|
||
|
*/
|
||
|
|
||
|
/*******************************************************************************/
|
||
|
static ReturnCode ndefT3TPollerReadBlocks( ndefContext *ctx, uint16_t blockNum, uint8_t nbBlocks, uint8_t *rxBuf, uint16_t rxBufLen, uint16_t *rcvLen )
|
||
|
{
|
||
|
ReturnCode ret;
|
||
|
uint16_t requestedDataSize;
|
||
|
rfalNfcfServBlockListParam servBlock;
|
||
|
rfalNfcfBlockListElem * listBlocks;
|
||
|
uint8_t index;
|
||
|
uint16_t rcvdLen = 0U;
|
||
|
rfalNfcfServ serviceCodeLst = 0x000BU; /* serviceCodeLst */
|
||
|
|
||
|
if( (ctx == NULL) || !ndefT3TisT3TDevice(&ctx->device) )
|
||
|
{
|
||
|
return ERR_PARAM;
|
||
|
}
|
||
|
|
||
|
requestedDataSize = (uint16_t)nbBlocks * NDEF_T3T_BLOCK_SIZE;
|
||
|
if( rxBufLen < requestedDataSize )
|
||
|
{
|
||
|
return ERR_PARAM;
|
||
|
}
|
||
|
|
||
|
listBlocks = ctx->subCtx.t3t.listBlocks;
|
||
|
|
||
|
for (index = 0U; index < nbBlocks; index++ )
|
||
|
{
|
||
|
/* Write each block number (16 bits per block address) */
|
||
|
listBlocks[index].conf = (uint8_t) NDEF_T3T_BLOCKNB_CONF;
|
||
|
listBlocks[index].blockNum = (uint16_t)( blockNum + (uint16_t) index);
|
||
|
}
|
||
|
|
||
|
servBlock.numServ = 1U;
|
||
|
servBlock.servList = &serviceCodeLst;
|
||
|
servBlock.numBlock = nbBlocks;
|
||
|
servBlock.blockList = listBlocks;
|
||
|
|
||
|
ret = rfalNfcfPollerCheck(ctx->device.dev.nfcf.sensfRes.NFCID2, &servBlock, ctx->subCtx.t3t.rxbuf, (uint16_t)sizeof(ctx->subCtx.t3t.rxbuf), &rcvdLen);
|
||
|
if (ret != ERR_NONE)
|
||
|
{
|
||
|
return ret;
|
||
|
}
|
||
|
if( rcvdLen != (uint16_t)(NDEF_T3T_CHECK_NB_BLOCKS_LEN + requestedDataSize) )
|
||
|
{
|
||
|
return ERR_REQUEST;
|
||
|
}
|
||
|
if( requestedDataSize > 0U )
|
||
|
{
|
||
|
(void)ST_MEMCPY( rxBuf, &ctx->subCtx.t3t.rxbuf[NDEF_T3T_CHECK_NB_BLOCKS_LEN], requestedDataSize );
|
||
|
if (rcvLen != NULL)
|
||
|
{
|
||
|
*rcvLen = requestedDataSize;
|
||
|
}
|
||
|
}
|
||
|
return ERR_NONE;
|
||
|
}
|
||
|
|
||
|
/*******************************************************************************/
|
||
|
ReturnCode ndefT3TPollerReadBytes(ndefContext *ctx, uint32_t offset, uint32_t len, uint8_t *buf, uint32_t *rcvdLen)
|
||
|
{
|
||
|
uint16_t res;
|
||
|
uint16_t nbRead;
|
||
|
ReturnCode result = ERR_NONE;
|
||
|
uint32_t currentLen = len;
|
||
|
uint32_t lvRcvLen = 0U;
|
||
|
const uint16_t blockLen = (uint16_t) NDEF_T3T_BLOCKLEN;
|
||
|
uint16_t startBlock = (uint16_t) (offset / blockLen);
|
||
|
uint16_t startAddr = (uint16_t) (startBlock * blockLen);
|
||
|
uint16_t startOffset= (uint16_t) (offset - (uint32_t) startAddr);
|
||
|
uint16_t nbBlocks = (uint16_t) NDEF_T3T_NBBLOCKSMAX;
|
||
|
|
||
|
if( (ctx == NULL) || !ndefT3TisT3TDevice(&ctx->device) || (len == 0U) )
|
||
|
{
|
||
|
return ERR_PARAM;
|
||
|
}
|
||
|
if (ctx->state != NDEF_STATE_INVALID)
|
||
|
{
|
||
|
nbBlocks = ctx->cc.t3t.nbR;
|
||
|
}
|
||
|
|
||
|
if ( startOffset != 0U )
|
||
|
{
|
||
|
/* Unaligned read, need to use a tmp buffer */
|
||
|
res = ndefT3TPollerReadBlocks(ctx, startBlock, 1U /* One block */ , ctx->subCtx.t3t.rxbuf, blockLen, &nbRead);
|
||
|
if (res != ERR_NONE)
|
||
|
{
|
||
|
/* Check result */
|
||
|
result = res;
|
||
|
}
|
||
|
else if (nbRead != NDEF_T3T_BLOCKLEN)
|
||
|
{
|
||
|
/* Check len */
|
||
|
result = ERR_MEM_CORRUPT;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
nbRead = (uint16_t) (nbRead - (uint16_t)startOffset);
|
||
|
if ((uint32_t) nbRead > currentLen)
|
||
|
{
|
||
|
nbRead = (uint16_t) currentLen;
|
||
|
}
|
||
|
if (nbRead > 0U)
|
||
|
{
|
||
|
(void)ST_MEMCPY(buf, &ctx->subCtx.t3t.rxbuf[offset], (uint32_t)nbRead);
|
||
|
}
|
||
|
lvRcvLen += (uint32_t) nbRead;
|
||
|
currentLen -= (uint32_t) nbRead;
|
||
|
startBlock++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
while ( (currentLen >= (uint32_t)blockLen) && (result == ERR_NONE) )
|
||
|
{
|
||
|
if ( currentLen < ((uint32_t)blockLen * nbBlocks) )
|
||
|
{
|
||
|
/* Reduce the nb of blocks to read */
|
||
|
nbBlocks = (uint16_t) (currentLen / blockLen);
|
||
|
}
|
||
|
res = ndefT3TPollerReadBlocks(ctx, startBlock, (uint8_t)nbBlocks, ctx->subCtx.t3t.rxbuf, blockLen * nbBlocks, &nbRead);
|
||
|
if (res != ERR_NONE)
|
||
|
{
|
||
|
/* Check result */
|
||
|
return res;
|
||
|
}
|
||
|
else if (nbRead != (blockLen * nbBlocks))
|
||
|
{
|
||
|
/* Check len */
|
||
|
return ERR_MEM_CORRUPT;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
(void)ST_MEMCPY(&buf[lvRcvLen], ctx->subCtx.t3t.rxbuf, (uint32_t)currentLen);
|
||
|
lvRcvLen += nbRead;
|
||
|
currentLen -= nbRead;
|
||
|
startBlock += nbBlocks;
|
||
|
}
|
||
|
}
|
||
|
if ( (currentLen > 0U) && (result == ERR_NONE) )
|
||
|
{
|
||
|
/* Unaligned read, need to use a tmp buffer */
|
||
|
res = ndefT3TPollerReadBlocks(ctx, startBlock, 1U /* One block */, ctx->subCtx.t3t.rxbuf, blockLen, &nbRead);
|
||
|
if (res != ERR_NONE)
|
||
|
{
|
||
|
/* Check result */
|
||
|
return res;
|
||
|
}
|
||
|
else if (nbRead != NDEF_T3T_BLOCKLEN)
|
||
|
{
|
||
|
/* Check len */
|
||
|
return ERR_MEM_CORRUPT;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (currentLen > 0U)
|
||
|
{
|
||
|
(void)ST_MEMCPY(&buf[lvRcvLen], ctx->subCtx.t3t.rxbuf, (uint32_t)currentLen);
|
||
|
}
|
||
|
lvRcvLen += (uint32_t) currentLen;
|
||
|
currentLen -= (uint32_t) currentLen;
|
||
|
}
|
||
|
}
|
||
|
if ( (currentLen == 0U) && (result == ERR_NONE) )
|
||
|
{
|
||
|
result = ERR_NONE;
|
||
|
}
|
||
|
if( rcvdLen != NULL )
|
||
|
{
|
||
|
*rcvdLen = lvRcvLen;
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/*******************************************************************************/
|
||
|
static ReturnCode ndefT3TPollerReadAttributeInformationBlock( ndefContext * ctx)
|
||
|
{
|
||
|
/* Follow 7.4.1 NDEF Detection Procedure */
|
||
|
ReturnCode retcode;
|
||
|
uint8_t * rxbuf;
|
||
|
uint16_t checksum_received;
|
||
|
uint16_t checksum_computed = 0U;
|
||
|
uint16_t rcvLen = 0U;
|
||
|
uint8_t i;
|
||
|
|
||
|
if( (ctx == NULL) || !ndefT3TisT3TDevice(&ctx->device) )
|
||
|
{
|
||
|
return ERR_PARAM;
|
||
|
}
|
||
|
rxbuf = ctx->ccBuf;
|
||
|
retcode = ndefT3TPollerReadBlocks( ctx, NDEF_T3T_ATTRIB_INFO_BLOCK_NB, 1U /* One block */, rxbuf, NDEF_T3T_BLOCK_SIZE, &rcvLen );
|
||
|
if ( (retcode != ERR_NONE) && (rcvLen != NDEF_T3T_BLOCK_SIZE) )
|
||
|
{
|
||
|
return retcode;
|
||
|
}
|
||
|
/* Now compute checksum */
|
||
|
for (i = 0U; i < NDEF_T3T_ATTRIB_INFO_CHECKSUM_LEN; i++)
|
||
|
{
|
||
|
checksum_computed += (uint16_t) rxbuf[i];
|
||
|
}
|
||
|
checksum_received = ((uint16_t)rxbuf[NDEF_T3T_ATTRIB_INFO_CHECKSUM_LEN] << 8U) + (uint16_t)rxbuf[NDEF_T3T_ATTRIB_INFO_CHECKSUM_LEN+ 1U];
|
||
|
if (checksum_received != checksum_computed)
|
||
|
{
|
||
|
return ERR_REQUEST;
|
||
|
}
|
||
|
|
||
|
/* Now copy the attribute struct */
|
||
|
ctx->cc.t3t.majorVersion = ( rxbuf[NDEF_T3T_ATTRIB_INFO_OFFSET_VERSION] >> 4U);
|
||
|
ctx->cc.t3t.minorVersion = ( rxbuf[NDEF_T3T_ATTRIB_INFO_OFFSET_VERSION] & 0xFU);
|
||
|
ctx->cc.t3t.nbR = rxbuf[NDEF_T3T_ATTRIB_INFO_OFFSET_NBR];
|
||
|
ctx->cc.t3t.nbW = rxbuf[NDEF_T3T_ATTRIB_INFO_OFFSET_NBW];
|
||
|
ctx->cc.t3t.nMaxB = ((uint16_t)rxbuf[NDEF_T3T_ATTRIB_INFO_OFFSET_MAXB] << 8U) + (uint16_t)rxbuf[NDEF_T3T_ATTRIB_INFO_OFFSET_MAXB + 1U];
|
||
|
ctx->cc.t3t.writeFlag = rxbuf[NDEF_T3T_ATTRIB_INFO_OFFSET_FLAG_W];
|
||
|
ctx->cc.t3t.rwFlag = rxbuf[NDEF_T3T_ATTRIB_INFO_OFFSET_FLAG_RW];
|
||
|
ctx->cc.t3t.Ln = ((uint32_t)rxbuf[NDEF_T3T_ATTRIB_INFO_OFFSET_FLAG_LN + 0U] << 0x10U)
|
||
|
| ((uint32_t)rxbuf[NDEF_T3T_ATTRIB_INFO_OFFSET_FLAG_LN + 1U] << 0x8U)
|
||
|
| (uint32_t)rxbuf[NDEF_T3T_ATTRIB_INFO_OFFSET_FLAG_LN + 2U];
|
||
|
return ERR_NONE;
|
||
|
}
|
||
|
|
||
|
/*******************************************************************************/
|
||
|
ReturnCode ndefT3TPollerContextInitialization(ndefContext *ctx, const rfalNfcDevice *dev)
|
||
|
{
|
||
|
if( (ctx == NULL) || (dev == NULL) || !ndefT3TisT3TDevice(dev) )
|
||
|
{
|
||
|
return ERR_PARAM;
|
||
|
}
|
||
|
|
||
|
(void)ST_MEMCPY(&ctx->device, dev, sizeof(ctx->device));
|
||
|
|
||
|
ctx->state = NDEF_STATE_INVALID;
|
||
|
|
||
|
return ERR_NONE;
|
||
|
}
|
||
|
|
||
|
/*******************************************************************************/
|
||
|
ReturnCode ndefT3TPollerNdefDetect(ndefContext *ctx, ndefInfo *info)
|
||
|
{
|
||
|
ReturnCode retcode;
|
||
|
rfalFeliCaPollRes pollRes[NDEF_T3T_MAX_DEVICE];
|
||
|
uint8_t devCnt = NDEF_T3T_MAX_DEVICE;
|
||
|
uint8_t collisions = 0U;
|
||
|
|
||
|
if( info != NULL )
|
||
|
{
|
||
|
info->state = NDEF_STATE_INVALID;
|
||
|
info->majorVersion = 0U;
|
||
|
info->minorVersion = 0U;
|
||
|
info->areaLen = 0U;
|
||
|
info->areaAvalableSpaceLen = 0U;
|
||
|
info->messageLen = 0U;
|
||
|
}
|
||
|
|
||
|
if( (ctx == NULL) || !ndefT3TisT3TDevice(&ctx->device) )
|
||
|
{
|
||
|
return ERR_PARAM;
|
||
|
}
|
||
|
ctx->state = NDEF_STATE_INVALID;
|
||
|
|
||
|
/* TS T3T v1.0 7.4.1.1 the Reader/Writer SHALL send a SENSF_REQ Command with System Code set to 12FCh. */
|
||
|
retcode = rfalNfcfPollerPoll( RFAL_FELICA_1_SLOT, NDEF_T3T_SYSTEMCODE, (uint8_t)RFAL_FELICA_POLL_RC_NO_REQUEST, pollRes, &devCnt, &collisions );
|
||
|
if( retcode != ERR_NONE )
|
||
|
{
|
||
|
/* TS T3T v1.0 7.4.1.2 Conclude procedure. */
|
||
|
return retcode;
|
||
|
}
|
||
|
|
||
|
/* Check if UID of the first card is the same */
|
||
|
if( ST_BYTECMP(&(pollRes[0U][NDEF_T3T_SENSFRES_NFCID2]), ctx->device.dev.nfcf.sensfRes.NFCID2, RFAL_NFCF_NFCID2_LEN ) != 0 )
|
||
|
{
|
||
|
return ERR_REQUEST; /* Wrong UID */
|
||
|
}
|
||
|
|
||
|
/* TS T3T v1.0 7.4.1.3 The Reader/Writer SHALL read the Attribute Information Block using the CHECK Command. */
|
||
|
/* TS T3T v1.0 7.4.1.4 The Reader/Writer SHALL verify the value of Checksum of the Attribute Information Block. */
|
||
|
retcode = ndefT3TPollerReadAttributeInformationBlock(ctx);
|
||
|
if( retcode != ERR_NONE )
|
||
|
{
|
||
|
return retcode;
|
||
|
}
|
||
|
|
||
|
/* TS T3T v1.0 7.4.1.6 The Reader/Writer SHALL check if it supports the NDEF mapping version number based on the rules given in Section 7.3. */
|
||
|
if( ctx->cc.t3t.majorVersion != ndefMajorVersion(NDEF_T3T_ATTRIB_INFO_VERSION_1_0) )
|
||
|
{
|
||
|
return ERR_REQUEST;
|
||
|
}
|
||
|
|
||
|
ctx->messageLen = ctx->cc.t3t.Ln;
|
||
|
ctx->messageOffset = NDEF_T3T_AREA_OFFSET;
|
||
|
ctx->areaLen = (uint32_t)ctx->cc.t3t.nMaxB * NDEF_T3T_BLOCK_SIZE;
|
||
|
ctx->state = NDEF_STATE_INITIALIZED;
|
||
|
if (ctx->messageLen > 0U)
|
||
|
{
|
||
|
if (ctx->cc.t3t.rwFlag == NDEF_T3T_FLAG_RW)
|
||
|
{
|
||
|
ctx->state = NDEF_STATE_READWRITE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (ctx->cc.t3t.rwFlag == NDEF_T3T_FLAG_RO)
|
||
|
{
|
||
|
ctx->state = NDEF_STATE_READONLY;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if( info != NULL )
|
||
|
{
|
||
|
info->state = ctx->state;
|
||
|
info->majorVersion = ctx->cc.t3t.majorVersion;
|
||
|
info->minorVersion = ctx->cc.t3t.minorVersion;
|
||
|
info->areaLen = ctx->areaLen;
|
||
|
info->areaAvalableSpaceLen = ctx->areaLen;
|
||
|
info->messageLen = ctx->messageLen;
|
||
|
}
|
||
|
|
||
|
return ERR_NONE;
|
||
|
}
|
||
|
|
||
|
/*******************************************************************************/
|
||
|
ReturnCode ndefT3TPollerReadRawMessage(ndefContext *ctx, uint8_t *buf, uint32_t bufLen, uint32_t *rcvdLen)
|
||
|
{
|
||
|
ReturnCode ret;
|
||
|
|
||
|
if( (ctx == NULL) || !ndefT3TisT3TDevice(&ctx->device) || (buf == NULL) )
|
||
|
{
|
||
|
return ERR_PARAM;
|
||
|
}
|
||
|
|
||
|
/* TS T3T v1.0 7.4.2: This procedure assumes that the Reader/Writer has successfully performed the NDEF detection procedure. */
|
||
|
/* Warning: current tag content must not be changed between NDEF Detect procedure and NDEF read procedure*/
|
||
|
if ( ctx->state <= NDEF_STATE_INITIALIZED )
|
||
|
{
|
||
|
return ERR_WRONG_STATE;
|
||
|
}
|
||
|
/* TS T3T v1.0 7.4.2.1: If the WriteFlag remembered during the NDEF detection procedure is set to ON, the NDEF data may be inconsistent ...*/
|
||
|
if( ndefT3TIsWriteFlagON(ctx->cc.t3t.writeFlag) )
|
||
|
{
|
||
|
/* TS T3T v1.0 7.4.2.1: ... the Reader/Writer SHALL conclude the NDEF read procedure*/
|
||
|
return ERR_WRONG_STATE;
|
||
|
}
|
||
|
|
||
|
if( ctx->messageLen > bufLen )
|
||
|
{
|
||
|
return ERR_NOMEM;
|
||
|
}
|
||
|
|
||
|
/* TS T3T v1.0 7.4.2.2: Read NDEF data */
|
||
|
ret = ndefT3TPollerReadBytes( ctx, ctx->messageOffset, ctx->messageLen, buf, rcvdLen );
|
||
|
if( ret != ERR_NONE )
|
||
|
{
|
||
|
ctx->state = NDEF_STATE_INVALID;
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
#if NDEF_FEATURE_ALL
|
||
|
|
||
|
/*******************************************************************************/
|
||
|
static ReturnCode ndefT3TPollerWriteBlocks( ndefContext * ctx, uint16_t blockNum, uint8_t nbBlocks, const uint8_t* dataBlocks)
|
||
|
{
|
||
|
ReturnCode ret;
|
||
|
rfalNfcfServBlockListParam servBlock;
|
||
|
rfalNfcfBlockListElem * listBlocks;
|
||
|
uint8_t index;
|
||
|
rfalNfcfServ serviceCodeLst = 0x0009U;
|
||
|
|
||
|
if( (ctx == NULL) || !ndefT3TisT3TDevice(&ctx->device) )
|
||
|
{
|
||
|
return ERR_PARAM;
|
||
|
}
|
||
|
|
||
|
listBlocks = ctx->subCtx.t3t.listBlocks;
|
||
|
|
||
|
for (index = 0U; index < nbBlocks; index++)
|
||
|
{
|
||
|
/* Write each block number (16 bits per block address) */
|
||
|
listBlocks[index].conf = (uint8_t) NDEF_T3T_BLOCKNB_CONF;
|
||
|
listBlocks[index].blockNum = (uint16_t)( blockNum + (uint16_t) index);
|
||
|
}
|
||
|
servBlock.numServ = 1U;
|
||
|
servBlock.servList = &serviceCodeLst;
|
||
|
servBlock.numBlock = nbBlocks;
|
||
|
servBlock.blockList = listBlocks;
|
||
|
|
||
|
ret = rfalNfcfPollerUpdate( ctx->device.dev.nfcf.sensfRes.NFCID2, &servBlock, ctx->subCtx.t3t.txbuf, (uint16_t)sizeof(ctx->subCtx.t3t.txbuf), dataBlocks, ctx->subCtx.t3t.rxbuf, (uint16_t)sizeof(ctx->subCtx.t3t.rxbuf));
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/*******************************************************************************/
|
||
|
ReturnCode ndefT3TPollerWriteBytes(ndefContext *ctx, uint32_t offset, const uint8_t *buf, uint32_t len)
|
||
|
{
|
||
|
uint16_t nbRead;
|
||
|
uint16_t nbWrite;
|
||
|
uint16_t res;
|
||
|
ReturnCode result = ERR_NONE;
|
||
|
uint32_t currentLen = len;
|
||
|
uint32_t txtLen = 0U;
|
||
|
const uint16_t blockLen = (uint16_t )NDEF_T3T_BLOCKLEN;
|
||
|
uint16_t nbBlocks = (uint16_t ) NDEF_T3T_NBBLOCKSMAX;
|
||
|
uint16_t startBlock = (uint16_t) (offset / blockLen);
|
||
|
uint16_t startAddr = (uint16_t) (startBlock * blockLen);
|
||
|
uint16_t startOffset= (uint16_t) (offset - (uint32_t) startAddr);
|
||
|
uint8_t tmpBuf[NDEF_T3T_BLOCKLEN];
|
||
|
|
||
|
if( (ctx == NULL) || !ndefT3TisT3TDevice(&ctx->device) || (len == 0U) )
|
||
|
{
|
||
|
return ERR_PARAM;
|
||
|
}
|
||
|
if (ctx->state != NDEF_STATE_INVALID)
|
||
|
{
|
||
|
nbBlocks = ctx->cc.t3t.nbW;
|
||
|
}
|
||
|
|
||
|
if ( startOffset != 0U )
|
||
|
{
|
||
|
/* Unaligned write, need to use a tmp buffer */
|
||
|
res = ndefT3TPollerReadBlocks(ctx, startBlock, 1, tmpBuf, blockLen, &nbRead);
|
||
|
if (res != ERR_NONE)
|
||
|
{
|
||
|
/* Check result */
|
||
|
result = res;
|
||
|
}
|
||
|
else if (nbRead != blockLen)
|
||
|
{
|
||
|
/* Check len */
|
||
|
result = ERR_MEM_CORRUPT;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
/* Fill the rest of the buffer with user data */
|
||
|
nbWrite = NDEF_T3T_BLOCKLEN - startOffset;
|
||
|
if (nbWrite > len)
|
||
|
{
|
||
|
nbWrite = (uint16_t) len;
|
||
|
}
|
||
|
(void)ST_MEMCPY(&tmpBuf[startOffset], buf, nbWrite);
|
||
|
res = ndefT3TPollerWriteBlocks(ctx, startBlock, 1U /* One block */, tmpBuf);
|
||
|
if (res == ERR_NONE)
|
||
|
{
|
||
|
txtLen += (uint32_t) nbWrite;
|
||
|
currentLen -= (uint32_t) nbWrite;
|
||
|
startBlock++;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
result = res; /* Copy the error code */
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
while ( (currentLen >= (uint32_t)blockLen) && (result == ERR_NONE) )
|
||
|
{
|
||
|
if ( currentLen < ((uint32_t)blockLen * nbBlocks) )
|
||
|
{
|
||
|
/* Reduce the nb of blocks to read */
|
||
|
nbBlocks = (uint16_t) (currentLen / blockLen);
|
||
|
}
|
||
|
nbWrite = blockLen * nbBlocks;
|
||
|
res = ndefT3TPollerWriteBlocks(ctx, startBlock, (uint8_t) nbBlocks, &buf[txtLen]);
|
||
|
if (res != ERR_NONE)
|
||
|
{
|
||
|
/* Check result */
|
||
|
result = res;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
txtLen += nbWrite;
|
||
|
currentLen -= nbWrite;
|
||
|
startBlock += nbBlocks;
|
||
|
}
|
||
|
}
|
||
|
if ( (currentLen > 0U) && (result == ERR_NONE) )
|
||
|
{
|
||
|
/* Unaligned write, need to use a tmp buffer */
|
||
|
res = ndefT3TPollerReadBlocks(ctx, startBlock, 1U /* One block */, tmpBuf, blockLen, &nbRead);
|
||
|
if (res != ERR_NONE)
|
||
|
{
|
||
|
/* Check result */
|
||
|
result = res;
|
||
|
}
|
||
|
else if (nbRead != blockLen)
|
||
|
{
|
||
|
/* Check len */
|
||
|
result = ERR_MEM_CORRUPT;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
/* Fill the beginning of the buffer with user data */
|
||
|
(void)ST_MEMCPY( tmpBuf, &buf[txtLen], currentLen);
|
||
|
res = ndefT3TPollerWriteBlocks(ctx, startBlock, 1U /* One block */, tmpBuf);
|
||
|
if (res == ERR_NONE)
|
||
|
{
|
||
|
currentLen = 0U;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
result = res; /* Copy the error code */
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if ( (currentLen == 0U) && (result == ERR_NONE) )
|
||
|
{
|
||
|
result = ERR_NONE;
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/*******************************************************************************/
|
||
|
static ReturnCode ndefT3TPollerWriteAttributeInformationBlock(ndefContext * ctx)
|
||
|
{
|
||
|
uint8_t dataIt;
|
||
|
uint16_t checksum;
|
||
|
uint8_t * buf;
|
||
|
ReturnCode ret;
|
||
|
|
||
|
if( (ctx == NULL) || !ndefT3TisT3TDevice(&ctx->device) )
|
||
|
{
|
||
|
return ERR_PARAM;
|
||
|
}
|
||
|
if ( ctx->state < NDEF_STATE_INITIALIZED )
|
||
|
{
|
||
|
return ERR_WRONG_STATE;
|
||
|
}
|
||
|
dataIt = 0U;
|
||
|
buf = ctx->ccBuf;
|
||
|
checksum = 0U;
|
||
|
buf[dataIt] = ((uint8_t)(ctx->cc.t3t.majorVersion << 4U)) | ctx->cc.t3t.minorVersion; /* Byte 0 Ver */
|
||
|
checksum += buf[dataIt];
|
||
|
dataIt++;
|
||
|
buf[dataIt] = ctx->cc.t3t.nbR; /* Byte 1 Nbr */
|
||
|
checksum += buf[dataIt];
|
||
|
dataIt++;
|
||
|
buf[dataIt] = ctx->cc.t3t.nbW; /* Byte 2 Nbw */
|
||
|
checksum += buf[dataIt];
|
||
|
dataIt++;
|
||
|
buf[dataIt] = (uint8_t)(ctx->cc.t3t.nMaxB >> 8U); /* Byte 3 NmaxB (MSB) */
|
||
|
checksum += buf[dataIt];
|
||
|
dataIt++;
|
||
|
buf[dataIt] = (uint8_t)(ctx->cc.t3t.nMaxB >> 0U); /* Byte 4 NmaxB (LSB) */
|
||
|
checksum += buf[dataIt];
|
||
|
dataIt++;
|
||
|
buf[dataIt] = 0U; /* Byte 5 RFU */
|
||
|
dataIt++;
|
||
|
buf[dataIt] = 0U; /* Byte 6 RFU */
|
||
|
dataIt++;
|
||
|
buf[dataIt] = 0U; /* Byte 7 RFU */
|
||
|
dataIt++;
|
||
|
buf[dataIt] = 0U; /* Byte 8 RFU */
|
||
|
dataIt++;
|
||
|
buf[dataIt] = ctx->cc.t3t.writeFlag; /* Byte 9 WriteFlag */
|
||
|
checksum += buf[dataIt];
|
||
|
dataIt++;
|
||
|
buf[dataIt] = ctx->cc.t3t.rwFlag; /* Byte 10 RWFlag */
|
||
|
checksum += buf[dataIt];
|
||
|
dataIt++;
|
||
|
buf[dataIt] = (uint8_t)(ctx->cc.t3t.Ln >> 16U); /* Byte 11 Ln (MSB) */
|
||
|
checksum += buf[dataIt];
|
||
|
dataIt++;
|
||
|
buf[dataIt] = (uint8_t)(ctx->cc.t3t.Ln >> 8U); /* Byte 12 Ln (middle) */
|
||
|
checksum += buf[dataIt];
|
||
|
dataIt++;
|
||
|
buf[dataIt] = (uint8_t)(ctx->cc.t3t.Ln >> 0U); /* Byte 13 Ln (LSB) */
|
||
|
checksum += buf[dataIt];
|
||
|
dataIt++;
|
||
|
buf[dataIt] = (uint8_t)(checksum >> 8U); /* Byte 14 checksum MSB */
|
||
|
dataIt++;
|
||
|
buf[dataIt] = (uint8_t)(checksum >> 0U); /* Byte 15 checksum LSB */
|
||
|
dataIt++;
|
||
|
|
||
|
ret = ndefT3TPollerWriteBlocks(ctx, NDEF_T3T_ATTRIB_INFO_BLOCK_NB, 1U /* One block */, buf);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/*******************************************************************************/
|
||
|
ReturnCode ndefT3TPollerWriteRawMessage(ndefContext *ctx, const uint8_t *buf, uint32_t bufLen)
|
||
|
{
|
||
|
ReturnCode ret;
|
||
|
|
||
|
if( (ctx == NULL) || !ndefT3TisT3TDevice(&ctx->device) || ((buf == NULL) && (bufLen != 0U)) )
|
||
|
{
|
||
|
return ERR_PARAM;
|
||
|
}
|
||
|
/* TS T3T v1.0 7.4.3: This procedure assumes that the Reader/Writer has successfully performed the NDEF detection procedure... */
|
||
|
/* Warning: current tag content must not be changed between NDEF Detect procedure and NDEF read procedure*/
|
||
|
|
||
|
/* TS T3T v1.0 7.4.3: ... and that the RWFlag in the Attribute Information Block is set to 01h. */
|
||
|
if ( (ctx->state != NDEF_STATE_INITIALIZED) && (ctx->state != NDEF_STATE_READWRITE) )
|
||
|
{
|
||
|
/* Conclude procedure */
|
||
|
return ERR_WRONG_STATE;
|
||
|
}
|
||
|
|
||
|
/* TS T3T v1.0 7.4.3.2: verify available space */
|
||
|
ret = ndefT3TPollerCheckAvailableSpace(ctx, bufLen);
|
||
|
if( ret != ERR_NONE )
|
||
|
{
|
||
|
/* Conclude procedure */
|
||
|
return ERR_PARAM;
|
||
|
}
|
||
|
|
||
|
/* TS T3T v1.0 7.4.3.3: update WriteFlag */
|
||
|
ret = ndefT3TPollerBeginWriteMessage(ctx, bufLen);
|
||
|
if( ret != ERR_NONE )
|
||
|
{
|
||
|
ctx->state = NDEF_STATE_INVALID;
|
||
|
/* Conclude procedure */
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
if( bufLen != 0U )
|
||
|
{
|
||
|
/* TS T3T v1.0 7.4.3.4: write new NDEF message */
|
||
|
ret = ndefT3TPollerWriteBytes(ctx, ctx->messageOffset, buf, bufLen);
|
||
|
if (ret != ERR_NONE)
|
||
|
{
|
||
|
/* Conclude procedure */
|
||
|
ctx->state = NDEF_STATE_INVALID;
|
||
|
return ret;
|
||
|
}
|
||
|
}
|
||
|
/* TS T3T v1.0 7.4.3.5: update Ln value and set WriteFlag to OFF */
|
||
|
ret = ndefT3TPollerEndWriteMessage(ctx, bufLen);
|
||
|
if( ret != ERR_NONE )
|
||
|
{
|
||
|
/* Conclude procedure */
|
||
|
ctx->state = NDEF_STATE_INVALID;
|
||
|
return ret;
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/*******************************************************************************/
|
||
|
ReturnCode ndefT3TPollerTagFormat(ndefContext *ctx, const ndefCapabilityContainer * cc, uint32_t options)
|
||
|
{
|
||
|
ReturnCode res;
|
||
|
rfalFeliCaPollRes buffOut [NDEF_T3T_MAX_DEVICE];
|
||
|
uint8_t devCnt = NDEF_T3T_MAX_DEVICE;
|
||
|
uint8_t collisions = 0U;
|
||
|
NO_WARNING(options); /* options not used in T3T */
|
||
|
|
||
|
if( (ctx == NULL) || !ndefT3TisT3TDevice(&ctx->device) )
|
||
|
{
|
||
|
return ERR_PARAM;
|
||
|
}
|
||
|
if ( cc == NULL)
|
||
|
{
|
||
|
/* No default CC found so have to analyse the tag */
|
||
|
res = ndefT3TPollerReadAttributeInformationBlock(ctx); /* Read current cc */
|
||
|
if (res != ERR_NONE)
|
||
|
{
|
||
|
return res;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
/* Nothing to do */
|
||
|
(void)ST_MEMCPY(&ctx->cc, cc, sizeof(ndefCapabilityContainer));
|
||
|
}
|
||
|
|
||
|
/* 4.3.3 System Definition Information for SystemCode = 0x12FC (NDEF) */
|
||
|
res = rfalNfcfPollerPoll( RFAL_FELICA_1_SLOT, NDEF_T3T_SYSTEMCODE, (uint8_t)RFAL_FELICA_POLL_RC_NO_REQUEST, buffOut, &devCnt, &collisions );
|
||
|
if (res != ERR_NONE)
|
||
|
{
|
||
|
return res;
|
||
|
}
|
||
|
res = rfalNfcfPollerPoll( RFAL_FELICA_1_SLOT, NDEF_T3T_SYSTEMCODE, (uint8_t)RFAL_FELICA_POLL_RC_SYSTEM_CODE, buffOut, &devCnt, &collisions );
|
||
|
if (res != ERR_NONE)
|
||
|
{
|
||
|
return res;
|
||
|
}
|
||
|
ctx->state = NDEF_STATE_INITIALIZED; /* to be sure that the block will be written */
|
||
|
ctx->cc.t3t.Ln = 0U; /* Force actual stored NDEF size to 0 */
|
||
|
ctx->cc.t3t.writeFlag = 0U; /* Force WriteFlag to 0 */
|
||
|
res = ndefT3TPollerWriteAttributeInformationBlock(ctx);
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
/*******************************************************************************/
|
||
|
ReturnCode ndefT3TPollerCheckPresence(ndefContext *ctx)
|
||
|
{
|
||
|
ReturnCode retcode;
|
||
|
uint16_t nbRead;
|
||
|
|
||
|
if( (ctx == NULL) || !ndefT3TisT3TDevice(&ctx->device) )
|
||
|
{
|
||
|
return ERR_PARAM;
|
||
|
}
|
||
|
/* Perform a simple readblock */
|
||
|
retcode = ndefT3TPollerReadBlocks(ctx, 0U /* First block */, 1U /* One Block */, ctx->subCtx.t3t.rxbuf, NDEF_T3T_BLOCKLEN, &nbRead);
|
||
|
return retcode;
|
||
|
}
|
||
|
|
||
|
/*******************************************************************************/
|
||
|
ReturnCode ndefT3TPollerCheckAvailableSpace(const ndefContext *ctx, uint32_t messageLen)
|
||
|
{
|
||
|
if( (ctx == NULL) || !ndefT3TisT3TDevice(&ctx->device) )
|
||
|
{
|
||
|
return ERR_PARAM;
|
||
|
}
|
||
|
|
||
|
if ( ctx->state == NDEF_STATE_INVALID )
|
||
|
{
|
||
|
return ERR_WRONG_STATE;
|
||
|
}
|
||
|
if( messageLen > ctx->areaLen )
|
||
|
{
|
||
|
return ERR_NOMEM;
|
||
|
}
|
||
|
return ERR_NONE;
|
||
|
}
|
||
|
|
||
|
/*******************************************************************************/
|
||
|
ReturnCode ndefT3TPollerBeginWriteMessage(ndefContext *ctx, uint32_t messageLen)
|
||
|
{
|
||
|
ReturnCode ret;
|
||
|
NO_WARNING(messageLen);
|
||
|
|
||
|
if( (ctx == NULL) || !ndefT3TisT3TDevice(&ctx->device) )
|
||
|
{
|
||
|
return ERR_PARAM;
|
||
|
}
|
||
|
|
||
|
if( (ctx->state != NDEF_STATE_INITIALIZED) && (ctx->state != NDEF_STATE_READWRITE) )
|
||
|
{
|
||
|
return ERR_WRONG_STATE;
|
||
|
}
|
||
|
/* Update WriteFlag */
|
||
|
ctx->cc.t3t.writeFlag = NDEF_T3T_WRITEFLAG_ON;
|
||
|
ret = ndefT3TPollerWriteAttributeInformationBlock(ctx);
|
||
|
if( ret != ERR_NONE )
|
||
|
{
|
||
|
/* Conclude procedure */
|
||
|
ctx->state = NDEF_STATE_INVALID;
|
||
|
return ret;
|
||
|
}
|
||
|
ctx->state = NDEF_STATE_INITIALIZED;
|
||
|
return ERR_NONE;
|
||
|
}
|
||
|
|
||
|
/*******************************************************************************/
|
||
|
ReturnCode ndefT3TPollerEndWriteMessage(ndefContext *ctx, uint32_t messageLen)
|
||
|
{
|
||
|
ReturnCode ret;
|
||
|
|
||
|
if( (ctx == NULL) || !ndefT3TisT3TDevice(&ctx->device) )
|
||
|
{
|
||
|
return ERR_PARAM;
|
||
|
}
|
||
|
if( ctx->state != NDEF_STATE_INITIALIZED )
|
||
|
{
|
||
|
return ERR_WRONG_STATE;
|
||
|
}
|
||
|
/* TS T3T v1.0 7.4.3.5 Update Attribute Information Block */
|
||
|
ctx->cc.t3t.writeFlag = NDEF_T3T_WRITEFLAG_OFF;
|
||
|
ctx->cc.t3t.Ln = messageLen;
|
||
|
ret = ndefT3TPollerWriteAttributeInformationBlock(ctx);
|
||
|
if( ret != ERR_NONE )
|
||
|
{
|
||
|
/* Conclude procedure */
|
||
|
ctx->state = NDEF_STATE_INVALID;
|
||
|
return ret;
|
||
|
}
|
||
|
ctx->messageLen = messageLen;
|
||
|
ctx->state = (ctx->messageLen == 0U) ? NDEF_STATE_INITIALIZED : NDEF_STATE_READWRITE;
|
||
|
return ERR_NONE;
|
||
|
}
|
||
|
|
||
|
/*******************************************************************************/
|
||
|
ReturnCode ndefT3TPollerWriteRawMessageLen(ndefContext *ctx, uint32_t rawMessageLen)
|
||
|
{
|
||
|
if( (ctx == NULL) || !ndefT3TisT3TDevice(&ctx->device) )
|
||
|
{
|
||
|
return ERR_PARAM;
|
||
|
}
|
||
|
|
||
|
if( (ctx->state != NDEF_STATE_INITIALIZED) && (ctx->state != NDEF_STATE_READWRITE) )
|
||
|
{
|
||
|
return ERR_WRONG_STATE;
|
||
|
}
|
||
|
return ndefT3TPollerEndWriteMessage(ctx, rawMessageLen);
|
||
|
}
|
||
|
|
||
|
#endif /* NDEF_FEATURE_ALL */
|
||
|
|
||
|
#endif /* RFAL_FEATURE_NFCF */
|