Skip to content

Commit af61b3c

Browse files
committed
Refactor timeout feature to use callbacks
1 parent e81c5f9 commit af61b3c

9 files changed

Lines changed: 584 additions & 135 deletions

File tree

docs/draft/timeout.md

Lines changed: 44 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,35 @@
22

33
## 1. Configuration at Init Time
44

5-
When creating a client, you provide a `whTimeoutConfig` specifying the timeout duration and an optional callback:
5+
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.
6+
7+
When creating a client, you provide a `whTimeoutConfig` specifying the platform callbacks, platform context, and an optional application-level expired callback:
68
```c
9+
/* Platform-specific setup (e.g. POSIX) */
10+
posixTimeoutContext posixCtx = {0};
11+
posixTimeoutConfig posixCfg = {.timeoutUs = WH_SEC_TO_USEC(5)};
12+
whTimeoutCb timeoutCbTable = POSIX_TIMEOUT_CB;
13+
714
whTimeoutConfig timeoutCfg = {
8-
.timeoutUs = WH_SEC_TO_USEC(5), /* 5-second timeout */
9-
.expiredCb = myTimeoutHandler, /* optional callback on expiry */
10-
.cbCtx = myAppContext, /* context passed to callback */
15+
.cb = &timeoutCbTable, /* platform callback table */
16+
.context = &posixCtx, /* platform context */
17+
.config = &posixCfg, /* platform-specific config */
18+
.expiredCb = myTimeoutHandler, /* optional app callback on expiry */
19+
.expiredCtx = myAppContext, /* context passed to app callback */
1120
};
1221
whClientConfig clientCfg = {
1322
.comm = &commConfig,
14-
.respTimeoutConfig = &timeoutCfg, /* attach timeout config */
23+
.respTimeoutConfig = &timeoutCfg, /* attach timeout config */
1524
};
1625
wh_Client_Init(&clientCtx, &clientCfg);
1726
```
1827
19-
During `wh_Client_Init` (`src/wh_client.c:84-89`), the config is copied into an embedded `whTimeoutCtx respTimeout[1]` inside the client context via `wh_Timeout_Init()`. This stores the timeout duration and callback but doesn't start any timer yet.
20-
If `respTimeoutConfig` is NULL, the timeout context is left zeroed and effectively disabled (a `timeoutUs` of 0 means "never expires").
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).
2130
2231
## 2. What Happens During a Crypto Call
2332
24-
Before this PR, every crypto function in `wh_client_crypto.c` had this pattern after sending a request:
33+
Before the timeout feature, every crypto function in `wh_client_crypto.c` had this pattern after sending a request:
2534
```c
2635
/* Old pattern -- infinite busy-wait */
2736
do {
@@ -30,16 +39,16 @@ do {
3039
```
3140

