blob: f623558897d32077dafb2455a909e10708d1da9a [file]
/*
*
* Copyright (c) 2025 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#undef CHIP_NFC_DEBUG_LOGGING_ENABLED
/**
* @file
* Provides an implementation of the NFCCommissioningManager singleton object
* for Android platforms.
*/
#include <stdio.h>
#include <stdlib.h>
#include <platform/internal/CHIPDeviceLayerInternal.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/SafeInt.h>
#include <platform/internal/NFCCommissioningManager.h>
#ifdef __APPLE__
#include <PCSC/winscard.h>
#include <PCSC/wintypes.h>
#else
#include <winscard.h>
#endif
#if CHIP_DEVICE_CONFIG_ENABLE_NFC_BASED_COMMISSIONING
using namespace chip;
using namespace ::nl;
using namespace ::chip::Nfc;
namespace chip {
namespace DeviceLayer {
namespace Internal {
namespace {
inline void AssertMutexIsHeld(const std::mutex & m) CHIP_TSA_ATTRIBUTE__(assert_exclusive_lock(m)) {}
} // namespace
#define MAX_APDU_SIZE 256
#define MAX_MESSAGE_SIZE 1280
#define APDU_HEADER_SIZE_IN_BYTES 7
#define APDU_LE_SIZE_IN_BYTES 3
#define TYPE4_SIMPLE_APDU_MAX_TX_SIZE 245 // NB: 245 is the optimum size to get the biggest TX chunks and thus the max throughput
#define TYPE4_SIMPLE_APDU_MAX_RX_SIZE 250 // NB: 250 is the optimum size to get the biggest RX chunks and thus the max throughput
#ifndef MAX
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#endif
#ifndef MIN
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
#endif
#define CHECK_FOR_SCARD_SUCCESS(f, rv) \
if (SCARD_S_SUCCESS != rv) \
{ \
ChipLogError(DeviceLayer, "%s : %s", f, pcsc_stringify_error(rv)); \
return CHIP_ERROR_INTERNAL; \
}
#ifdef CHIP_NFC_DEBUG_LOGGING_ENABLED
#define ChipLogProgressNfcDebug(module, ...) ChipLogProgress(module, __VA_ARGS__)
#else
#define ChipLogProgressNfcDebug(module, ...) \
do \
{ \
} while (0)
#endif
// This NFC Tag contains all the variables, handles and buffers related to one NFC tag instance
class TagInstance
{
private:
Transport::NFCBase * nfcBase;
Transport::PeerAddress peerAddress;
char * readerName;
SCARDHANDLE cardHandle;
const SCARD_IO_REQUEST * pioSendPci;
uint16_t discriminator;
// Buffer containing a single APDU command to send to the tag
uint8_t mAPDUTxBuffer[MAX_APDU_SIZE];
// Buffer used to receive the response to an APDU command
uint8_t mAPDURxBuffer[MAX_APDU_SIZE];
uint32_t mAPDUResponseLength = 0;
// Buffer storing the chained APDU messages received from the tag
uint8_t mChainedResponseBuffer[MAX_MESSAGE_SIZE];
uint32_t mChainedResponseLength = 0;
public:
// `handle` ownership is transferred to TagInstance
// TagInstance's destructor will release it via SCardDisconnect()
TagInstance(Transport::NFCBase * base, const char * name, SCARDHANDLE handle, const SCARD_IO_REQUEST * sendPci) :
nfcBase(base), peerAddress(), // peerAddress will be filled when 'discriminator' is set
cardHandle(handle), pioSendPci(sendPci)
{
ChipLogProgress(DeviceLayer, "New TagInstance with cardHandle 0x" ChipLogFormatX64, ChipLogValueX64(cardHandle));
readerName = strdup(name); // Allocate memory and copy the string
discriminator = 0; // Will be retrieved when RetrieveDiscriminator() is called
memset(mAPDUTxBuffer, 0, sizeof(mAPDUTxBuffer));
memset(mAPDURxBuffer, 0, sizeof(mAPDURxBuffer));
memset(mChainedResponseBuffer, 0, sizeof(mChainedResponseBuffer));
}
// TagInstance is non-copyable and non-movable.
// Copying would duplicate ownership of resources such as `cardHandle` and `readerName`.
// Moving is disallowed because TagInstance objects are shared asynchronously by pointer;
// queued work may still access a given instance, and allowing the object itself to be
// moved would leave that instance in a moved-from state.
TagInstance(const TagInstance &) = delete;
TagInstance & operator=(const TagInstance &) = delete;
TagInstance(TagInstance &&) = delete;
TagInstance & operator=(TagInstance &&) = delete;
~TagInstance()
{
ChipLogProgressNfcDebug(DeviceLayer, "Destructor of cardHandle: 0x" ChipLogFormatX64, ChipLogValueX64(cardHandle));
if (cardHandle != 0)
{
SCardDisconnect(cardHandle, SCARD_LEAVE_CARD);
cardHandle = 0;
}
if (readerName != nullptr)
{
free(readerName);
readerName = nullptr;
}
}
void Print() const
{
ChipLogProgressNfcDebug(DeviceLayer, "TagInstance %p", this);
ChipLogProgressNfcDebug(DeviceLayer, "cardHandle 0x" ChipLogFormatX64, ChipLogValueX64(cardHandle));
}
const char * GetReaderName() const { return readerName; }
SCARDHANDLE GetCardHandle() const { return cardHandle; }
uint16_t GetDiscriminator() const { return discriminator; }
// SW1=0x90 SW2=0x00 indicate a successful command
bool IsStatusSuccess(uint8_t sw1, uint8_t sw2)
{
return ((sw1 == static_cast<uint8_t>(0x90)) && (sw2 == static_cast<uint8_t>(0x00)));
}
// SW1=0x61 SW2=0xXX indicate that the last command was successful and that the tag is transmitting
// a chained response. It is used when the response is too long to be transmitted in one shot.
// SW2 indicates the size of the data in the next block. It can be read by calling
// 'GetResponse' command.
bool IsResponseBlockAvailable(uint8_t sw1) { return (sw1 == static_cast<uint8_t>(0x61)); }
CHIP_ERROR RetrieveDiscriminator()
{
// Discriminator can be found in the response to select application
CHIP_ERROR res = SelectMatterApplet();
return res;
}
// Function sending a message to an NFC Tag.
// If the data doesn't fit in a single APDU, it will be split in
// several chained APDUs.
//
// Same thing for the tag's response:
// If the response doesn't fit in 255 bytes, it will be chained.
//
// When the tag's response is fully received, OnNfcTagResponse() notification
// will be called.
void SendChainedAPDUs(ByteSpan dataToSend)
{
CHIP_ERROR res;
uint32_t totalLength = static_cast<uint32_t>(dataToSend.size());
uint32_t nbrOfBytesRemaining = static_cast<uint32_t>(dataToSend.size());
const uint8_t * pNextDataToSend = dataToSend.data();
ChipLogProgressNfcDebug(DeviceLayer, "SendChainedAPDUs cardHandle: 0x" ChipLogFormatX64, ChipLogValueX64(cardHandle));
while (nbrOfBytesRemaining > 0)
{
ChipLogProgressNfcDebug(DeviceLayer, "Sending chained APDU");
// Size of the next APDU
uint32_t nbrOfBytesToSend = MIN(nbrOfBytesRemaining, TYPE4_SIMPLE_APDU_MAX_TX_SIZE);
bool isLastBlock = (nbrOfBytesToSend == nbrOfBytesRemaining);
mAPDUResponseLength = sizeof(mAPDURxBuffer); // Initialized with with mAPDURxBuffer size to indicate to the low level
// driver the capacity of the RX buffer
res =
SendTransportAPDU(pNextDataToSend, nbrOfBytesToSend, isLastBlock, totalLength, mAPDURxBuffer, &mAPDUResponseLength);
if ((res != CHIP_NO_ERROR) || (mAPDUResponseLength < 2))
{
ProcessError("Invalid NFC Type4 response");
return;
}
uint8_t sw1 = mAPDURxBuffer[mAPDUResponseLength - 2];
uint8_t sw2 = mAPDURxBuffer[mAPDUResponseLength - 1];
if (isLastBlock)
{
// The last block has been sent.
// The possible response are:
// SW1=0x90 SW2=0x00 if the transmission was successful and the recipient has
// transmitted a short response (less than 256 bytes).
// SW1=0x61 SW2=0xXX if the transmission was successful and the recipient has
// transmitted the first part of a chained response (SW2 indicates the size of
// the data in the next block).
if (IsStatusSuccess(sw1, sw2) || IsResponseBlockAvailable(sw1))
{
// Command successful. Response will be processed outside of the while loop
}
else
{
// Any others values are an error
PrintSw1Sw2(sw1, sw2);
ProcessError("Error during chained APDUs");
return;
}
}
else
{
// This is an intermediate block so the only valid response is 0x90 0x00
if (IsStatusSuccess(sw1, sw2))
{
// The command was successfully sent
// Continue with the next block
}
else
{
PrintSw1Sw2(sw1, sw2);
ProcessError("Error during chained APDUs");
return;
}
}
// APDU sent successfully
nbrOfBytesRemaining -= nbrOfBytesToSend;
pNextDataToSend += nbrOfBytesToSend;
}
if (nbrOfBytesRemaining != 0)
{
ProcessError("Error during APDUs transmission!");
return;
}
// The command has been successfully sent and we have received the first response block in mAPDURxBuffer.
// Process it and read the next blocks (if any)
ProcessAPDUResponse();
}
// Process an APDU response contained in mAPDURxBuffer
void ProcessAPDUResponse()
{
ChipLogProgressNfcDebug(DeviceLayer, "Processing APDU response");
CHIP_ERROR res;
// mAPDURxBuffer should contain at least the 2 status bytes
if (mAPDUResponseLength < 2)
{
ProcessError("Error! Invalid Type4 response");
return;
}
ResetChainedResponseBuffer();
uint8_t sw1 = mAPDURxBuffer[mAPDUResponseLength - 2];
uint8_t sw2 = mAPDURxBuffer[mAPDUResponseLength - 1];
if (IsStatusSuccess(sw1, sw2))
{
// Response fits in a single block.
// Drop the 2 status bytes and return it
NotifyResponse(mAPDURxBuffer, mAPDUResponseLength - 2);
return;
}
else if (IsResponseBlockAvailable(sw1))
{
// SW1=0x61 indicates a chained response. it means that the response is too big to be transmitted in a single packet.
// Put the received bytes (without the 2 status bytes) into the ChainedResponseBuffer
res = AddDataToChainedResponseBuffer(mAPDURxBuffer, mAPDUResponseLength - 2);
if (res != CHIP_NO_ERROR)
{
ProcessError("Too many data!");
return;
}
// Loop until there is no more response block to read
// SW2 indicates the size of the data in the next response block.
// It can be read thanks to a call to getResponse() command.
while (IsResponseBlockAvailable(sw1))
{
// If SW2 is 0x00 or if it is higher than TYPE4_SIMPLE_APDU_MAX_RX_SIZE, we clamp it to
// TYPE4_SIMPLE_APDU_MAX_RX_SIZE.
uint8_t nextBlockLength = ((sw2 == 0x00) || (static_cast<int>(sw2) > TYPE4_SIMPLE_APDU_MAX_RX_SIZE))
? static_cast<uint8_t>(TYPE4_SIMPLE_APDU_MAX_RX_SIZE)
: sw2;
mAPDUResponseLength = sizeof(mAPDURxBuffer); // Initialized with with mAPDURxBuffer size to indicate to the low
// level driver the capacity of the RX buffer
// Response will be written into mAPDURxBuffer
res = GetResponse(nextBlockLength, mAPDURxBuffer, &mAPDUResponseLength);
if ((res != CHIP_NO_ERROR) || (mAPDUResponseLength < 2))
{
ProcessError("Invalid NFC Type4 response");
return;
}
sw1 = mAPDURxBuffer[mAPDUResponseLength - 2];
sw2 = mAPDURxBuffer[mAPDUResponseLength - 1];
if (IsStatusSuccess(sw1, sw2))
{
// Put the received bytes (without the 2 status bytes) into the ChainedResponseBuffer
res = AddDataToChainedResponseBuffer(mAPDURxBuffer, mAPDUResponseLength - 2);
if (res != CHIP_NO_ERROR)
{
ProcessError("Too many data!");
return;
}
}
else if (IsResponseBlockAvailable(sw1))
{
// We have successfully received a block and it is not the last one
// Put the received bytes (without the 2 status bytes) into the ChainedResponseBuffer
res = AddDataToChainedResponseBuffer(mAPDURxBuffer, mAPDUResponseLength - 2);
if (res != CHIP_NO_ERROR)
{
ProcessError("Too many data!");
return;
}
}
else
{
PrintSw1Sw2(sw1, sw2);
ProcessError("Error! Invalid APDU response");
return;
}
// If SW1 still equals to 0x61, continue to read the next response block
}
// Chained response received successfully
NotifyResponse(mChainedResponseBuffer, mChainedResponseLength);
return;
}
else
{
PrintSw1Sw2(sw1, sw2);
ProcessError("Error! Invalid APDU response");
return;
}
}
CHIP_ERROR SelectMatterApplet()
{
ChipLogProgressNfcDebug(DeviceLayer, "Select Matter NFC applet");
CHIP_ERROR res;
VerifyOrReturnLogError(cardHandle != 0, CHIP_ERROR_INTERNAL);
BYTE dataReceived[10];
DWORD receivedLength = sizeof(dataReceived);
BYTE select_matter_applet[] = {
0x00, 0xA4, 0x04, 0x0C, 0x09, // CLA INS P1 P2 Lc
0xA0, 0x00, 0x00, 0x09, 0x09, 0x8A, 0x77, 0xE4, 0x01, // Matter AID
0x00 // Le
};
LONG result = SCardTransmit(cardHandle, pioSendPci, select_matter_applet, sizeof(select_matter_applet), NULL, dataReceived,
&receivedLength);
if (result == SCARD_S_SUCCESS)
{
if (receivedLength >= 10)
{
discriminator = static_cast<uint16_t>(dataReceived[2] * 256 + dataReceived[3]);
ChipLogProgressNfcDebug(DeviceLayer, "Tag discriminator: %d", discriminator);
peerAddress = Transport::PeerAddress::NFC(discriminator);
res = CHIP_NO_ERROR;
}
else
{
ChipLogError(DeviceLayer, "Response to Select Matter AID cmd is too small (%d bytes)",
static_cast<unsigned int>(receivedLength));
res = CHIP_ERROR_INTERNAL;
}
}
else
{
res = CHIP_ERROR_INTERNAL;
}
return res;
}
void PrintSw1Sw2(uint8_t sw1, uint8_t sw2) { ChipLogProgress(DeviceLayer, "SW1=0x%x SW2=0x%x", sw1, sw2); }
void ProcessError(const char * msg)
{
ChipLogError(DeviceLayer, "%s", msg);
TEMPORARY_RETURN_IGNORED SendOnNfcTagError();
}
void NotifyResponse(uint8_t * response, uint32_t responseLen)
{
System::PacketBufferHandle buffer =
System::PacketBufferHandle::NewWithData(reinterpret_cast<const uint8_t *>(response), static_cast<size_t>(responseLen));
TEMPORARY_RETURN_IGNORED SendOnNfcTagResponse(std::move(buffer));
}
void ResetChainedResponseBuffer(void)
{
ChipLogProgressNfcDebug(DeviceLayer, "ResetChainedResponseBuffer()");
mChainedResponseLength = 0;
}
CHIP_ERROR AddDataToChainedResponseBuffer(uint8_t * data, uint32_t dataLen)
{
ChipLogProgressNfcDebug(DeviceLayer, "Add %d bytes to chainedResponseBuffer", dataLen);
// Check that mChainedResponseBuffer will not overflow
VerifyOrReturnLogError((mChainedResponseLength + dataLen) <= sizeof(mChainedResponseBuffer), CHIP_ERROR_MESSAGE_TOO_LONG);
memcpy(&(mChainedResponseBuffer[mChainedResponseLength]), data, dataLen);
mChainedResponseLength += dataLen;
return CHIP_NO_ERROR;
}
/////////////////////////////////////////////////////////////////
CHIP_ERROR SendTransportAPDU(const uint8_t * dataToSend, uint32_t dataToSendLength, bool isLastBlock, uint32_t totalLength,
uint8_t * pRcvBuffer, uint32_t * pRcvLength)
{
VerifyOrReturnLogError(dataToSendLength <= sizeof(mAPDUTxBuffer), CHIP_ERROR_INTERNAL);
mAPDUTxBuffer[0] = isLastBlock ? static_cast<uint8_t>(0x80) : static_cast<uint8_t>(0x90); // CLA
mAPDUTxBuffer[1] = 0x20; // INS
mAPDUTxBuffer[2] = static_cast<uint8_t>((totalLength >> 8) & 0xFF); // P1 (contains the totalLength's MSB)
mAPDUTxBuffer[3] = static_cast<uint8_t>(totalLength & 0xFF); // P2 (contains the totalLength's LSB)
mAPDUTxBuffer[4] = static_cast<uint8_t>(dataToSendLength); // Lc
memcpy(&(mAPDUTxBuffer[5]), dataToSend, dataToSendLength);
mAPDUTxBuffer[5 + dataToSendLength] = static_cast<uint8_t>(TYPE4_SIMPLE_APDU_MAX_RX_SIZE); // Le
uint32_t apduLength = 6 + dataToSendLength;
CHIP_ERROR result = Transceive("SendTransportAPDU", mAPDUTxBuffer, apduLength, pRcvBuffer, pRcvLength);
return result;
}
CHIP_ERROR GetResponse(uint8_t length, uint8_t * pRcvBuffer, uint32_t * pRcvLength)
{
uint8_t frame[5];
frame[0] = 0x00; // CLA
frame[1] = static_cast<uint8_t>(0xC0); // INS
frame[2] = 0x00; // P1
frame[3] = 0x00; // P2
frame[4] = length; // Le
CHIP_ERROR result = Transceive("GetResponse", frame, sizeof(frame), pRcvBuffer, pRcvLength);
return result;
}
CHIP_ERROR Transceive(const char * commandName, uint8_t * pSendBuffer, uint32_t sendBufferLength, uint8_t * pRcvBuffer,
uint32_t * pRcvLength)
{
CHIP_ERROR ret;
// Use a local 'dwRecvLength' variable to avoid cast of pRcvLength from 'uint32_t *' to 'DWORD *'.
// Before the transceive action, those variables contain the size of pRcvBuffer buffer.
// After the transceive, they will contain the length of the received data.
DWORD dwRecvLength = static_cast<DWORD>(*pRcvLength);
LONG result = SCardTransmit(cardHandle, pioSendPci, pSendBuffer, sendBufferLength, NULL, pRcvBuffer, &dwRecvLength);
if ((result == SCARD_S_SUCCESS) && (dwRecvLength >= 2))
{
// Copy the length of received data to pRcvLength
*pRcvLength = static_cast<uint32_t>(dwRecvLength);
ret = CHIP_NO_ERROR;
}
else
{
ChipLogError(DeviceLayer, "SCardTransmit failed with error 0x" ChipLogFormatX64, ChipLogValueX64(result));
ret = CHIP_ERROR_INTERNAL;
}
return ret;
}
/////////////////////////////////////////////////////////////////
struct NfcResponseContext
{
Transport::NFCBase * nfcBase;
Transport::PeerAddress peerAddress;
System::PacketBufferHandle buffer;
};
static void DispatchNfcTagResponse(intptr_t arg)
{
ChipLogProgressNfcDebug(DeviceLayer, "Dispatching NFC tag's response");
auto * ctx = reinterpret_cast<NfcResponseContext *>(arg);
if (ctx->nfcBase != nullptr)
{
ctx->nfcBase->OnNfcTagResponse(ctx->peerAddress, std::move(ctx->buffer));
}
delete ctx;
}
static void DispatchNfcTagError(intptr_t arg)
{
ChipLogProgressNfcDebug(DeviceLayer, "Dispatching error from received NFC tag's response");
auto * ctx = reinterpret_cast<NfcResponseContext *>(arg);
if (ctx->nfcBase != nullptr)
{
ctx->nfcBase->OnNfcTagError(ctx->peerAddress);
}
delete ctx;
}
CHIP_ERROR SendOnNfcTagResponse(System::PacketBufferHandle && buffer)
{
auto * ctx = new (std::nothrow) NfcResponseContext();
VerifyOrReturnError(ctx != nullptr, CHIP_ERROR_NO_MEMORY);
ctx->nfcBase = nfcBase;
ctx->peerAddress = peerAddress;
ctx->buffer = std::move(buffer);
CHIP_ERROR err = DeviceLayer::PlatformMgr().ScheduleWork(DispatchNfcTagResponse, reinterpret_cast<intptr_t>(ctx));
if (err != CHIP_NO_ERROR)
{
ChipLogError(DeviceLayer, "Couldn't schedule dispatch of NFC tag response");
delete ctx;
}
return err;
}
CHIP_ERROR SendOnNfcTagError()
{
auto * ctx = new (std::nothrow) NfcResponseContext();
VerifyOrReturnError(ctx != nullptr, CHIP_ERROR_NO_MEMORY);
ctx->nfcBase = nfcBase;
ctx->peerAddress = peerAddress;
CHIP_ERROR err = DeviceLayer::PlatformMgr().ScheduleWork(DispatchNfcTagError, reinterpret_cast<intptr_t>(ctx));
if (err != CHIP_NO_ERROR)
{
delete ctx;
}
return err;
}
};
///////////////////////////////////////////////////////////////////////////////////////////////
struct TagDiscoveryContext
{
NFCCommissioningManagerImpl * manager;
Nfc::NFCTag::Identifier foundId;
};
void NFCCommissioningManagerImpl::DispatchTagDiscovery(intptr_t arg)
{
auto * ctx = reinterpret_cast<TagDiscoveryContext *>(arg);
Nfc::NFCReaderTransportDelegate * delegate = nullptr;
{
std::lock_guard<std::mutex> lock(ctx->manager->mStateMutex);
delegate = ctx->manager->mDelegate;
}
if (delegate != nullptr)
{
delegate->OnTagDiscovered(ctx->foundId);
}
delete ctx;
}
Global<NFCCommissioningManagerImpl> NFCCommissioningManagerImpl::sInstance;
// ===== start impl of NFCCommissioningManager internal interface, ref NFCCommissioningManager.h
CHIP_ERROR NFCCommissioningManagerImpl::_Init()
{
// NFC processing Thread will be started with the first request to do NFC-based commissioning
ChipLogProgressNfcDebug(DeviceLayer, "NFCCommissioningManagerImpl::_Init()");
return CHIP_NO_ERROR;
}
void NFCCommissioningManagerImpl::_Shutdown()
{
ChipLogProgressNfcDebug(DeviceLayer, "NFCCommissioningManagerImpl::_Shutdown()");
std::queue<NfcWorkItem> droppedWorkItems;
// Stop the NFC worker thread
{
std::lock_guard<std::mutex> lock(mWorkQueueMutex);
mShuttingDown = true;
mNfcWorkerThreadRunning = false;
// Discard all pending work immediately.
mWorkQueue.swap(droppedWorkItems);
}
mWorkQueueCondition.notify_one();
// Work items present in the queue are going to be dropped without being processed
// by the worker thread. Complete them with CHIP_ERROR_SHUT_DOWN so waiting
// callers are unblocked
while (!droppedWorkItems.empty())
{
NfcWorkItem & item = droppedWorkItems.front();
if (item.syncCtx != nullptr)
{
{
std::lock_guard<std::mutex> lock(item.syncCtx->mutex);
item.syncCtx->result = CHIP_ERROR_SHUT_DOWN;
item.syncCtx->done = true;
}
item.syncCtx->cv.notify_one();
}
droppedWorkItems.pop();
}
if (mNfcWorkerThread.joinable())
{
mNfcWorkerThread.join();
}
{
std::lock_guard<std::mutex> lock(mStateMutex);
mLastTagInstanceUsed = nullptr;
mTagInstances.clear();
}
}
// ===== start implement virtual methods on NfcApplicationDelegate.
CHIP_ERROR NFCCommissioningManagerImpl::EnsureWorkerThreadStarted()
{
{
std::lock_guard<std::mutex> lock(mWorkQueueMutex);
if (!mNfcWorkerThreadRunning)
{
if (mNfcWorkerThread.joinable())
{
// Thread terminated but still joinable. Terminate it before creating a new Thread.
mNfcWorkerThread.join();
}
{
std::lock_guard<std::mutex> initLock(mWorkerInitMutex);
mWorkerInitCompleted = false;
mWorkerInitResult = CHIP_ERROR_INCORRECT_STATE;
}
mShuttingDown = false;
mNfcWorkerThreadRunning = true;
mNfcWorkerThread = std::thread(&NFCCommissioningManagerImpl::NfcWorkerThreadMain, this);
}
}
std::unique_lock<std::mutex> initLock(mWorkerInitMutex);
mWorkerInitCondition.wait(initLock, [this]() CHIP_REQUIRES(mWorkerInitMutex) { return mWorkerInitCompleted; });
// we know this is locked by the above, we did not unlock the initLock
AssertMutexIsHeld(mWorkerInitMutex);
return mWorkerInitResult;
}
CHIP_ERROR NFCCommissioningManagerImpl::RunSyncOnWorker(NfcWorkItem && item)
{
ReturnErrorOnFailure(EnsureWorkerThreadStarted());
SyncWorkContext syncCtx;
item.syncCtx = &syncCtx;
ReturnErrorOnFailure(EnqueueWork(std::move(item)));
std::unique_lock<std::mutex> lock(syncCtx.mutex);
syncCtx.cv.wait(lock, [&syncCtx]() { return syncCtx.done; });
return syncCtx.result;
}
Transport::NFCBase * NFCCommissioningManagerImpl::GetNFCBase()
{
std::lock_guard<std::mutex> lock(mStateMutex);
return mNFCBase;
}
void NFCCommissioningManagerImpl::SetNFCBase(Transport::NFCBase * nfcBase)
{
std::lock_guard<std::mutex> lock(mStateMutex);
mNFCBase = nfcBase;
}
bool NFCCommissioningManagerImpl::CanSendToPeer(const Transport::PeerAddress & address)
{
CHIP_ERROR err = EnsureWorkerThreadStarted();
if (err != CHIP_NO_ERROR)
{
ChipLogError(DeviceLayer, "Failed to start NFC worker Thread: %" CHIP_ERROR_FORMAT, err.Format());
return false;
}
// nfcShortId is used to find the peer device
uint16_t nfcShortId = address.GetNFCShortId();
ChipLogProgress(DeviceLayer, "CanSendToPeer? nfcShortId = %d", nfcShortId);
{
std::lock_guard<std::mutex> lock(mStateMutex);
// Check if mLastTagInstanceUsed corresponds to the same nfcShortId
if ((mLastTagInstanceUsed != nullptr) && (mLastTagInstanceUsed->GetDiscriminator() == nfcShortId))
{
return true;
}
// Check if we already have a TagInstance corresponding to this nfcShortId
std::shared_ptr<TagInstance> tagInstance = SearchTagInstanceFromDiscriminator(nfcShortId);
if (tagInstance != nullptr)
{
// Found a matching cardHandle
mLastTagInstanceUsed = tagInstance;
return true;
}
}
// We don't have yet a cardHandle for this nfcShortId.
// Execute a synchronous scan on the NFC worker thread.
NfcWorkItem item;
item.type = NfcWorkType::kScan;
item.targetIdentifier = {};
err = RunSyncOnWorker(std::move(item));
if (err != CHIP_NO_ERROR)
{
ChipLogError(DeviceLayer, "Synchronous NFC scan failed: %" CHIP_ERROR_FORMAT, err.Format());
return false;
}
{
std::lock_guard<std::mutex> lock(mStateMutex);
// Check again if we now have a TagInstance corresponding to this nfcShortId
std::shared_ptr<TagInstance> tagInstance = SearchTagInstanceFromDiscriminator(nfcShortId);
if (tagInstance != nullptr)
{
ChipLogProgress(DeviceLayer, "tagInstance with discr %d found", nfcShortId);
mLastTagInstanceUsed = tagInstance;
return true;
}
}
ChipLogProgress(DeviceLayer, "No tagInstance found with discr %d!", nfcShortId);
return false;
}
CHIP_ERROR NFCCommissioningManagerImpl::EnqueueWork(NfcWorkItem && item)
{
{
std::lock_guard<std::mutex> lock(mWorkQueueMutex);
VerifyOrReturnError(!mShuttingDown, CHIP_ERROR_SHUT_DOWN);
VerifyOrReturnError(mNfcWorkerThreadRunning, CHIP_ERROR_INCORRECT_STATE);
mWorkQueue.push(std::move(item));
}
mWorkQueueCondition.notify_one();
return CHIP_NO_ERROR;
}
CHIP_ERROR NFCCommissioningManagerImpl::SendToNfcTag(const Transport::PeerAddress & address, System::PacketBufferHandle && msgBuf)
{
ChipLogProgressNfcDebug(DeviceLayer, "NFCCommissioningManagerImpl::SendToNfcTag");
std::shared_ptr<TagInstance> targetedTagInstance = nullptr;
ReturnErrorOnFailure(EnsureWorkerThreadStarted());
// nfcShortId is used to find the peer device
uint16_t nfcShortId = address.GetNFCShortId();
{
std::lock_guard<std::mutex> lock(mStateMutex);
// Check if mLastTagInstanceUsed corresponds to the same nfcShortId
if ((mLastTagInstanceUsed != nullptr) && (mLastTagInstanceUsed->GetDiscriminator() == nfcShortId))
{
targetedTagInstance = mLastTagInstanceUsed;
}
else
{
// Search in all the TagInstance if there is one with this Discriminator
std::shared_ptr<TagInstance> tagInstance = SearchTagInstanceFromDiscriminator(nfcShortId);
if (tagInstance != nullptr)
{
// Found an instance with expected Discriminator
targetedTagInstance = tagInstance;
mLastTagInstanceUsed = tagInstance;
}
}
}
VerifyOrReturnLogError(targetedTagInstance != nullptr, CHIP_ERROR_PEER_NODE_NOT_FOUND);
// Create the NFCMessage as a unique_ptr
auto nfcMessage = std::make_unique<NFCMessage>(targetedTagInstance, std::move(msgBuf));
VerifyOrReturnLogError(nfcMessage->IsMessageValid(), CHIP_ERROR_NO_MEMORY);
// Enqueue the message for processing in the NFC worker thread
NfcWorkItem item;
item.type = NfcWorkType::kSend;
item.message = std::move(nfcMessage);
ReturnErrorOnFailure(EnqueueWork(std::move(item)));
return CHIP_NO_ERROR;
}
void NFCCommissioningManagerImpl::NfcWorkerThreadMain()
{
ChipLogProgressNfcDebug(DeviceLayer, "Start NFC Worker Thread");
// Creates an Application Context to the PC/SC Resource Manager.
LONG result = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, &mPcscContext);
{
std::lock_guard<std::mutex> lock(mWorkerInitMutex);
if (result == SCARD_S_SUCCESS)
{
mWorkerInitResult = CHIP_NO_ERROR;
}
else
{
ChipLogError(DeviceLayer, "SCardEstablishContext failed: %s", pcsc_stringify_error(result));
mWorkerInitResult = CHIP_ERROR_INTERNAL;
{
std::lock_guard<std::mutex> queueLock(mWorkQueueMutex);
mNfcWorkerThreadRunning = false;
}
}
mWorkerInitCompleted = true;
}
mWorkerInitCondition.notify_all();
if (result != SCARD_S_SUCCESS)
{
return;
}
while (true)
{
NfcWorkItem item;
CHIP_ERROR itemResult = CHIP_NO_ERROR;
// Wait for a work item to process
{
std::unique_lock<std::mutex> lock(mWorkQueueMutex);
mWorkQueueCondition.wait(
lock, [this]() CHIP_REQUIRES(mWorkQueueMutex) { return !mWorkQueue.empty() || !mNfcWorkerThreadRunning; });
// we do not manually unlock it
AssertMutexIsHeld(mWorkQueueMutex);
// Exit immediately once the worker is stopped.
if (!mNfcWorkerThreadRunning)
{
break;
}
// Retrieve the work item from the queue
item = std::move(mWorkQueue.front());
mWorkQueue.pop();
}
if (item.type == NfcWorkType::kScan)
{
itemResult = ProcessScanWorkItem(item);
}
else if (item.type == NfcWorkType::kSend)
{
itemResult = ProcessSendWorkItem(item);
}
// Notify the caller if this work item was submitted synchronously.
if (item.syncCtx != nullptr)
{
{
std::lock_guard<std::mutex> lock(item.syncCtx->mutex);
item.syncCtx->result = itemResult;
item.syncCtx->done = true;
}
item.syncCtx->cv.notify_one();
}
}
// Release all TagInstances before releasing the PC/SC context.
{
std::lock_guard<std::mutex> lock(mStateMutex);
mLastTagInstanceUsed = nullptr;
mTagInstances.clear();
}
if (mPcscContext != 0)
{
SCardReleaseContext(mPcscContext);
mPcscContext = 0;
}
}
// Scan all the readers and tags, and search one with 'item.targetIdentifier.discriminator'
CHIP_ERROR NFCCommissioningManagerImpl::ProcessScanWorkItem(const NfcWorkItem & item)
{
ChipLogProgressNfcDebug(DeviceLayer, "Scan all the readers and tags");
CHIP_ERROR scanResult = ScanAllReaders();
// If no specific target was requested, this is just a cache refresh scan.
if (!item.targetIdentifier.IsValid())
{
ChipLogProgressNfcDebug(DeviceLayer, "scanResult available. No specific discriminator requested. Returning scanResult");
return scanResult;
}
ChipLogProgressNfcDebug(DeviceLayer, "scanResult available. Now searching for a specific discriminator: %d",
item.targetIdentifier.discriminator);
std::shared_ptr<TagInstance> tagInstance;
{
std::lock_guard<std::mutex> lock(mStateMutex);
tagInstance = SearchTagInstanceFromDiscriminator(item.targetIdentifier.discriminator);
}
if (tagInstance == nullptr)
{
ChipLogProgressNfcDebug(DeviceLayer, "Tag NOT found!");
return scanResult;
}
ChipLogProgressNfcDebug(DeviceLayer, "Tag found");
auto * ctx = new (std::nothrow) TagDiscoveryContext();
if (ctx == nullptr)
{
ChipLogError(DeviceLayer, "Failed to allocate TagDiscoveryContext");
return scanResult;
}
ctx->manager = this;
ctx->foundId = item.targetIdentifier;
CHIP_ERROR err = DeviceLayer::PlatformMgr().ScheduleWork(DispatchTagDiscovery, reinterpret_cast<intptr_t>(ctx));
if (err != CHIP_NO_ERROR)
{
ChipLogError(DeviceLayer, "Failed to schedule tag discovery dispatch: %" CHIP_ERROR_FORMAT, err.Format());
delete ctx;
}
return scanResult;
}
CHIP_ERROR NFCCommissioningManagerImpl::ProcessSendWorkItem(const NfcWorkItem & item)
{
if (!item.message)
{
ChipLogError(DeviceLayer, "Null NFCMessage detected. Skipping");
return CHIP_ERROR_INVALID_ARGUMENT;
}
// Process the message
ChipLogProgressNfcDebug(DeviceLayer, "Sending the next NFCMessage from the queue");
std::shared_ptr<TagInstance> tagInstance = item.message->GetTagInstance();
if (tagInstance == nullptr)
{
ChipLogError(DeviceLayer, "Invalid TagInstance detected. Discarding message");
return CHIP_ERROR_INCORRECT_STATE;
}
// Send the data to the NFC tag
ByteSpan dataToSend = item.message->GetDataToSend();
tagInstance->SendChainedAPDUs(dataToSend);
return CHIP_NO_ERROR;
}
// Start scan on all available readers and scan for NFC Tags.
CHIP_ERROR NFCCommissioningManagerImpl::ScanAllReaders()
{
LONG result;
LPTSTR mszReaders; // LPTSTR is a "typedef char *"
DWORD dwReaders;
ChipLogProgressNfcDebug(DeviceLayer, "Scan all NFC Readers");
result = SCardListReaders(mPcscContext, NULL, NULL, &dwReaders);
CHECK_FOR_SCARD_SUCCESS("SCardListReaders", result)
// dwReaders now contains "mszReaders" data size
// Allocate a buffer where we will store "mszReaders" multi-string.
mszReaders = static_cast<LPTSTR>(calloc(dwReaders, sizeof(char)));
if (mszReaders == nullptr)
{
ChipLogError(DeviceLayer, "Memory allocation failed");
return CHIP_ERROR_NO_MEMORY;
}
result = SCardListReaders(mPcscContext, NULL, mszReaders, &dwReaders);
if (result != SCARD_S_SUCCESS)
{
free(mszReaders);
CHECK_FOR_SCARD_SUCCESS("SCardListReaders", result)
}
// "mszReaders" contains a multi-string with list of readers.
// Each reader name is separated by a null character ('\0') and ended by a double null character
// Example : "Reader1\0Reader2\0\0"
// Pointer to traverse the string
char * reader = mszReaders;
// Loop through the string
while (*reader != '\0' || *(reader + 1) != '\0')
{
if (*reader != '\0')
{
LogErrorOnFailure(ScanReader(reader));
// Move the pointer to the next substring
reader += strlen(reader) + 1;
}
else
{
// Move the pointer to the next character
reader++;
}
}
free(mszReaders);
return CHIP_NO_ERROR;
}
// Start scan on a given reader
CHIP_ERROR NFCCommissioningManagerImpl::ScanReader(char * readerName)
{
SCARDHANDLE cardHandle;
DWORD dwActiveProtocol;
std::shared_ptr<TagInstance> tagInstance = nullptr;
ChipLogProgressNfcDebug(DeviceLayer, "Scan NFC Reader %s", readerName);
Transport::NFCBase * nfcBase = GetNFCBase();
VerifyOrReturnLogError(nfcBase != nullptr, CHIP_ERROR_INCORRECT_STATE);
// Before launching a new scan of a reader, we should discard all the saved instances using this readerName
{
std::lock_guard<std::mutex> lock(mStateMutex);
EraseAllTagInstancesUsingReaderName(readerName);
}
LONG result = SCardConnect(mPcscContext, readerName, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1, &cardHandle,
&dwActiveProtocol);
switch (result)
{
case static_cast<LONG>(SCARD_S_SUCCESS): {
ChipLogProgressNfcDebug(DeviceLayer, "SCardConnect succeeded for reader %s, cardHandle: 0x" ChipLogFormatX64, readerName,
ChipLogValueX64(cardHandle));
// Check if we already have this couple (readerName, cardHandle)
{
std::lock_guard<std::mutex> lock(mStateMutex);
tagInstance = SearchTagInstanceFromReaderNameAndCardHandle(readerName, cardHandle);
}
if (tagInstance != nullptr)
{
// This couple (readerName, cardHandle) is already known: Nothing else to do
}
else
{
// This couple (readerName, cardHandle) is not known yet: Create a new TagInstance
// `cardHandle` ownership is transferred to TagInstance.
// TagInstance's destructor will release it via SCardDisconnect()
auto newTagInstance = std::make_shared<TagInstance>(
nfcBase, readerName, cardHandle, (dwActiveProtocol == SCARD_PROTOCOL_T0) ? SCARD_PCI_T0 : SCARD_PCI_T1);
CHIP_ERROR err = newTagInstance->RetrieveDiscriminator();
if (err != CHIP_NO_ERROR)
{
return err;
}
{
std::lock_guard<std::mutex> lock(mStateMutex);
mTagInstances.push_back(newTagInstance);
}
}
}
break;
case static_cast<LONG>(SCARD_E_NO_SMARTCARD):
ChipLogProgress(DeviceLayer, "No NFC Tag detected");
break;
default:
// An error happened
ChipLogError(DeviceLayer, "SCardConnect failed with error 0x" ChipLogFormatX64, ChipLogValueX64(result));
return CHIP_ERROR_INTERNAL;
}
return CHIP_NO_ERROR;
}
// Function to search for a TagInstance based on readerName and cardHandle
std::shared_ptr<TagInstance> NFCCommissioningManagerImpl::SearchTagInstanceFromReaderNameAndCardHandle(const char * readerName,
SCARDHANDLE cardHandle)
{
ChipLogProgressNfcDebug(DeviceLayer, "SearchTagInstanceFromReaderNameAndCardHandle %s", readerName);
for (auto & instance : mTagInstances)
{
if (strcmp(instance->GetReaderName(), readerName) == 0 && instance->GetCardHandle() == cardHandle)
{
return instance;
}
}
return nullptr; // Return nullptr if not found
}
// Function to search for a TagInstance based on discriminator
std::shared_ptr<TagInstance> NFCCommissioningManagerImpl::SearchTagInstanceFromDiscriminator(uint16_t discriminator)
{
ChipLogProgressNfcDebug(DeviceLayer, "SearchTagInstanceFromDiscriminator %d", discriminator);
for (auto & instance : mTagInstances)
{
if (instance->GetDiscriminator() == discriminator)
{
return instance;
}
}
return nullptr; // Return nullptr if not found
}
// Erase all the TagInstance(s) using the given readerName
void NFCCommissioningManagerImpl::EraseAllTagInstancesUsingReaderName(const char * readerName)
{
if ((mLastTagInstanceUsed != nullptr) && (strcmp(mLastTagInstanceUsed->GetReaderName(), readerName) == 0))
{
mLastTagInstanceUsed = nullptr;
}
for (auto it = mTagInstances.begin(); it != mTagInstances.end();)
{
if (strcmp((*it)->GetReaderName(), readerName) == 0)
{
// tagInstance will be deleted automatically when both the mTagInstances vector
// and the message queue will no more use it.
it = mTagInstances.erase(it);
}
else
{
++it;
}
}
}
// ===== NFCReaderTransport implementation
void NFCCommissioningManagerImpl::SetDelegate(Nfc::NFCReaderTransportDelegate * delegate)
{
std::lock_guard<std::mutex> lock(mStateMutex);
mDelegate = delegate;
}
CHIP_ERROR NFCCommissioningManagerImpl::StartDiscoveringTagMatchingAddress(const Nfc::NFCTag::Identifier & tagIdentifier)
{
{
std::lock_guard<std::mutex> lock(mStateMutex);
VerifyOrReturnError(mNFCBase != nullptr, CHIP_ERROR_INCORRECT_STATE);
}
ReturnErrorOnFailure(EnsureWorkerThreadStarted());
ChipLogProgressNfcDebug(DeviceLayer, "StartDiscoveringTagMatchingAddress discr %d", tagIdentifier.discriminator);
NfcWorkItem item;
item.type = NfcWorkType::kScan;
item.targetIdentifier = tagIdentifier;
ReturnErrorOnFailure(EnqueueWork(std::move(item)));
return CHIP_NO_ERROR;
}
CHIP_ERROR NFCCommissioningManagerImpl::StopDiscoveringTags()
{
return CHIP_NO_ERROR;
}
bool NFCCommissioningManagerImpl::FindTagMatchingIdentifier(const Nfc::NFCTag::Identifier & tagIdentifier)
{
std::lock_guard<std::mutex> lock(mStateMutex);
return SearchTagInstanceFromDiscriminator(tagIdentifier.discriminator) != nullptr;
}
} // namespace Internal
} // namespace DeviceLayer
} // namespace chip
#endif // CHIP_DEVICE_CONFIG_ENABLE_NFC_BASED_COMMISSIONING