Skip to content

Commit 3c01bc1

Browse files
committed
fix: Remove timeouts
REmoving the command timeouts and adding support for cancel action. On IOS the cancel is drived by the OS and the entry point is in the channel On Android the cancel is app driven
1 parent 6f7caeb commit 3c01bc1

17 files changed

+550
-432
lines changed

docs/API.md

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -175,14 +175,13 @@ connect(commManager.get(), &CommunicationManager::commandCompleted,
175175

176176
```cpp
177177
// Execute command synchronously (blocks until complete)
178-
CommandResult executeCommandSync(std::unique_ptr<CardCommand> cmd,
179-
int timeoutMs = -1);
178+
CommandResult executeCommandSync(std::unique_ptr<CardCommand> cmd);
180179
```
181180
182181
**Example:**
183182
```cpp
184183
auto cmd = std::make_unique<VerifyPINCommand>("123456");
185-
CommandResult result = commManager->executeCommandSync(std::move(cmd), 5000);
184+
CommandResult result = commManager->executeCommandSync(std::move(cmd));
186185
187186
if (result.success) {
188187
qDebug() << "PIN verified!";
@@ -637,10 +636,7 @@ QByteArray getData(uint8_t type);
637636
QByteArray identify(const QByteArray& challenge = QByteArray());
638637

639638
// Wait for card presence
640-
bool waitForCard(int timeoutMs = -1);
641-
642-
// Set default wait timeout
643-
void setDefaultWaitTimeout(int timeoutMs);
639+
bool waitForCard();
644640

645641
// Get last error message
646642
QString lastError() const;
@@ -1324,15 +1320,15 @@ commManager->startDetection();
13241320
```cpp
13251321
// Verify PIN (blocks until complete, but thread-safe!)
13261322
auto verifyCmd = std::make_unique<VerifyPINCommand>("123456");
1327-
CommandResult result = commManager->executeCommandSync(std::move(verifyCmd), 5000);
1323+
CommandResult result = commManager->executeCommandSync(std::move(verifyCmd));
13281324

13291325
if (result.success) {
13301326
qDebug() << "PIN verified!";
13311327

13321328
// Sign a transaction
13331329
QByteArray hash = /* 32-byte hash */;
13341330
auto signCmd = std::make_unique<SignCommand>(hash, "m/44'/60'/0'/0/0");
1335-
CommandResult signResult = commManager->executeCommandSync(std::move(signCmd), 30000);
1331+
CommandResult signResult = commManager->executeCommandSync(std::move(signCmd));
13361332

13371333
if (signResult.success) {
13381334
QByteArray signature = signResult.data.toMap()["signature"].toByteArray();
@@ -1398,7 +1394,7 @@ connect(commManager.get(), &CommunicationManager::cardInitialized,
13981394
if (!result.appInfo.initialized) {
13991395
// Initialize new card
14001396
auto initCmd = std::make_unique<InitCommand>("123456", "123456789012", "KeycardDefaultPairing");
1401-
CommandResult initResult = commManager->executeCommandSync(std::move(initCmd), 60000);
1397+
CommandResult initResult = commManager->executeCommandSync(std::move(initCmd));
14021398
14031399
if (!initResult.success) {
14041400
qWarning() << "Init failed:" << initResult.error;
@@ -1681,14 +1677,8 @@ constexpr uint8_t P1StoreDataCash = 0x02; // Cash data
16811677
}
16821678
```
16831679

1684-
2. **Handle platform-specific timeouts:**
1685-
```cpp
1686-
#ifdef Q_OS_IOS
1687-
cmdSet->setDefaultWaitTimeout(30000); // 30s for iOS
1688-
#else
1689-
cmdSet->setDefaultWaitTimeout(60000); // 60s for desktop
1690-
#endif
1691-
```
1680+
2. **Use event-driven card waiting:**
1681+
`waitForCard()` now waits for channel events (detect/error/detection-stopped) instead of a timeout.
16921682

16931683
---
16941684

examples/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ QUuid token = commMgr->enqueueCommand(std::move(cmd));
136136

137137
// Sync API (for worker threads)
138138
auto cmd = std::make_unique<SelectCommand>();
139-
CommandResult result = commMgr->executeCommandSync(std::move(cmd), 5000);
139+
CommandResult result = commMgr->executeCommandSync(std::move(cmd));
140140
```
141141
142142
### Dependency Injection

include/keycard-qt/backends/keycard_channel_backend.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,12 @@ class KeycardChannelBackend : public QObject
105105
*/
106106
virtual void stopDetection() = 0;
107107

108+
/**
109+
* @brief Check if detection is active
110+
* @return true if detection is active, false otherwise
111+
*/
112+
virtual bool isDetectionActive() const = 0;
113+
108114
/**
109115
* @brief Disconnect from the currently connected card/tag
110116
*/
@@ -194,6 +200,14 @@ class KeycardChannelBackend : public QObject
194200
*/
195201
void cardRemoved();
196202

203+
/**
204+
* @brief Emitted when the backend stops target detection
205+
*
206+
* Used by event-driven waiters that should unblock when detection ends.
207+
* @param forced true if detection was forcefully stopped by the user or application
208+
*/
209+
void targetDetectionStopped(bool forced);
210+
197211
/**
198212
* @brief Emitted when an error occurs
199213
* @param message Error description

include/keycard-qt/backends/keycard_channel_pcsc.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ class KeycardChannelPcsc : public KeycardChannelBackend
4242
// KeycardChannelBackend interface
4343
void startDetection() override;
4444
void stopDetection() override;
45+
bool isDetectionActive() const override { return m_stopDetection.loadAcquire() == 0; }
4546
void disconnect() override;
4647
bool isConnected() const override;
4748
QByteArray transmit(const QByteArray& apdu) override;

include/keycard-qt/backends/keycard_channel_unified_qt_nfc.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ class KeycardChannelUnifiedQtNfc : public KeycardChannelBackend
2828
// KeycardChannelBackend interface
2929
void startDetection() override;
3030
void stopDetection() override;
31+
bool isDetectionActive() const override { return m_detectionActive; }
3132
void disconnect() override;
3233
bool isConnected() const override;
3334
QByteArray transmit(const QByteArray& apdu) override;

include/keycard-qt/card_command.h

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,35 @@ namespace Keycard {
1010
// Forward declarations
1111
class CommandSet;
1212

13+
enum class CommandResultType {
14+
Success,
15+
Error,
16+
Timeout,
17+
Cancelled,
18+
InvalidState,
19+
InvalidCommand,
20+
InvalidParameter
21+
};
22+
1323
/**
1424
* @brief Result of a card command execution
1525
*/
1626
struct CommandResult {
1727
bool success;
1828
QVariant data;
1929
QString error;
30+
CommandResultType reason;
2031

21-
CommandResult() : success(false) {}
22-
CommandResult(bool s, const QVariant& d = QVariant(), const QString& e = QString())
23-
: success(s), data(d), error(e) {}
32+
CommandResult() : success(false), reason(CommandResultType::InvalidState) {}
33+
CommandResult(bool s, const QVariant& d = QVariant(), const QString& e = QString(), CommandResultType t = CommandResultType::Success)
34+
: success(s), data(d), error(e), reason(t) {}
2435

2536
static CommandResult fromSuccess(const QVariant& data = QVariant()) {
26-
return CommandResult(true, data);
37+
return CommandResult(true, data, QString(), CommandResultType::Success);
2738
}
2839

29-
static CommandResult fromError(const QString& error) {
30-
return CommandResult(false, QVariant(), error);
40+
static CommandResult fromError(const QString& error, CommandResultType type = CommandResultType::Error) {
41+
return CommandResult(false, QVariant(), error, type);
3142
}
3243
};
3344

include/keycard-qt/command_set.h

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -319,17 +319,9 @@ class CommandSet : public QObject {
319319
/**
320320
* @brief Wait for card to be present
321321
* Checks if card is connected, enables card detection if needed, and waits for card
322-
* @param timeoutMs Timeout in milliseconds (default: uses defaultWaitTimeout)
323-
* @return true if card detected, false on timeout or error
322+
* @return true if card detected, false on error or when detection stops
324323
*/
325-
bool waitForCard(int timeoutMs = -1);
326-
327-
/**
328-
* @brief Set default timeout for waitForCard operations
329-
* @param timeoutMs Timeout in milliseconds (default: 60000)
330-
* Useful for tests to use shorter timeouts
331-
*/
332-
void setDefaultWaitTimeout(int timeoutMs);
324+
bool waitForCard();
333325

334326
/**
335327
* @brief Ensure pairing is available for current card
@@ -400,6 +392,12 @@ class CommandSet : public QObject {
400392
* CommunicationManager should use this instead of accessing channel directly.
401393
*/
402394
void stopDetection();
395+
396+
/**
397+
* @brief Check if detection is active
398+
* @return true if detection is active, false otherwise
399+
*/
400+
bool isDetectionActive() const { return m_channel && m_channel->isDetectionActive(); }
403401

404402
/**
405403
* @brief Get current card UID
@@ -496,10 +494,9 @@ private slots:
496494

497495
/**
498496
* @brief Internal implementation of waitForCard (must be called from correct thread)
499-
* @param timeoutMs Timeout in milliseconds
500-
* @return true if card detected, false on timeout
497+
* @return true if card detected, false on error or when detection stops
501498
*/
502-
bool waitForCardInternal(int timeoutMs);
499+
bool waitForCardInternal();
503500

504501
/**
505502
* @brief Clean up state after factory reset
@@ -543,8 +540,6 @@ private slots:
543540
QString m_cachedPIN; // Cached PIN for auto-reauth after NFC session loss
544541
bool m_needsSecureChannelReestablishment = false; // Flag: secure channel must be re-opened before next command
545542

546-
// Default timeout for waitForCard operations (can be configured for tests)
547-
int m_defaultWaitTimeout = 60000; // 60 seconds default
548543

549544
std::atomic_bool m_cardReady = false;
550545
};

include/keycard-qt/communication_manager.h

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,15 @@ class CommunicationManager : public ICommunicationManager {
9292
* Call startDetection() to resume monitoring.
9393
*/
9494
void stopDetection() override;
95+
96+
/**
97+
* @brief Cancel all pending operations with the given reason
98+
* @param reason Error message for cancelled operations (default: "User cancelled NFC")
99+
*
100+
* Wakes sync waiters, flushes the queue with error results, and stops detection.
101+
* Keeps the manager thread alive. Thread-safe: marshals to manager thread if needed.
102+
*/
103+
void cancelPendingOperations(const QString& reason = "User cancelled NFC") override;
95104

96105
/**
97106
* @brief Stop the communication manager completely
@@ -137,16 +146,15 @@ class CommunicationManager : public ICommunicationManager {
137146
/**
138147
* @brief Execute command synchronously (blocking)
139148
* @param cmd Command to execute
140-
* @param timeoutMs Timeout in milliseconds
141149
* @return CommandResult with success/failure and data
142150
*
143151
* This is a convenience wrapper around enqueueCommand() that blocks
144-
* until the command completes or times out.
152+
* until the command completes or the manager is stopped.
145153
*
146154
* IMPORTANT: Do NOT call this from the communication thread or main thread
147155
* if the main thread needs to process events. Use from worker threads only.
148156
*/
149-
CommandResult executeCommandSync(std::unique_ptr<CardCommand> cmd, int timeoutMs = -1) override;
157+
CommandResult executeCommandSync(std::unique_ptr<CardCommand> cmd) override;
150158

151159
/**
152160
* @brief Get current state
@@ -221,6 +229,15 @@ private slots:
221229
* Allows tracking of channel state for operational state emission.
222230
*/
223231
void onChannelStateChanged(ChannelState state);
232+
233+
/**
234+
* @brief Handle target detection stopped from channel (user cancelled NFC)
235+
*
236+
* Emitted when the NFC backend stops detection (e.g., user closed drawer).
237+
* Triggers unified cancel cleanup.
238+
* @param forced true if detection was forcefully stopped by the user or application
239+
*/
240+
void onTargetDetectionStopped(bool forced);
224241

225242
/**
226243
* @brief Process next command in queue
@@ -267,6 +284,14 @@ private slots:
267284
* @brief Set state and emit signal
268285
*/
269286
void setState(State newState);
287+
288+
/**
289+
* @brief Internal cancel: wake sync waiters, flush queue, stop detection
290+
* @param reason Error message for cancelled operations
291+
*
292+
* Keeps manager thread alive. Used by cancelPendingOperations() and stop().
293+
*/
294+
void cancelPendingOperationsInternal(const QString& reason);
270295

271296
// Thread and queue management
272297
CommunicationThread* m_commThread;

include/keycard-qt/i_communication_manager.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,10 @@ class ICommunicationManager : public QObject {
5757
// Detection management
5858
virtual bool startDetection() = 0;
5959
virtual void stopDetection() = 0;
60+
virtual void cancelPendingOperations(const QString& reason) = 0;
6061

6162
// Command execution
62-
virtual CommandResult executeCommandSync(std::unique_ptr<CardCommand> cmd, int timeoutMs = -1) = 0;
63+
virtual CommandResult executeCommandSync(std::unique_ptr<CardCommand> cmd) = 0;
6364

6465
// Card information
6566
virtual ApplicationInfo applicationInfo() const = 0;
@@ -87,6 +88,12 @@ class ICommunicationManager : public QObject {
8788
* @brief Emitted when state changes (optional - for diagnostics)
8889
*/
8990
void stateChanged(int newState);
91+
92+
/**
93+
* @brief Emitted when an operation is cancelled
94+
* @param reason Reason for cancellation
95+
*/
96+
void operationCancelled(const QString& reason);
9097
};
9198

9299
} // namespace Keycard

include/keycard-qt/keycard_channel.h

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,14 @@ class KeycardChannel : public QObject, public IChannel {
9191
* an already connected card.
9292
*/
9393
void stopDetection();
94-
94+
95+
96+
/**
97+
* @brief Check if detection is active
98+
* @return true if detection is active, false otherwise
99+
*/
100+
bool isDetectionActive() const;
101+
95102
/**
96103
* @brief Force immediate re-scan for cards
97104
*
@@ -189,6 +196,12 @@ class KeycardChannel : public QObject, public IChannel {
189196
* After this signal, transmit() will fail until a new card is detected.
190197
*/
191198
void targetLost();
199+
200+
/**
201+
* @brief Emitted when target detection is stopped by backend/platform
202+
* @param forced true if detection was forcefully stopped by the user or application
203+
*/
204+
void targetDetectionStopped(bool forced);
192205

193206
/**
194207
* @brief Emitted when an error occurs

0 commit comments

Comments
 (0)