Skip to content

Commit 2a3c6d3

Browse files
committed
Move response timeout logic into comm layer
1 parent af61b3c commit 2a3c6d3

7 files changed

Lines changed: 252 additions & 241 deletions

File tree

docs/draft/timeout.md

Lines changed: 27 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
The timeout feature uses a callback-based abstraction (similar to the lock feature) that allows platform-specific timer implementations without introducing OS dependencies in core wolfHSM code. A platform port provides a callback table implementing the timer operations, and the core timeout module delegates to these callbacks.
66

7-
When creating a client, you provide a `whTimeoutConfig` specifying the platform callbacks, platform context, and an optional application-level expired callback:
7+
The timeout lives in the comm layer. When creating a client, you provide a `whTimeoutConfig` in the `whCommClientConfig`:
88
```c
99
/* Platform-specific setup (e.g. POSIX) */
1010
posixTimeoutContext posixCtx = {0};
@@ -18,62 +18,36 @@ whTimeoutConfig timeoutCfg = {
1818
.expiredCb = myTimeoutHandler, /* optional app callback on expiry */
1919
.expiredCtx = myAppContext, /* context passed to app callback */
2020
};
21-
whClientConfig clientCfg = {
22-
.comm = &commConfig,
21+
whCommClientConfig commConfig = {
22+
.transport_cb = &transportCb,
23+
.transport_context = &transportCtx,
24+
.transport_config = &transportCfg,
25+
.client_id = 1,
2326
.respTimeoutConfig = &timeoutCfg, /* attach timeout config */
2427
};
28+
whClientConfig clientCfg = {
29+
.comm = &commConfig,
30+
};
2531
wh_Client_Init(&clientCtx, &clientCfg);
2632
```
2733
28-
During `wh_Client_Init`, the config is used to initialize an embedded `whTimeout respTimeout` inside the client context via `wh_Timeout_Init()`. This calls the platform `init` callback to set up timer resources but doesn't start any timer yet.
29-
If `respTimeoutConfig` is NULL (or `cb` is NULL), the timeout is disabled and all operations become no-ops (timeout never expires).
34+
During `wh_CommClient_Init`, the timeout is initialized via `wh_Timeout_Init()`. This calls the platform `init` callback to set up timer resources but doesn't start any timer yet.
35+
If `respTimeoutConfig` is NULL (or `cb` is NULL), the timeout enters no-op mode and never expires.
3036
31-
## 2. What Happens During a Crypto Call
37+
## 2. How the Timeout Works
3238
33-
Before the timeout feature, every crypto function in `wh_client_crypto.c` had this pattern after sending a request:
34-
```c
35-
/* Old pattern -- infinite busy-wait */
36-
do {
37-
ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, dataPtr);
38-
} while (ret == WH_ERROR_NOTREADY);
39-
```
39+
The timeout is handled transparently in the comm layer:
4040
41-
If the server never responded, the client would spin forever.
42-
This is replaced with a single helper `_recvCryptoResponse()` (`src/wh_client_crypto.c`):
43-
```c
44-
static int _recvCryptoResponse(whClientContext* ctx,
45-
uint16_t* group, uint16_t* action,
46-
uint16_t* size, void *data)
47-
{
48-
int ret;
49-
#ifdef WOLFHSM_CFG_ENABLE_TIMEOUT
50-
ret = wh_Client_RecvResponseBlockingWithTimeout(ctx, group, action,
51-
size, data);
52-
#else
53-
do {
54-
ret = wh_Client_RecvResponse(ctx, group, action, size, data);
55-
} while (ret == WH_ERROR_NOTREADY);
56-
#endif
57-
return ret;
58-
}
59-
```
41+
1. **`wh_CommClient_SendRequest`**: After a successful send, starts the response timer via `wh_Timeout_Start()`.
42+
2. **`wh_CommClient_RecvResponse`**: When the transport returns `WH_ERROR_NOTREADY`, checks `wh_Timeout_Expired()`. If expired, returns `WH_ERROR_TIMEOUT`. On successful receive, stops the timer via `wh_Timeout_Stop()`.
43+
44+
This means every `do { ... } while (ret == WH_ERROR_NOTREADY)` loop in the codebase automatically gets timeout support -- crypto, NVM, keystore, cert, SHE, keywrap, and all other client operations.
6045
61-
When timeout is enabled, it delegates to `wh_Client_RecvResponseBlockingWithTimeout`. When disabled, the old infinite-loop behavior is preserved.
62-
63-
## 3. The Timeout Receive Loop
64-
`wh_Client_RecvResponseBlockingWithTimeout` (`src/wh_client.c`) does this:
65-
1. **Starts the timer** -- calls `wh_Timeout_Start()` which delegates to the platform `start` callback (e.g. captures the current time).
66-
2. **Polls for a response** -- calls `wh_Client_RecvResponse()` in a loop.
67-
3. **On each `WH_ERROR_NOTREADY`**, checks `wh_Timeout_Expired()`:
68-
- Delegates to the platform `expired` callback to check elapsed time
69-
- If expired: invokes the application `expiredCb` (if set), then returns `WH_ERROR_TIMEOUT`
70-
- If not expired: loops again
71-
4. **On any other return value** (success or error), returns immediately.
7246
```
73-
Client App _recvCryptoResponse whTimeout
47+
Client App CommClient whTimeout
7448
| | |
75-
|-- wh_Client_AesCbc() --------> | |
76-
| |-- wh_Timeout_Start --------> cb->start()
49+
|-- wh_Client_AesCbc() -------->| |
50+
| |-- SendRequest ------> cb->start()
7751
| | |
7852
| |-- RecvResponse (NOTREADY) |
7953
| |-- Expired? -------> cb->expired() -> no
@@ -86,11 +60,12 @@ Client App _recvCryptoResponse whTimeout
8660
|<-- WH_ERROR_TIMEOUT -----------| |
8761
```
8862
89-
## 4. What the Client Sees
90-
From the application's perspective, the crypto APIs (`wh_Client_AesCbc`, `wh_Client_RsaFunction`, `wh_Client_EccSign`, etc.) now return `WH_ERROR_TIMEOUT` (-2010) instead of hanging indefinitely. The application can then decide how to handle it -- retry, log, fail gracefully, etc.
63+
## 3. What the Client Sees
64+
65+
From the application's perspective, any client API that waits for a server response can now return `WH_ERROR_TIMEOUT` (-2010) instead of hanging indefinitely. The application can then decide how to handle it -- retry, log, fail gracefully, etc.
9166
The `expiredCb` fires *before* the error is returned, so you can use it for logging or cleanup without needing to check the return code first.
9267
93-
## 5. Overriding Expiration via the Callback
68+
## 4. Overriding Expiration via the Callback
9469
9570
The application expired callback receives a pointer to the `isExpired` flag and can override it by setting `*isExpired = 0`. This suppresses the expiration for the current check, allowing the polling loop to continue. A common use case is to extend the timeout deadline: clear the flag, then call `wh_Timeout_Start()` to restart the timer.
9671
@@ -129,7 +104,6 @@ whTimeoutConfig timeoutCfg = {
129104
};
130105
```
131106

132-
## 6. Scope Limitations
133-
A few things to note about the current design:
134-
- **Only crypto responses are covered.** Non-crypto client calls (key management, NVM operations, comm init) still use the old infinite-wait pattern. The timeout is specifically wired into `_recvCryptoResponse`.
135-
- **The timeout is per-client, not per-call.** All crypto operations for a given client share the same `respTimeout` context with the same duration. You can call `wh_Timeout_Set()` to change the duration between calls, but there's no per-operation override.
107+
## 5. Design Notes
108+
- **The timeout is per-comm-client, not per-call.** All operations for a given client share the same `respTimeout` context with the same duration. You can call `wh_Timeout_Set()` to change the duration between calls, but there's no per-operation override.
109+
- **Timer starts on send, checks on receive.** The timer window begins when a request is successfully sent, measuring the full round-trip wait.

src/wh_client.c

Lines changed: 0 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -77,15 +77,6 @@ int wh_Client_Init(whClientContext* c, const whClientConfig* config)
7777

7878
memset(c, 0, sizeof(*c));
7979

80-
#ifdef WOLFHSM_CFG_ENABLE_TIMEOUT
81-
if (config->respTimeoutConfig != NULL) {
82-
rc = wh_Timeout_Init(&c->respTimeout, config->respTimeoutConfig);
83-
if (rc != WH_ERROR_OK) {
84-
return rc;
85-
}
86-
}
87-
#endif
88-
8980
rc = wh_CommClient_Init(c->comm, config->comm);
9081

9182
#ifndef WOLFHSM_CFG_NO_CRYPTO
@@ -138,10 +129,6 @@ int wh_Client_Cleanup(whClientContext* c)
138129
(void)wolfCrypt_Cleanup();
139130
#endif /* !WOLFHSM_CFG_NO_CRYPTO */
140131

141-
#ifdef WOLFHSM_CFG_ENABLE_TIMEOUT
142-
(void)wh_Timeout_Cleanup(&c->respTimeout);
143-
#endif
144-
145132
(void)wh_CommClient_Cleanup(c->comm);
146133

147134
memset(c, 0, sizeof(*c));
@@ -208,46 +195,6 @@ int wh_Client_RecvResponse(whClientContext *c,
208195
return rc;
209196
}
210197

211-
#ifdef WOLFHSM_CFG_ENABLE_TIMEOUT
212-
int wh_Client_RecvResponseBlockingWithTimeout(whClientContext* c,
213-
uint16_t* out_group,
214-
uint16_t* out_action,
215-
uint16_t* out_size, void* data)
216-
{
217-
int ret;
218-
whTimeout* timeout;
219-
220-
if (c == NULL) {
221-
return WH_ERROR_BADARGS;
222-
}
223-
224-
timeout = &c->respTimeout;
225-
226-
/* If no timeout configured, fall back to standard blocking loop */
227-
if (timeout->cb == NULL) {
228-
do {
229-
ret = wh_Client_RecvResponse(c, out_group, out_action, out_size,
230-
data);
231-
} while (ret == WH_ERROR_NOTREADY);
232-
return ret;
233-
}
234-
235-
ret = wh_Timeout_Start(timeout);
236-
if (ret != WH_ERROR_OK) {
237-
return ret;
238-
}
239-
240-
do {
241-
ret = wh_Client_RecvResponse(c, out_group, out_action, out_size, data);
242-
if ((ret == WH_ERROR_NOTREADY) && wh_Timeout_Expired(timeout)) {
243-
return WH_ERROR_TIMEOUT;
244-
}
245-
} while (ret == WH_ERROR_NOTREADY);
246-
247-
return ret;
248-
}
249-
#endif /* WOLFHSM_CFG_ENABLE_TIMEOUT */
250-
251198
int wh_Client_CommInitRequest(whClientContext* c)
252199
{
253200
whMessageCommInitRequest msg = {0};

0 commit comments

Comments
 (0)