Skip to content

Commit fe7e8b9

Browse files
committed
feat: add reader availability handling making a distinction between card and reader removal
Problem was that both "card removed from reader" and "reader unplugged" produced the same cardLost signal and SessionManager couldn't distinguish between them. Proper connection to `readerAvailabilityChanged` to make a clear distinction between card and reader removal, 2 different signals are sent to client in this case `waiting-for-reader` and `waiting-for-card`. Now in case of removing reader: - KeycardChannelBackend -> readerAvailabilityChanged(false) -> KeycardChannel -> SessionManager::onReaderAvailabilityChanged -> state is WaitingForReader - CommandSet -> cardLost -> CommunicationManager -> SessionManager::onCardRemoved -> (no signal, cause already WaitingForReader) In case of removing card, but reader is connected: - CommandSet -> cardLost -> CommunicationManager -> SessionManager::onCardRemoved -> state is WaitingForCard
1 parent 49b3379 commit fe7e8b9

File tree

5 files changed

+82
-8
lines changed

5 files changed

+82
-8
lines changed

src/session/session_composite_operations.cpp

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ SessionManager::LoginKeys SessionManager::login(const QString& keyUid, const QSt
3737
// Step 2: Wait for card to be ready (event-driven, no polling)
3838
{
3939
QMutexLocker locker(&m_cardReadyMutex);
40-
while (m_state == SessionState::WaitingForCard && !m_compositeMethodCallCancelled) {
40+
while ((m_state == SessionState::WaitingForCard || m_state == SessionState::WaitingForReader)
41+
&& !m_compositeMethodCallCancelled) {
4142
m_cardReadyCondition.wait(&m_cardReadyMutex);
4243
}
4344
}
@@ -107,7 +108,8 @@ SessionManager::RecoverKeys SessionManager::recover(const QString& pin, const QS
107108
// Step 2: Wait for card to be ready (event-driven, no polling)
108109
{
109110
QMutexLocker locker(&m_cardReadyMutex);
110-
while (m_state == SessionState::WaitingForCard && !m_compositeMethodCallCancelled) {
111+
while ((m_state == SessionState::WaitingForCard || m_state == SessionState::WaitingForReader)
112+
&& !m_compositeMethodCallCancelled) {
111113
m_cardReadyCondition.wait(&m_cardReadyMutex);
112114
}
113115
}
@@ -173,7 +175,8 @@ SessionManager::ExtendedPublicKey SessionManager::exportExtendedPublicKey(const
173175

174176
{
175177
QMutexLocker locker(&m_cardReadyMutex);
176-
while (m_state == SessionState::WaitingForCard && !m_compositeMethodCallCancelled) {
178+
while ((m_state == SessionState::WaitingForCard || m_state == SessionState::WaitingForReader)
179+
&& !m_compositeMethodCallCancelled) {
177180
m_cardReadyCondition.wait(&m_cardReadyMutex);
178181
}
179182
}
@@ -238,7 +241,8 @@ SessionManager::ExportPublicKeyResult SessionManager::exportPublicKey(const QStr
238241

239242
{
240243
QMutexLocker locker(&m_cardReadyMutex);
241-
while (m_state == SessionState::WaitingForCard && !m_compositeMethodCallCancelled) {
244+
while ((m_state == SessionState::WaitingForCard || m_state == SessionState::WaitingForReader)
245+
&& !m_compositeMethodCallCancelled) {
242246
m_cardReadyCondition.wait(&m_cardReadyMutex);
243247
}
244248
}

src/session/session_manager.cpp

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,14 +100,32 @@ bool SessionManager::start(bool logEnabled, const QString& logFilePath)
100100
this, [this]() { setState(SessionState::Cancelled); },
101101
Qt::AutoConnection);
102102

103+
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
104+
if (auto cmdSet = m_commMgr->commandSet()) {
105+
if (auto ch = cmdSet->channel()) {
106+
connect(ch.get(), &Keycard::KeycardChannel::readerAvailabilityChanged,
107+
this, &SessionManager::onReaderAvailabilityChanged,
108+
Qt::AutoConnection);
109+
}
110+
}
111+
#endif
112+
113+
// Mark as started before startDetection(), because detection may emit
114+
// readerAvailabilityChanged synchronously and the slot needs m_started == true.
115+
m_started = true;
116+
103117
// Start card detection (CommunicationManager should already be init'd by caller)
104118
if (!m_commMgr->startDetection()) {
105119
qWarning() << "SessionManager: Failed to start card detection";
120+
m_started = false;
106121
return false;
107122
}
108123

109-
setState(SessionState::WaitingForCard);
110-
m_started = true;
124+
// Only set WaitingForCard if a reader-availability signal hasn't already
125+
// moved us to a more specific state (e.g. WaitingForReader).
126+
if (m_state == SessionState::UnknownReaderState) {
127+
setState(SessionState::WaitingForCard);
128+
}
111129

112130
qDebug() << "StatusKeycardQt::SessionManager: Started successfully, monitoring for cards";
113131
return true;
@@ -132,6 +150,14 @@ void SessionManager::stop()
132150
if (m_commMgr) {
133151
// Disconnect from CommunicationManager signals so SessionManager doesn't react to card events anymore
134152
QObject::disconnect(m_commMgr.get(), nullptr, this, nullptr);
153+
154+
// Also disconnect from the channel's readerAvailabilityChanged signal
155+
if (auto cmdSet = m_commMgr->commandSet()) {
156+
if (auto ch = cmdSet->channel()) {
157+
QObject::disconnect(ch.get(), nullptr, this, nullptr);
158+
}
159+
}
160+
135161
m_commMgr->stopDetection();
136162
}
137163

@@ -210,11 +236,36 @@ void SessionManager::onCardRemoved()
210236
m_currentCardUID.clear();
211237

212238
if (m_started) {
213-
setState(SessionState::WaitingForCard);
239+
// If the reader was already marked as unavailable, the reader itself was removed
240+
// (onReaderAvailabilityChanged already set the appropriate state).
241+
// Only transition to WaitingForCard if the reader is still present.
242+
if (m_state != SessionState::WaitingForReader) {
243+
setState(SessionState::WaitingForCard);
244+
}
214245
}
215246
#endif
216247
}
217248

249+
void SessionManager::onReaderAvailabilityChanged(bool available)
250+
{
251+
qDebug() << "StatusKeycardQt::========================================";
252+
qDebug() << "StatusKeycardQt::SessionManager: READER AVAILABILITY CHANGED -" << (available ? "available" : "removed");
253+
qDebug() << "StatusKeycardQt::========================================";
254+
255+
if (!m_started) {
256+
return;
257+
}
258+
259+
if (!available) {
260+
m_currentCardUID.clear();
261+
setState(SessionState::WaitingForReader);
262+
} else {
263+
if (m_state == SessionState::WaitingForReader) {
264+
setState(SessionState::WaitingForCard);
265+
}
266+
}
267+
}
268+
218269
void SessionManager::setError(const QString& error)
219270
{
220271
qDebug() << "StatusKeycardQt::SessionManager::setError() - Error:" << error;

src/session/session_manager.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ class SessionManager : public QObject {
172172
public slots:
173173
void onCardInitialized(const Keycard::CardInitializationResult& result);
174174
void onCardRemoved();
175+
void onReaderAvailabilityChanged(bool available);
175176

176177
private:
177178
void setState(SessionState newState);

tests/mocks/mock_communication_manager.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,14 @@ void MockCommunicationManager::simulateCardRemoved()
6666
emit cardLost();
6767
}
6868

69+
void MockCommunicationManager::simulateReaderAvailabilityChanged(bool available)
70+
{
71+
qDebug() << "StatusKeycardQt::[MockCommunicationManager] Simulating reader availability changed:" << available;
72+
// In the real stack, this signal comes from KeycardChannel.
73+
// For mock testing, call SessionManager::onReaderAvailabilityChanged directly.
74+
Q_UNUSED(available);
75+
}
76+
6977
void MockCommunicationManager::setMockApplicationInfo(const Keycard::ApplicationInfo& info)
7078
{
7179
m_mockAppInfo = info;

tests/mocks/mock_communication_manager.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,20 @@ class MockCommunicationManager : public Keycard::ICommunicationManager {
4444

4545
/**
4646
* @brief Simulate card removal
47-
*
47+
*
4848
* This will emit cardLost signal asynchronously.
4949
*/
5050
void simulateCardRemoved();
51+
52+
/**
53+
* @brief Simulate reader availability change (PC/SC reader plugged/unplugged)
54+
* @param available true if reader is present, false if removed
55+
*
56+
* Note: In the real implementation, readerAvailabilityChanged comes from the
57+
* KeycardChannel, not ICommunicationManager. The mock exposes this for convenience;
58+
* tests connect SessionManager::onReaderAvailabilityChanged directly.
59+
*/
60+
void simulateReaderAvailabilityChanged(bool available);
5161

5262
/**
5363
* @brief Set mock application info to be returned

0 commit comments

Comments
 (0)