blob: 9178e64ad61c78f00359f038a20bd72905b69983 [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.
*/
/**
* @file
* Provides an implementation of the NFCCommissioningManager singleton object
* for the Android platforms.
*/
#pragma once
#include <transport/raw/NfcApplicationDelegate.h>
#include <lib/core/Global.h>
#include <platform/internal/NFCCommissioningManager.h>
#include <condition_variable>
#include <cstring>
#include <functional>
#include <lib/support/Span.h>
#include <mutex>
#include <queue>
#include <system/SystemMutex.h>
#include <thread>
#include <vector>
#ifdef __APPLE__
#include <PCSC/winscard.h>
#else
#include <winscard.h>
#endif
#if CHIP_DEVICE_CONFIG_ENABLE_NFC_BASED_COMMISSIONING
namespace chip {
namespace DeviceLayer {
namespace Internal {
class TagInstance;
// Message to send to an NFC Tag.
class NFCMessage
{
private:
// Pointer to the NFC Tag instance to communicate with
std::shared_ptr<TagInstance> mTagInstance;
// Dynamically allocated buffer to store the duplicated message data
std::unique_ptr<uint8_t[]> mDataToSendBuffer;
// Size of the duplicated message data
size_t mDataToSendSize = 0;
bool mIsMessageValid = false;
public:
// Constructor
NFCMessage(std::shared_ptr<TagInstance> instance, System::PacketBufferHandle && msgBuf) : mTagInstance(std::move(instance))
{
// Duplicate the data from the PacketBufferHandle
mDataToSendSize = msgBuf->DataLength();
mDataToSendBuffer.reset(new (std::nothrow) uint8_t[mDataToSendSize]);
if (mDataToSendBuffer != nullptr)
{
std::memcpy(mDataToSendBuffer.get(), msgBuf->Start(), mDataToSendSize);
mIsMessageValid = true;
}
else
{
ChipLogError(DeviceLayer, "Failed to allocate memory for NFCMessage");
mIsMessageValid = false;
}
}
// Move Constructor
NFCMessage(NFCMessage &&) noexcept = default;
// Move Assignment Operator
NFCMessage & operator=(NFCMessage &&) noexcept = default;
// Deleted Copy Constructor
NFCMessage(const NFCMessage &) = delete;
// Deleted Copy Assignment Operator
NFCMessage & operator=(const NFCMessage &) = delete;
// Destructor
~NFCMessage() = default;
// Get the TagInstance
std::shared_ptr<TagInstance> GetTagInstance() const { return mTagInstance; }
// Get the data to send
chip::ByteSpan GetDataToSend() const { return chip::ByteSpan(mDataToSendBuffer.get(), mDataToSendSize); }
// Check if the message is valid
bool IsMessageValid() const { return mIsMessageValid; }
};
// Context used to synchronize a synchronous work item with the caller thread.
struct SyncWorkContext
{
std::mutex mutex;
std::condition_variable cv;
bool done = false;
CHIP_ERROR result = CHIP_NO_ERROR;
};
// Work types handled by the processing thread
enum class NfcWorkType
{
kScan, // Scan every NFC Readers
kSend, // Send NFC commands to a Card
};
// Work item processed by the NFC worker thread.
struct NfcWorkItem
{
NfcWorkType type;
Nfc::NFCTag::Identifier targetIdentifier;
std::unique_ptr<NFCMessage> message;
// If not null, the worker thread must signal completion of this work item
// through this synchronization context.
SyncWorkContext * syncCtx = nullptr;
};
/**
* Concrete implementation of the NFCCommissioningManagerImpl singleton object for the Linux platforms.
*
* Threading model:
* - This class is intended to be used by one CHIP/platform event-loop thread plus one NFC worker thread.
* - Public API methods are expected to be called from the CHIP thread.
* - The NFC worker thread performs blocking PC/SC operations and never directly invokes CHIP transport callbacks;
* instead it dispatches back onto the CHIP thread via PlatformMgr().ScheduleWork(...).
*
* Locking model:
* - mStateMutex protects shared manager state:
* - mNFCBase
* - mDelegate
* - mLastTagInstanceUsed
* - mTagInstances
* - mWorkQueueMutex protects the worker queue and worker lifecycle flags:
* - mWorkQueue
* - mNfcWorkerThreadRunning
* - mShuttingDown
* - mWorkerInitMutex is used only for the worker initialization handshake.
*
* Locking rules:
* - Do not hold mStateMutex while performing blocking PC/SC operations.
* - Do not hold mStateMutex while invoking or scheduling callbacks into CHIP objects.
* - Do not hold mWorkQueueMutex while processing a work item.
* - Avoid nested locking between mStateMutex and mWorkQueueMutex.
*
* TagInstance lifetime:
* - TagInstance objects are reference-counted via std::shared_ptr.
* - Once a work item captures a TagInstance, the instance remains alive until that work item completes,
* even if the manager cache no longer references it.
*/
class NFCCommissioningManagerImpl final : public NFCCommissioningManager,
public Nfc::NFCReaderTransport,
private Nfc::NfcApplicationDelegate
{
// Allow the NFCCommissioningManager interface class to delegate method calls to
// the implementation methods provided by this class.
friend NFCCommissioningManager;
public:
// ===== Members that implement virtual methods on NfcApplicationDelegate.
void SetNFCBase(Transport::NFCBase * nfcBase) override;
bool CanSendToPeer(const Transport::PeerAddress & address) override;
CHIP_ERROR SendToNfcTag(const Transport::PeerAddress & address, System::PacketBufferHandle && msgBuf) override;
// ===== Members that implement virtual methods on NFCReaderTransport.
void SetDelegate(Nfc::NFCReaderTransportDelegate * delegate) override;
CHIP_ERROR StartDiscoveringTagMatchingAddress(const Nfc::NFCTag::Identifier & tagIdentifier) override;
CHIP_ERROR StopDiscoveringTags() override;
bool FindTagMatchingIdentifier(const Nfc::NFCTag::Identifier & tagIdentifier) override;
CHIP_ERROR SendMessage(System::PacketBufferHandle && message, const Nfc::NFCTag::Identifier & tagIdentifier,
std::function<void(System::PacketBufferHandle &&, CHIP_ERROR)> onResponse) override
{
static_cast<void>(message);
static_cast<void>(tagIdentifier);
static_cast<void>(onResponse);
return CHIP_ERROR_NOT_IMPLEMENTED;
}
private:
// ===== Members that implement the NFCCommissioningManager internal interface.
CHIP_ERROR _Init();
void _Shutdown();
Nfc::NFCReaderTransport * _GetNFCReaderTransport() const { return const_cast<NFCCommissioningManagerImpl *>(this); }
void _SetNFCReaderTransport(Nfc::NFCReaderTransport * readerTransport) {}
// ===== Members for internal use by the following friends.
friend NFCCommissioningManager & NFCCommissioningMgr();
friend NFCCommissioningManagerImpl & NFCCommissioningMgrImpl();
static Global<NFCCommissioningManagerImpl> sInstance;
void EraseAllTagInstancesUsingReaderName(const char * readerName) CHIP_REQUIRES(mStateMutex);
std::shared_ptr<TagInstance> SearchTagInstanceFromReaderNameAndCardHandle(const char * readerName, SCARDHANDLE cardHandle)
CHIP_REQUIRES(mStateMutex);
std::shared_ptr<TagInstance> SearchTagInstanceFromDiscriminator(uint16_t discriminator) CHIP_REQUIRES(mStateMutex);
// Start scan on all available readers and scan for NFC Tags.
CHIP_ERROR ScanAllReaders(void);
// Start scan on a given reader
CHIP_ERROR ScanReader(char * readerName);
Transport::NFCBase * mNFCBase CHIP_GUARDED_BY(mStateMutex) = nullptr;
// PC/SC context owned by the NFC worker thread.
SCARDCONTEXT mPcscContext = 0;
// Cache of discovered NFC tag instances.
std::shared_ptr<TagInstance> mLastTagInstanceUsed CHIP_GUARDED_BY(mStateMutex);
std::vector<std::shared_ptr<TagInstance>> mTagInstances CHIP_GUARDED_BY(mStateMutex);
// Shared manager state mutex
std::mutex mStateMutex;
// NFC worker Thread and synchronization primitives
std::thread mNfcWorkerThread;
std::queue<NfcWorkItem> mWorkQueue CHIP_GUARDED_BY(mWorkQueueMutex);
std::mutex mWorkQueueMutex;
std::condition_variable mWorkQueueCondition;
bool mNfcWorkerThreadRunning CHIP_GUARDED_BY(mWorkQueueMutex) = false;
bool mShuttingDown CHIP_GUARDED_BY(mWorkQueueMutex) = false;
// Synchronization primitives used to wait until the worker thread has finished
// its initialization, including PC/SC context creation.
std::mutex mWorkerInitMutex;
std::condition_variable mWorkerInitCondition;
bool mWorkerInitCompleted CHIP_GUARDED_BY(mWorkerInitMutex) = false;
CHIP_ERROR mWorkerInitResult CHIP_GUARDED_BY(mWorkerInitMutex) = CHIP_ERROR_INCORRECT_STATE;
// NFCReaderTransport state
Nfc::NFCReaderTransportDelegate * mDelegate CHIP_GUARDED_BY(mStateMutex) = nullptr;
// Private methods
void NfcWorkerThreadMain();
CHIP_ERROR ProcessScanWorkItem(const NfcWorkItem & item);
CHIP_ERROR ProcessSendWorkItem(const NfcWorkItem & item);
CHIP_ERROR EnqueueWork(NfcWorkItem && item);
CHIP_ERROR EnsureWorkerThreadStarted();
CHIP_ERROR RunSyncOnWorker(NfcWorkItem && item);
Transport::NFCBase * GetNFCBase();
static void DispatchTagDiscovery(intptr_t arg);
};
/**
* Returns a reference to the public interface of the NFCCommissioningManager singleton object.
*
* Internal components should use this to access features of the NFCCommissioningManager object
* that are common to all platforms.
*/
inline NFCCommissioningManager & NFCCommissioningMgr()
{
return NFCCommissioningManagerImpl::sInstance.get();
}
/**
* Returns the platform-specific implementation of the NFCCommissioningManager singleton object.
*
* Internal components can use this to gain access to features of the NFCCommissioningManager
* that are specific to the Linux platforms.
*/
inline NFCCommissioningManagerImpl & NFCCommissioningMgrImpl()
{
return NFCCommissioningManagerImpl::sInstance.get();
}
} // namespace Internal
} // namespace DeviceLayer
} // namespace chip
#endif // CHIP_DEVICE_CONFIG_ENABLE_NFC_BASED_COMMISSIONING