3241
If the server never responded, the client would spin forever.
33-
The PR replaces all ~30 of these with a single helper `_recvCryptoResponse()` (`src/wh_client_crypto.c:165-180`):
42+
This is replaced with a single helper `_recvCryptoResponse()` (`src/wh_client_crypto.c`):
3443
```c
3544
static int _recvCryptoResponse(whClientContext* ctx,
3645
uint16_t* group, uint16_t* action,
3746
uint16_t* size, void *data)
3847
{
3948
int ret;
4049
#ifdef WOLFHSM_CFG_ENABLE_TIMEOUT
41-
ret = wh_Client_RecvResponseTimeout(ctx, group, action, size, data,
42-
ctx->respTimeout);
50+
ret = wh_Client_RecvResponseBlockingWithTimeout(ctx, group, action,
51+
size, data);
4352
#else
4453
do {
4554
ret = wh_Client_RecvResponse(ctx, group, action, size, data);
@@ -49,31 +58,30 @@ static int _recvCryptoResponse(whClientContext* ctx,
4958
}
5059
```
5160
52-
When timeout is enabled, it delegates to `wh_Client_RecvResponseTimeout`. When disabled, the old infinite-loop behavior is preserved.
61+
When timeout is enabled, it delegates to `wh_Client_RecvResponseBlockingWithTimeout`. When disabled, the old infinite-loop behavior is preserved.
5362
5463
## 3. The Timeout Receive Loop
55-
`wh_Client_RecvResponseTimeout` (`src/wh_client.c:211-231`) does this:
56-
1. **Starts the timer** -- calls `wh_Timeout_Start()` which snapshots the current time via `WH_GETTIME_US()` into `timeout->startUs`.
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).
5766
2. **Polls for a response** -- calls `wh_Client_RecvResponse()` in a loop.
5867
3. **On each `WH_ERROR_NOTREADY`**, checks `wh_Timeout_Expired()`:
59-
- Gets the current time via `WH_GETTIME_US()`
60-
- Computes `(now - startUs) >= timeoutUs`
61-
- If expired: invokes the `expiredCb` (if set), then returns `WH_ERROR_TIMEOUT`
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`
6270
- If not expired: loops again
6371
4. **On any other return value** (success or error), returns immediately.
6472
```
65-
Client App _recvCryptoResponse wh_Timeout
73+
Client App _recvCryptoResponse whTimeout
6674
| | |
6775
|-- wh_Client_AesCbc() --------> | |
68-
| |-- wh_Timeout_Start --------> capture time
76+
| |-- wh_Timeout_Start --------> cb->start()
6977
| | |
7078
| |-- RecvResponse (NOTREADY) |
71-
| |-- Expired? ------------------> no
79+
| |-- Expired? -------> cb->expired() -> no
7280
| |-- RecvResponse (NOTREADY) |
73-
| |-- Expired? ------------------> no
81+
| |-- Expired? -------> cb->expired() -> no
7482
| | ... |
7583
| |-- RecvResponse (NOTREADY) |
76-
| |-- Expired? ------------------> YES
84+
| |-- Expired? -------> cb->expired() -> YES
7785
| | |-- expiredCb()
7886
|<-- WH_ERROR_TIMEOUT -----------| |
7987
```
@@ -84,14 +92,14 @@ The `expiredCb` fires *before* the error is returned, so you can use it for logg
8492
8593
## 5. Overriding Expiration via the Callback
8694
87-
The 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.
95+
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.
8896
8997
The callback can also return a non-zero error code to signal a failure. When it does, `wh_Timeout_Expired()` propagates that error directly to the caller instead of returning the expired flag.
9098
9199
```c
92-
static int myOverrideCb(whTimeoutCtx* ctx, int* isExpired)
100+
static int myOverrideCb(whTimeout* timeout, int* isExpired)
93101
{
94-
int* retryCount = (int*)ctx->cbCtx;
102+
int* retryCount = (int*)timeout->expiredCtx;
95103
if (retryCount == NULL) {
96104
return WH_ERROR_BADARGS;
97105
}
@@ -101,21 +109,27 @@ static int myOverrideCb(whTimeoutCtx* ctx, int* isExpired)
101109
if (*retryCount <= 1) {
102110
/* First expiration: suppress and restart the timer */
103111
*isExpired = 0;
104-
wh_Timeout_Start(ctx);
112+
wh_Timeout_Start(timeout);
105113
}
106114
/* Subsequent expirations: allow the timeout to fire */
107115
return WH_ERROR_OK;
108116
}
109117
110118
int retryCount = 0;
119+
posixTimeoutContext posixCtx = {0};
120+
posixTimeoutConfig posixCfg = {.timeoutUs = WH_SEC_TO_USEC(5)};
121+
whTimeoutCb timeoutCbTable = POSIX_TIMEOUT_CB;
122+
111123
whTimeoutConfig timeoutCfg = {
112-
.timeoutUs = WH_SEC_TO_USEC(5),
113-
.expiredCb = myOverrideCb,
114-
.cbCtx = &retryCount,
124+
.cb = &timeoutCbTable,
125+
.context = &posixCtx,
126+
.config = &posixCfg,
127+
.expiredCb = myOverrideCb,
128+
.expiredCtx = &retryCount,
115129
};
116130
```
117131

118132
## 6. Scope Limitations
119133
A few things to note about the current design:
120134
- **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`.
121-
- **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(ctx->respTimeout, newValue)` to change it between calls, but there's no per-operation override.
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.

port/posix/posix_timeout.c

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
/*
2+
* Copyright (C) 2026 wolfSSL Inc.
3+
*
4+
* This file is part of wolfHSM.
5+
*
6+
* wolfHSM is free software; you can redistribute it and/or modify
7+
* it under the terms of the GNU General Public License as published by
8+
* the Free Software Foundation; either version 3 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* wolfHSM is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU General Public License
17+
* along with wolfHSM. If not, see <http://www.gnu.org/licenses/>.
18+
*/
19+
/*
20+
* port/posix/posix_timeout.c
21+
*
22+
* POSIX implementation of the wolfHSM timeout abstraction.
23+
* Uses posixGetTime() from posix_time.h for time measurement.
24+
*/
25+
26+
#include "wolfhsm/wh_settings.h"
27+
28+
#ifdef WOLFHSM_CFG_ENABLE_TIMEOUT
29+
30+
#include <stddef.h>
31+
32+
#include "wolfhsm/wh_error.h"
33+
#include "wolfhsm/wh_timeout.h"
34+
35+
#include "port/posix/posix_time.h"
36+
#include "port/posix/posix_timeout.h"
37+
38+
int posixTimeout_Init(void* context, const void* config)
39+
{
40+
posixTimeoutContext* ctx = (posixTimeoutContext*)context;
41+
const posixTimeoutConfig* cfg = (const posixTimeoutConfig*)config;
42+
43+
if (ctx == NULL) {
44+
return WH_ERROR_BADARGS;
45+
}
46+
47+
/* Already initialized? */
48+
if (ctx->initialized) {
49+
return WH_ERROR_OK;
50+
}
51+
52+
ctx->startUs = 0;
53+
ctx->timeoutUs = (cfg != NULL) ? cfg->timeoutUs : 0;
54+
ctx->started = 0;
55+
56+
ctx->initialized = 1;
57+
return WH_ERROR_OK;
58+
}
59+
60+
int posixTimeout_Cleanup(void* context)
61+
{
62+
posixTimeoutContext* ctx = (posixTimeoutContext*)context;
63+
64+
if (ctx == NULL) {
65+
return WH_ERROR_BADARGS;
66+
}
67+
68+
/* Not initialized? */
69+
if (!ctx->initialized) {
70+
return WH_ERROR_OK;
71+
}
72+
73+
ctx->startUs = 0;
74+
ctx->timeoutUs = 0;
75+
ctx->started = 0;
76+
ctx->initialized = 0;
77+
78+
return WH_ERROR_OK;
79+
}
80+
81+
int posixTimeout_Set(void* context, uint64_t timeoutUs)
82+
{
83+
posixTimeoutContext* ctx = (posixTimeoutContext*)context;
84+
85+
if (ctx == NULL) {
86+
return WH_ERROR_BADARGS;
87+
}
88+
89+
if (!ctx->initialized) {
90+
return WH_ERROR_NOTREADY;
91+
}
92+
93+
ctx->timeoutUs = timeoutUs;
94+
95+
return WH_ERROR_OK;
96+
}
97+
98+
int posixTimeout_Start(void* context)
99+
{
100+
posixTimeoutContext* ctx = (posixTimeoutContext*)context;
101+
102+
if (ctx == NULL) {
103+
return WH_ERROR_BADARGS;
104+
}
105+
106+
if (!ctx->initialized) {
107+
return WH_ERROR_NOTREADY;
108+
}
109+
110+
ctx->startUs = posixGetTime();
111+
ctx->started = 1;
112+
113+
return WH_ERROR_OK;
114+
}
115+
116+
int posixTimeout_Stop(void* context)
117+
{
118+
posixTimeoutContext* ctx = (posixTimeoutContext*)context;
119+
120+
if (ctx == NULL) {
121+
return WH_ERROR_BADARGS;
122+
}
123+
124+
if (!ctx->initialized) {
125+
return WH_ERROR_NOTREADY;
126+
}
127+
128+
ctx->startUs = 0;
129+
ctx->started = 0;
130+
131+
return WH_ERROR_OK;
132+
}
133+
134+
int posixTimeout_Expired(void* context, int* expired)
135+
{
136+
posixTimeoutContext* ctx = (posixTimeoutContext*)context;
137+
uint64_t nowUs;
138+
139+
if ((ctx == NULL) || (expired == NULL)) {
140+
return WH_ERROR_BADARGS;
141+
}
142+
143+
if (!ctx->initialized) {
144+
return WH_ERROR_NOTREADY;
145+
}
146+
147+
/* Not started or no timeout configured = not expired */
148+
if (!ctx->started || (ctx->timeoutUs == 0)) {
149+
*expired = 0;
150+
return WH_ERROR_OK;
151+
}
152+
153+
nowUs = posixGetTime();
154+
*expired = ((nowUs - ctx->startUs) >= ctx->timeoutUs) ? 1 : 0;
155+
156+
return WH_ERROR_OK;
157+
}
158+
159+
#endif /* WOLFHSM_CFG_ENABLE_TIMEOUT */

port/posix/posix_timeout.h

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* Copyright (C) 2026 wolfSSL Inc.
3+
*
4+
* This file is part of wolfHSM.
5+
*
6+
* wolfHSM is free software; you can redistribute it and/or modify
7+
* it under the terms of the GNU General Public License as published by
8+
* the Free Software Foundation; either version 3 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* wolfHSM is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU General Public License
17+
* along with wolfHSM. If not, see <http://www.gnu.org/licenses/>.
18+
*/
19+
/*
20+
* port/posix/posix_timeout.h
21+
*
22+
* POSIX implementation of the wolfHSM timeout abstraction.
23+
* Uses posixGetTime() for time measurement.
24+
*/
25+
26+
#ifndef PORT_POSIX_POSIX_TIMEOUT_H_
27+
#define PORT_POSIX_POSIX_TIMEOUT_H_
28+
29+
#include "wolfhsm/wh_settings.h"
30+
31+
#ifdef WOLFHSM_CFG_ENABLE_TIMEOUT
32+
33+
#include <stdint.h>
34+
35+
#include "wolfhsm/wh_timeout.h"
36+
37+
/* Configuration for POSIX timeout backend */
38+
typedef struct posixTimeoutConfig_t {
39+
uint64_t timeoutUs; /* Timeout duration in microseconds; 0 = no timeout */
40+
} posixTimeoutConfig;
41+
42+
/* Context structure holding timer state */
43+
typedef struct posixTimeoutContext_t {
44+
uint64_t startUs; /* Snapshot of start time */
45+
uint64_t timeoutUs; /* Configured timeout duration */
46+
int started; /* 1 if timer is running, 0 otherwise */
47+
int initialized; /* 1 if initialized, 0 otherwise */
48+
} posixTimeoutContext;
49+
50+
/* Callback functions matching whTimeoutCb interface */
51+
int posixTimeout_Init(void* context, const void* config);
52+
int posixTimeout_Cleanup(void* context);
53+
int posixTimeout_Set(void* context, uint64_t timeoutUs);
54+
int posixTimeout_Start(void* context);
55+
int posixTimeout_Stop(void* context);
56+
int posixTimeout_Expired(void* context, int* expired);
57+
58+
/* Convenience macro for callback table initialization */
59+
/* clang-format off */
60+
#define POSIX_TIMEOUT_CB \
61+
{ \
62+
.init = posixTimeout_Init, \
63+
.cleanup = posixTimeout_Cleanup, \
64+
.set = posixTimeout_Set, \
65+
.start = posixTimeout_Start, \
66+
.stop = posixTimeout_Stop, \
67+
.expired = posixTimeout_Expired, \
68+
}
69+
/* clang-format on */
70+
71+
#endif /* WOLFHSM_CFG_ENABLE_TIMEOUT */
72+
73+
#endif /* !PORT_POSIX_POSIX_TIMEOUT_H_ */

0 commit comments

Comments
 (0)