diff --git a/docs/draft/timeout.md b/docs/draft/timeout.md new file mode 100644 index 000000000..815720f85 --- /dev/null +++ b/docs/draft/timeout.md @@ -0,0 +1,88 @@ +# Timeout Functionality: Client Perspective + +## 1. Configuration at Init Time + +When creating a client, you provide a `whTimeoutConfig` specifying the timeout duration and an optional callback: +```c +whTimeoutConfig timeoutCfg = { + .timeoutUs = WH_SEC_TO_USEC(5), /* 5-second timeout */ + .expiredCb = myTimeoutHandler, /* optional callback on expiry */ + .cbCtx = myAppContext, /* context passed to callback */ +}; +whClientConfig clientCfg = { + .comm = &commConfig, + .respTimeoutConfig = &timeoutCfg, /* attach timeout config */ +}; +wh_Client_Init(&clientCtx, &clientCfg); +``` + +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. +If `respTimeoutConfig` is NULL, the timeout context is left zeroed and effectively disabled (a `timeoutUs` of 0 means "never expires"). + +## 2. What Happens During a Crypto Call + +Before this PR, every crypto function in `wh_client_crypto.c` had this pattern after sending a request: +```c +/* Old pattern -- infinite busy-wait */ +do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, dataPtr); +} while (ret == WH_ERROR_NOTREADY); +``` + +If the server never responded, the client would spin forever. +The PR replaces all ~30 of these with a single helper `_recvCryptoResponse()` (`src/wh_client_crypto.c:165-180`): +```c +static int _recvCryptoResponse(whClientContext* ctx, + uint16_t* group, uint16_t* action, + uint16_t* size, void *data) +{ + int ret; +#ifdef WOLFHSM_CFG_ENABLE_TIMEOUT + ret = wh_Client_RecvResponseTimeout(ctx, group, action, size, data, + ctx->respTimeout); +#else + do { + ret = wh_Client_RecvResponse(ctx, group, action, size, data); + } while (ret == WH_ERROR_NOTREADY); +#endif + return ret; +} +``` + +When timeout is enabled, it delegates to `wh_Client_RecvResponseTimeout`. When disabled, the old infinite-loop behavior is preserved. + +## 3. The Timeout Receive Loop +`wh_Client_RecvResponseTimeout` (`src/wh_client.c:211-231`) does this: +1. **Starts the timer** -- calls `wh_Timeout_Start()` which snapshots the current time via `WH_GETTIME_US()` into `timeout->startUs`. +2. **Polls for a response** -- calls `wh_Client_RecvResponse()` in a loop. +3. **On each `WH_ERROR_NOTREADY`**, checks `wh_Timeout_Expired()`: + - Gets the current time via `WH_GETTIME_US()` + - Computes `(now - startUs) >= timeoutUs` + - If expired: invokes the `expiredCb` (if set), then returns `WH_ERROR_TIMEOUT` + - If not expired: loops again +4. **On any other return value** (success or error), returns immediately. +``` +Client App _recvCryptoResponse wh_Timeout + | | | + |-- wh_Client_AesCbc() --------> | | + | |-- wh_Timeout_Start --------> capture time + | | | + | |-- RecvResponse (NOTREADY) | + | |-- Expired? ------------------> no + | |-- RecvResponse (NOTREADY) | + | |-- Expired? ------------------> no + | | ... | + | |-- RecvResponse (NOTREADY) | + | |-- Expired? ------------------> YES + | | |-- expiredCb() + |<-- WH_ERROR_TIMEOUT -----------| | +``` + +## 4. What the Client Sees +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. +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. + +## 5. Scope Limitations +A few things to note about the current design: +- **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`. +- **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. diff --git a/src/wh_client.c b/src/wh_client.c index 132841b7b..d1f4ecbd6 100644 --- a/src/wh_client.c +++ b/src/wh_client.c @@ -77,6 +77,15 @@ int wh_Client_Init(whClientContext* c, const whClientConfig* config) memset(c, 0, sizeof(*c)); +#ifdef WOLFHSM_CFG_ENABLE_TIMEOUT + if (config->respTimeoutConfig != NULL) { + rc = wh_Timeout_Init(&c->respTimeout, config->respTimeoutConfig); + if (rc != WH_ERROR_OK) { + return rc; + } + } +#endif + rc = wh_CommClient_Init(c->comm, config->comm); #ifndef WOLFHSM_CFG_NO_CRYPTO @@ -195,6 +204,37 @@ int wh_Client_RecvResponse(whClientContext *c, return rc; } +#ifdef WOLFHSM_CFG_ENABLE_TIMEOUT +int wh_Client_RecvResponseBlockingWithTimeout(whClientContext* c, + uint16_t* out_group, + uint16_t* out_action, + uint16_t* out_size, void* data) +{ + int ret; + whTimeoutCtx* timeout; + + if (c == NULL) { + return WH_ERROR_BADARGS; + } + + timeout = &c->respTimeout; + + ret = wh_Timeout_Start(timeout); + if (ret != WH_ERROR_OK) { + return ret; + } + + do { + ret = wh_Client_RecvResponse(c, out_group, out_action, out_size, data); + if ((ret == WH_ERROR_NOTREADY) && wh_Timeout_Expired(timeout)) { + return WH_ERROR_TIMEOUT; + } + } while (ret == WH_ERROR_NOTREADY); + + return ret; +} +#endif /* WOLFHSM_CFG_ENABLE_TIMEOUT */ + int wh_Client_CommInitRequest(whClientContext* c) { whMessageCommInitRequest msg = {0}; diff --git a/src/wh_client_crypto.c b/src/wh_client_crypto.c index 3672eac48..d9865f475 100644 --- a/src/wh_client_crypto.c +++ b/src/wh_client_crypto.c @@ -163,6 +163,23 @@ static uint8_t* _createCryptoRequestWithSubtype(uint8_t* reqBuf, uint16_t type, return reqBuf + sizeof(whMessageCrypto_GenericRequestHeader); } +static int _recvCryptoResponse(whClientContext* ctx, uint16_t* group, + uint16_t* action, uint16_t* size, void* data) +{ + int ret; + +#ifdef WOLFHSM_CFG_ENABLE_TIMEOUT + ret = wh_Client_RecvResponseBlockingWithTimeout(ctx, group, action, size, + data); +#else + do { + ret = wh_Client_RecvResponse(ctx, group, action, size, data); + } while (ret == WH_ERROR_NOTREADY); +#endif /* WOLFHSM_CFG_ENABLE_TIMEOUT */ + + return ret; +} + /* Helper function to validate and extract crypto response */ /* TODO: add algoSubType checking */ static int _getCryptoResponse(uint8_t* respBuf, uint16_t type, @@ -233,10 +250,7 @@ int wh_Client_RngGenerate(whClientContext* ctx, uint8_t* out, uint32_t size) /* Send request and get response */ ret = wh_Client_SendRequest(ctx, group, action, req_len, dataPtr); if (ret == 0) { - do { - ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, - dataPtr); - } while (ret == WH_ERROR_NOTREADY); + ret = _recvCryptoResponse(ctx, &group, &action, &res_len, dataPtr); } if (ret == WH_ERROR_OK) { /* Get response */ @@ -311,10 +325,7 @@ int wh_Client_RngGenerateDma(whClientContext* ctx, uint8_t* out, uint32_t size) if (ret == WH_ERROR_OK) { /* Wait for and receive the response */ - do { - ret = wh_Client_RecvResponse(ctx, NULL, NULL, &respSz, - (uint8_t*)dataPtr); - } while (ret == WH_ERROR_NOTREADY); + ret = _recvCryptoResponse(ctx, NULL, NULL, &respSz, (uint8_t*)dataPtr); } if (ret == WH_ERROR_OK) { @@ -414,10 +425,7 @@ int wh_Client_AesCtr(whClientContext* ctx, Aes* aes, int enc, const uint8_t* in, if (ret == WH_ERROR_OK) { /* Response packet */ uint16_t res_len = 0; - do { - ret = - wh_Client_RecvResponse(ctx, &group, &action, &res_len, dataPtr); - } while (ret == WH_ERROR_NOTREADY); + ret = _recvCryptoResponse(ctx, &group, &action, &res_len, dataPtr); if (ret == WH_ERROR_OK) { ret = _getCryptoResponse(dataPtr, type, (uint8_t**)&res); if (ret == WH_ERROR_OK) { @@ -526,10 +534,7 @@ int wh_Client_AesEcb(whClientContext* ctx, Aes* aes, int enc, const uint8_t* in, if (ret == WH_ERROR_OK) { /* Response packet */ uint16_t res_len = 0; - do { - ret = - wh_Client_RecvResponse(ctx, &group, &action, &res_len, dataPtr); - } while (ret == WH_ERROR_NOTREADY); + ret = _recvCryptoResponse(ctx, &group, &action, &res_len, dataPtr); if (ret == WH_ERROR_OK) { ret = _getCryptoResponse(dataPtr, type, (uint8_t**)&res); if (ret == WH_ERROR_OK) { @@ -635,10 +640,7 @@ int wh_Client_AesCbc(whClientContext* ctx, Aes* aes, int enc, const uint8_t* in, if (ret == WH_ERROR_OK) { /* Response packet */ uint16_t res_len = 0; - do { - ret = - wh_Client_RecvResponse(ctx, &group, &action, &res_len, dataPtr); - } while (ret == WH_ERROR_NOTREADY); + ret = _recvCryptoResponse(ctx, &group, &action, &res_len, dataPtr); if (ret == WH_ERROR_OK) { ret = _getCryptoResponse(dataPtr, type, (uint8_t**)&res); if (ret == WH_ERROR_OK) { @@ -756,10 +758,7 @@ int wh_Client_AesGcm(whClientContext* ctx, Aes* aes, int enc, const uint8_t* in, ret = wh_Client_SendRequest(ctx, group, action, req_len, dataPtr); if (ret == 0) { uint16_t res_len = 0; - do { - ret = - wh_Client_RecvResponse(ctx, &group, &action, &res_len, dataPtr); - } while (ret == WH_ERROR_NOTREADY); + ret = _recvCryptoResponse(ctx, &group, &action, &res_len, dataPtr); if (ret == WH_ERROR_OK) { /* Get response */ @@ -956,10 +955,7 @@ int wh_Client_AesGcmDma(whClientContext* ctx, Aes* aes, int enc, } if (ret == 0) { uint16_t resLen = 0; - do { - ret = - wh_Client_RecvResponse(ctx, &group, &action, &resLen, dataPtr); - } while (ret == WH_ERROR_NOTREADY); + ret = _recvCryptoResponse(ctx, &group, &action, &resLen, dataPtr); if (ret == WH_ERROR_OK) { /* Get response */ @@ -1158,10 +1154,8 @@ static int _EccMakeKey(whClientContext* ctx, int size, int curveId, if (ret == WH_ERROR_OK) { /* Response Message */ uint16_t res_len; - do { - ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, - (uint8_t*)dataPtr); - } while (ret == WH_ERROR_NOTREADY); + ret = _recvCryptoResponse(ctx, &group, &action, &res_len, + (uint8_t*)dataPtr); if (ret == WH_ERROR_OK) { /* Get response structure pointer, validates generic header @@ -1322,10 +1316,8 @@ int wh_Client_EccSharedSecret(whClientContext* ctx, ecc_key* priv_key, uint16_t res_len; /* Recv Response */ - do { - ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, - (uint8_t*)dataPtr); - } while (ret == WH_ERROR_NOTREADY); + ret = _recvCryptoResponse(ctx, &group, &action, &res_len, + (uint8_t*)dataPtr); WH_DEBUG_CLIENT_VERBOSE("resp packet recv. ret:%d\n", ret); if (ret == WH_ERROR_OK) { /* Get response structure pointer, validates generic header @@ -1458,10 +1450,8 @@ int wh_Client_EccSign(whClientContext* ctx, ecc_key* key, const uint8_t* hash, uint16_t res_len = 0; /* Recv Response */ - do { - ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, - (uint8_t*)dataPtr); - } while (ret == WH_ERROR_NOTREADY); + ret = _recvCryptoResponse(ctx, &group, &action, &res_len, + (uint8_t*)dataPtr); if (ret == WH_ERROR_OK) { /* Get response structure pointer, validates generic header @@ -1612,10 +1602,8 @@ int wh_Client_EccVerify(whClientContext* ctx, ecc_key* key, const uint8_t* sig, uint16_t res_len = 0; /* Recv Response */ - do { - ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, - (uint8_t*)dataPtr); - } while (ret == WH_ERROR_NOTREADY); + ret = _recvCryptoResponse(ctx, &group, &action, &res_len, + (uint8_t*)dataPtr); if (ret == WH_ERROR_OK) { /* Get response structure pointer, validates generic header * rc */ @@ -1690,10 +1678,8 @@ int wh_Client_EccCheckPubKey(whClientContext* ctx, ecc_key* key, (uint8_t*)packet); /* read response */ if (ret == 0) { - do { - ret = wh_Client_RecvResponse(ctx, &group, &action, &dataSz, - (uint8_t*)packet); - } while (ret == WH_ERROR_NOTREADY); + ret = _recvCryptoResponse(ctx, &group, &action, &dataSz, + (uint8_t*)packet); } if (ret == 0) { if (packet->rc != 0) @@ -1835,10 +1821,8 @@ static int _Curve25519MakeKey(whClientContext* ctx, uint16_t size, WH_DEBUG_CLIENT_VERBOSE("Curve25519 KeyGen Req sent:size:%u, ret:%d\n", (unsigned int)req->sz, ret); if (ret == 0) { - do { - ret = wh_Client_RecvResponse(ctx, &group, &action, &data_len, - (uint8_t*)dataPtr); - } while (ret == WH_ERROR_NOTREADY); + ret = _recvCryptoResponse(ctx, &group, &action, &data_len, + (uint8_t*)dataPtr); } @@ -1994,10 +1978,8 @@ int wh_Client_Curve25519SharedSecret(whClientContext* ctx, pub_evict = prv_evict = 0; /* Recv Response */ - do { - ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, - (uint8_t*)dataPtr); - } while (ret == WH_ERROR_NOTREADY); + ret = _recvCryptoResponse(ctx, &group, &action, &res_len, + (uint8_t*)dataPtr); WH_DEBUG_CLIENT_VERBOSE("resp packet recv. ret:%d\n", ret); if (ret == WH_ERROR_OK) { @@ -2163,10 +2145,8 @@ static int _Ed25519MakeKey(whClientContext* ctx, whKeyId* inout_key_id, return ret; } uint16_t res_len = 0; - do { - ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, - (uint8_t*)dataPtr); - } while (ret == WH_ERROR_NOTREADY); + ret = + _recvCryptoResponse(ctx, &group, &action, &res_len, (uint8_t*)dataPtr); if (ret != WH_ERROR_OK) { return ret; @@ -2308,10 +2288,8 @@ int wh_Client_Ed25519Sign(whClientContext* ctx, ed25519_key* key, evict = 0; uint16_t res_len = 0; - do { - ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, - (uint8_t*)dataPtr); - } while (ret == WH_ERROR_NOTREADY); + ret = _recvCryptoResponse(ctx, &group, &action, &res_len, + (uint8_t*)dataPtr); if (group != WH_MESSAGE_GROUP_CRYPTO || action != WC_ALGO_TYPE_PK) { ret = WH_ERROR_ABORTED; @@ -2446,10 +2424,8 @@ int wh_Client_Ed25519Verify(whClientContext* ctx, ed25519_key* key, evict = 0; uint16_t res_len = 0; - do { - ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, - (uint8_t*)dataPtr); - } while (ret == WH_ERROR_NOTREADY); + ret = _recvCryptoResponse(ctx, &group, &action, &res_len, + (uint8_t*)dataPtr); if (group != WH_MESSAGE_GROUP_CRYPTO || action != WC_ALGO_TYPE_PK) { ret = WH_ERROR_ABORTED; @@ -2581,10 +2557,8 @@ int wh_Client_Ed25519SignDma(whClientContext* ctx, ed25519_key* key, evict = 0; uint16_t res_len = 0; - do { - ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, - (uint8_t*)dataPtr); - } while (ret == WH_ERROR_NOTREADY); + ret = _recvCryptoResponse(ctx, &group, &action, &res_len, + (uint8_t*)dataPtr); if (group != WH_MESSAGE_GROUP_CRYPTO_DMA || action != WC_ALGO_TYPE_PK) { @@ -2728,10 +2702,8 @@ int wh_Client_Ed25519VerifyDma(whClientContext* ctx, ed25519_key* key, evict = 0; uint16_t res_len = 0; - do { - ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, - (uint8_t*)dataPtr); - } while (ret == WH_ERROR_NOTREADY); + ret = _recvCryptoResponse(ctx, &group, &action, &res_len, + (uint8_t*)dataPtr); if (group != WH_MESSAGE_GROUP_CRYPTO_DMA || action != WC_ALGO_TYPE_PK) { @@ -2916,10 +2888,7 @@ static int _RsaMakeKey(whClientContext* ctx, uint32_t size, uint32_t e, (unsigned int)req->size, (unsigned int)req->e, ret); if (ret == 0) { uint16_t res_len = 0; - do { - ret = - wh_Client_RecvResponse(ctx, &group, &action, &res_len, dataPtr); - } while (ret == WH_ERROR_NOTREADY); + ret = _recvCryptoResponse(ctx, &group, &action, &res_len, dataPtr); WH_DEBUG_CLIENT_VERBOSE("RSA KeyGen Res recv: ret:%d, res_len: %u\n", ret, (unsigned int)res_len); @@ -3080,10 +3049,8 @@ int wh_Client_RsaFunction(whClientContext* ctx, RsaKey* key, int rsa_type, uint16_t res_len = 0; /* Recv Response */ - do { - ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, - (uint8_t*)dataPtr); - } while (ret == WH_ERROR_NOTREADY); + ret = _recvCryptoResponse(ctx, &group, &action, &res_len, + (uint8_t*)dataPtr); if (ret == WH_ERROR_OK) { /* Get response */ @@ -3192,10 +3159,8 @@ int wh_Client_RsaGetSize(whClientContext* ctx, const RsaKey* key, int* out_size) uint16_t res_len = 0; /* Recv Response */ - do { - ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, - (uint8_t*)dataPtr); - } while (ret == WH_ERROR_NOTREADY); + ret = _recvCryptoResponse(ctx, &group, &action, &res_len, + (uint8_t*)dataPtr); if (ret == WH_ERROR_OK) { /* Get response */ @@ -3316,10 +3281,7 @@ static int _HkdfMakeKey(whClientContext* ctx, int hashType, whKeyId keyIdIn, if (ret == 0) { uint16_t res_len = 0; - do { - ret = - wh_Client_RecvResponse(ctx, &group, &action, &res_len, dataPtr); - } while (ret == WH_ERROR_NOTREADY); + ret = _recvCryptoResponse(ctx, &group, &action, &res_len, dataPtr); WH_DEBUG_CLIENT_VERBOSE("HKDF Res recv: ret:%d, res_len: %u\n", ret, (unsigned int)res_len); @@ -3483,9 +3445,7 @@ static int _CmacKdfMakeKey(whClientContext* ctx, whKeyId saltKeyId, } uint16_t res_len = 0; - do { - ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, dataPtr); - } while (ret == WH_ERROR_NOTREADY); + ret = _recvCryptoResponse(ctx, &group, &action, &res_len, dataPtr); if (ret == WH_ERROR_OK) { ret = _getCryptoResponse(dataPtr, WC_ALGO_TYPE_KDF, (uint8_t**)&res); @@ -3675,10 +3635,8 @@ int wh_Client_Cmac(whClientContext* ctx, Cmac* cmac, CmacType type, uint16_t res_len = 0; - do { - ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, - (uint8_t*)dataPtr); - } while (ret == WH_ERROR_NOTREADY); + ret = _recvCryptoResponse(ctx, &group, &action, &res_len, + (uint8_t*)dataPtr); if (ret == WH_ERROR_OK) { /* Get response */ ret = @@ -3706,7 +3664,6 @@ int wh_Client_Cmac(whClientContext* ctx, Cmac* cmac, CmacType type, #endif /* !NO_AES */ - #ifdef WOLFHSM_CFG_DMA int wh_Client_CmacDma(whClientContext* ctx, Cmac* cmac, CmacType type, const uint8_t* key, uint32_t keyLen, const uint8_t* in, @@ -3808,11 +3765,7 @@ int wh_Client_CmacDma(whClientContext* ctx, Cmac* cmac, CmacType type, } uint16_t respSz = 0; - do { - ret = wh_Client_RecvResponse(ctx, NULL, NULL, &respSz, - (uint8_t*)dataPtr); - } while (ret == WH_ERROR_NOTREADY); - + ret = _recvCryptoResponse(ctx, NULL, NULL, &respSz, (uint8_t*)dataPtr); if (ret == WH_ERROR_OK) { ret = _getCryptoResponse(dataPtr, WC_ALGO_TYPE_CMAC, (uint8_t**)&res); @@ -3911,10 +3864,8 @@ static int _xferSha256BlockAndUpdateDigest(whClientContext* ctx, WH_DEBUG_CLIENT_VERBOSE(" ret = %d\n", ret); if (ret == 0) { - do { - ret = wh_Client_RecvResponse(ctx, &group, &action, &dataSz, - (uint8_t*)dataPtr); - } while (ret == WH_ERROR_NOTREADY); + ret = _recvCryptoResponse(ctx, &group, &action, &dataSz, + (uint8_t*)dataPtr); } if (ret == 0) { /* Get response */ @@ -4067,10 +4018,8 @@ int wh_Client_Sha256Dma(whClientContext* ctx, wc_Sha256* sha, const uint8_t* in, (uint8_t*)dataPtr); if (ret == WH_ERROR_OK) { - do { - ret = wh_Client_RecvResponse(ctx, NULL, NULL, &respSz, - (uint8_t*)dataPtr); - } while (ret == WH_ERROR_NOTREADY); + ret = _recvCryptoResponse(ctx, NULL, NULL, &respSz, + (uint8_t*)dataPtr); } if (ret == WH_ERROR_OK) { @@ -4096,10 +4045,8 @@ int wh_Client_Sha256Dma(whClientContext* ctx, wc_Sha256* sha, const uint8_t* in, (uint8_t*)dataPtr); if (ret == WH_ERROR_OK) { - do { - ret = wh_Client_RecvResponse(ctx, NULL, NULL, &respSz, - (uint8_t*)dataPtr); - } while (ret == WH_ERROR_NOTREADY); + ret = _recvCryptoResponse(ctx, NULL, NULL, &respSz, + (uint8_t*)dataPtr); } /* Copy out the final hash value */ @@ -4197,10 +4144,8 @@ static int _xferSha224BlockAndUpdateDigest(whClientContext* ctx, WH_DEBUG_CLIENT_VERBOSE(" ret = %d\n", ret); if (ret == 0) { - do { - ret = wh_Client_RecvResponse(ctx, &group, &action, &dataSz, - (uint8_t*)dataPtr); - } while (ret == WH_ERROR_NOTREADY); + ret = _recvCryptoResponse(ctx, &group, &action, &dataSz, + (uint8_t*)dataPtr); } if (ret == 0) { /* Get response */ @@ -4352,10 +4297,8 @@ int wh_Client_Sha224Dma(whClientContext* ctx, wc_Sha224* sha, const uint8_t* in, (uint8_t*)dataPtr); if (ret == WH_ERROR_OK) { - do { - ret = wh_Client_RecvResponse(ctx, NULL, NULL, &respSz, - (uint8_t*)dataPtr); - } while (ret == WH_ERROR_NOTREADY); + ret = _recvCryptoResponse(ctx, NULL, NULL, &respSz, + (uint8_t*)dataPtr); } if (ret == WH_ERROR_OK) { @@ -4382,10 +4325,8 @@ int wh_Client_Sha224Dma(whClientContext* ctx, wc_Sha224* sha, const uint8_t* in, (uint8_t*)dataPtr); if (ret == WH_ERROR_OK) { - do { - ret = wh_Client_RecvResponse(ctx, NULL, NULL, &respSz, - (uint8_t*)dataPtr); - } while (ret == WH_ERROR_NOTREADY); + ret = _recvCryptoResponse(ctx, NULL, NULL, &respSz, + (uint8_t*)dataPtr); } /* Copy out the final hash value */ @@ -4478,10 +4419,8 @@ static int _xferSha384BlockAndUpdateDigest(whClientContext* ctx, WH_DEBUG_CLIENT_VERBOSE(" ret = %d\n", ret); if (ret == 0) { - do { - ret = wh_Client_RecvResponse(ctx, &group, &action, &dataSz, - (uint8_t*)dataPtr); - } while (ret == WH_ERROR_NOTREADY); + ret = _recvCryptoResponse(ctx, &group, &action, &dataSz, + (uint8_t*)dataPtr); } if (ret == 0) { /* Get response */ @@ -4633,10 +4572,8 @@ int wh_Client_Sha384Dma(whClientContext* ctx, wc_Sha384* sha, const uint8_t* in, (uint8_t*)dataPtr); if (ret == WH_ERROR_OK) { - do { - ret = wh_Client_RecvResponse(ctx, NULL, NULL, &respSz, - (uint8_t*)dataPtr); - } while (ret == WH_ERROR_NOTREADY); + ret = _recvCryptoResponse(ctx, NULL, NULL, &respSz, + (uint8_t*)dataPtr); } if (ret == WH_ERROR_OK) { @@ -4663,10 +4600,8 @@ int wh_Client_Sha384Dma(whClientContext* ctx, wc_Sha384* sha, const uint8_t* in, (uint8_t*)dataPtr); if (ret == WH_ERROR_OK) { - do { - ret = wh_Client_RecvResponse(ctx, NULL, NULL, &respSz, - (uint8_t*)dataPtr); - } while (ret == WH_ERROR_NOTREADY); + ret = _recvCryptoResponse(ctx, NULL, NULL, &respSz, + (uint8_t*)dataPtr); } /* Copy out the final hash value */ @@ -4760,10 +4695,8 @@ static int _xferSha512BlockAndUpdateDigest(whClientContext* ctx, WH_DEBUG_CLIENT_VERBOSE(" ret = %d\n", ret); if (ret == 0) { - do { - ret = wh_Client_RecvResponse(ctx, &group, &action, &dataSz, - (uint8_t*)dataPtr); - } while (ret == WH_ERROR_NOTREADY); + ret = _recvCryptoResponse(ctx, &group, &action, &dataSz, + (uint8_t*)dataPtr); } if (ret == 0) { /* Get response */ @@ -4926,10 +4859,8 @@ int wh_Client_Sha512Dma(whClientContext* ctx, wc_Sha512* sha, const uint8_t* in, (uint8_t*)dataPtr); if (ret == WH_ERROR_OK) { - do { - ret = wh_Client_RecvResponse(ctx, NULL, NULL, &respSz, - (uint8_t*)dataPtr); - } while (ret == WH_ERROR_NOTREADY); + ret = _recvCryptoResponse(ctx, NULL, NULL, &respSz, + (uint8_t*)dataPtr); } if (ret == WH_ERROR_OK) { @@ -4956,10 +4887,8 @@ int wh_Client_Sha512Dma(whClientContext* ctx, wc_Sha512* sha, const uint8_t* in, (uint8_t*)dataPtr); if (ret == WH_ERROR_OK) { - do { - ret = wh_Client_RecvResponse(ctx, NULL, NULL, &respSz, - (uint8_t*)dataPtr); - } while (ret == WH_ERROR_NOTREADY); + ret = _recvCryptoResponse(ctx, NULL, NULL, &respSz, + (uint8_t*)dataPtr); } /* Copy out the final hash value */ @@ -5133,10 +5062,8 @@ static int _MlDsaMakeKey(whClientContext* ctx, int size, int level, (unsigned int)req->sz, ret); if (ret == 0) { uint16_t res_len; - do { - ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, - (uint8_t*)dataPtr); - } while (ret == WH_ERROR_NOTREADY); + ret = _recvCryptoResponse(ctx, &group, &action, &res_len, + (uint8_t*)dataPtr); if (ret == WH_ERROR_OK) { /* Get response structure pointer, validates generic header @@ -5294,10 +5221,8 @@ int wh_Client_MlDsaSign(whClientContext* ctx, const byte* in, word32 in_len, uint16_t res_len = 0; /* Recv Response */ - do { - ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, - (uint8_t*)dataPtr); - } while (ret == WH_ERROR_NOTREADY); + ret = _recvCryptoResponse(ctx, &group, &action, &res_len, + (uint8_t*)dataPtr); if (ret == WH_ERROR_OK) { /* Get response structure pointer, validates generic header @@ -5429,10 +5354,8 @@ int wh_Client_MlDsaVerify(whClientContext* ctx, const byte* sig, word32 sig_len, uint16_t res_len = 0; /* Recv Response */ - do { - ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, - (uint8_t*)dataPtr); - } while (ret == WH_ERROR_NOTREADY); + ret = _recvCryptoResponse(ctx, &group, &action, &res_len, + (uint8_t*)dataPtr); if (ret == 0) { /* Get response structure pointer, validates generic header * rc */ @@ -5597,10 +5520,8 @@ static int _MlDsaMakeKeyDma(whClientContext* ctx, int level, } if (ret == WH_ERROR_OK) { uint16_t res_len; - do { - ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, - (uint8_t*)dataPtr); - } while (ret == WH_ERROR_NOTREADY); + ret = _recvCryptoResponse(ctx, &group, &action, &res_len, + (uint8_t*)dataPtr); } (void)wh_Client_DmaProcessClientAddress( @@ -5756,10 +5677,8 @@ int wh_Client_MlDsaSignDma(whClientContext* ctx, const byte* in, word32 in_len, uint16_t res_len = 0; /* Recv Response */ - do { - ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, - (uint8_t*)dataPtr); - } while (ret == WH_ERROR_NOTREADY); + ret = _recvCryptoResponse(ctx, &group, &action, &res_len, + (uint8_t*)dataPtr); if (ret == WH_ERROR_OK) { /* Get response structure pointer, validates generic header @@ -5891,10 +5810,8 @@ int wh_Client_MlDsaVerifyDma(whClientContext* ctx, const byte* sig, uint16_t res_len = 0; /* Recv Response */ - do { - ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, - (uint8_t*)dataPtr); - } while (ret == WH_ERROR_NOTREADY); + ret = _recvCryptoResponse(ctx, &group, &action, &res_len, + (uint8_t*)dataPtr); if (ret == WH_ERROR_OK) { /* Get response structure pointer, validates generic header diff --git a/src/wh_timeout.c b/src/wh_timeout.c new file mode 100644 index 000000000..055d06ce8 --- /dev/null +++ b/src/wh_timeout.c @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHSM. + * + * wolfHSM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHSM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfHSM. If not, see . + */ +/* + * src/wh_timeout.c + */ + +/* Pick up compile-time configuration */ +#include "wolfhsm/wh_settings.h" + +#ifdef WOLFHSM_CFG_ENABLE_TIMEOUT + +#include "wolfhsm/wh_timeout.h" +#include "wolfhsm/wh_error.h" + +int wh_Timeout_Init(whTimeoutCtx* timeout, const whTimeoutConfig* config) +{ + if ((timeout == NULL) || (config == NULL)) { + return WH_ERROR_BADARGS; + } + + timeout->startUs = 0; + timeout->timeoutUs = config->timeoutUs; + timeout->expiredCb = config->expiredCb; + timeout->cbCtx = config->cbCtx; + + return WH_ERROR_OK; +} + +int wh_Timeout_Set(whTimeoutCtx* timeout, uint64_t timeoutUs) +{ + if (timeout == NULL) { + return WH_ERROR_BADARGS; + } + + timeout->timeoutUs = timeoutUs; + + return WH_ERROR_OK; +} + +int wh_Timeout_Start(whTimeoutCtx* timeout) +{ + if (timeout == NULL) { + return WH_ERROR_BADARGS; + } + + timeout->startUs = WH_GETTIME_US(); + + return WH_ERROR_OK; +} + +int wh_Timeout_Stop(whTimeoutCtx* timeout) +{ + if (timeout == NULL) { + return WH_ERROR_BADARGS; + } + + timeout->startUs = 0; + timeout->timeoutUs = 0; + + return WH_ERROR_OK; +} + +int wh_Timeout_Expired(whTimeoutCtx* timeout) +{ + uint64_t nowUs = 0; + int expired = 0; + + if (timeout == NULL) { + return 0; + } + + if (timeout->timeoutUs == 0) { + return 0; + } + + nowUs = WH_GETTIME_US(); + expired = (nowUs - timeout->startUs) >= timeout->timeoutUs; + if (expired && (timeout->expiredCb != NULL)) { + /* Allow the callback to overwrite the expired value */ + timeout->expiredCb(timeout, &expired); + } + return expired; +} + +#endif /* WOLFHSM_CFG_ENABLE_TIMEOUT */ diff --git a/test/config/wolfhsm_cfg.h b/test/config/wolfhsm_cfg.h index 0a4bb8789..d0519a574 100644 --- a/test/config/wolfhsm_cfg.h +++ b/test/config/wolfhsm_cfg.h @@ -65,4 +65,6 @@ /* Allow persistent NVM artifacts in tests */ #define WOLFHSM_CFG_TEST_ALLOW_PERSISTENT_NVM_ARTIFACTS +#define WOLFHSM_CFG_ENABLE_TIMEOUT + #endif /* WOLFHSM_CFG_H_ */ diff --git a/test/wh_test.c b/test/wh_test.c index 3fd9a1bb5..9dbf72448 100644 --- a/test/wh_test.c +++ b/test/wh_test.c @@ -42,6 +42,7 @@ #include "wh_test_log.h" #include "wh_test_lock.h" #include "wh_test_posix_threadsafe_stress.h" +#include "wh_test_timeout.h" #if defined(WOLFHSM_CFG_CERTIFICATE_MANAGER) #include "wh_test_cert.h" @@ -73,6 +74,9 @@ int whTest_Unit(void) /* Component Tests */ WH_TEST_ASSERT(0 == whTest_Flash_RamSim()); WH_TEST_ASSERT(0 == whTest_NvmFlash()); +#ifdef WOLFHSM_CFG_ENABLE_TIMEOUT + WH_TEST_ASSERT(0 == whTest_Timeout()); +#endif #ifdef WOLFHSM_CFG_LOGGING WH_TEST_ASSERT(0 == whTest_Log()); #endif diff --git a/test/wh_test_timeout.c b/test/wh_test_timeout.c new file mode 100644 index 000000000..8a372f60e --- /dev/null +++ b/test/wh_test_timeout.c @@ -0,0 +1,423 @@ +/* + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHSM. + * + * wolfHSM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHSM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfHSM. If not, see . + */ +/* + * test/wh_test_timeout.c + * + */ + +#include +#include + +#include "wolfhsm/wh_settings.h" + +#ifdef WOLFHSM_CFG_ENABLE_TIMEOUT + +#include "wolfhsm/wh_timeout.h" +#include "wolfhsm/wh_error.h" + +#include "wh_test_common.h" +#include "wh_test_timeout.h" + +#if !defined(WOLFHSM_CFG_NO_CRYPTO) +#include "wolfssl/wolfcrypt/settings.h" +#ifdef HAVE_AES_CBC +#include "wolfssl/wolfcrypt/aes.h" +#include "wolfhsm/wh_comm.h" +#include "wolfhsm/wh_transport_mem.h" +#include "wolfhsm/wh_client.h" +#include "wolfhsm/wh_client_crypto.h" +#include "wolfhsm/wh_server.h" +#include "wolfhsm/wh_nvm.h" +#include "wolfhsm/wh_nvm_flash.h" +#include "wolfhsm/wh_flash_ramsim.h" +#endif /* HAVE_AES_CBC */ +#endif /* !WOLFHSM_CFG_NO_CRYPTO */ + +static void whTest_TimeoutCb(whTimeoutCtx* ctx, int* isExpired) +{ + (void)isExpired; + int* counter = (int*)ctx->cbCtx; + if (counter != NULL) { + (*counter)++; + } +} + +#if !defined(WOLFHSM_CFG_NO_CRYPTO) && defined(HAVE_AES_CBC) + +#define TIMEOUT_TEST_BUFFER_SIZE 4096 +#define TIMEOUT_TEST_FLASH_RAM_SIZE (1024 * 1024) +#define TIMEOUT_TEST_FLASH_SECTOR_SIZE (128 * 1024) +#define TIMEOUT_TEST_FLASH_PAGE_SIZE 8 + +static whServerContext* timeoutTestServerCtx = NULL; + +static int _timeoutTestConnectCb(void* context, whCommConnected connected) +{ + (void)context; + + if (timeoutTestServerCtx == NULL) { + WH_ERROR_PRINT( + "Timeout test connect callback server context is NULL\n"); + WH_TEST_ASSERT_RETURN(0); + } + + return wh_Server_SetConnected(timeoutTestServerCtx, connected); +} + +static int whTest_TimeoutAesCbc(void) +{ + int rc = 0; + + /* Transport memory configuration */ + uint8_t req[TIMEOUT_TEST_BUFFER_SIZE] = {0}; + uint8_t resp[TIMEOUT_TEST_BUFFER_SIZE] = {0}; + whTransportMemConfig tmcf[1] = {{ + .req = (whTransportMemCsr*)req, + .req_size = sizeof(req), + .resp = (whTransportMemCsr*)resp, + .resp_size = sizeof(resp), + }}; + + /* Client configuration with timeout */ + whTimeoutConfig timeoutCfg = { + .timeoutUs = 1, + .expiredCb = NULL, + .cbCtx = NULL, + }; + + whTransportClientCb tccb[1] = {WH_TRANSPORT_MEM_CLIENT_CB}; + whTransportMemClientContext tmcc[1] = {0}; + whCommClientConfig cc_conf[1] = {{ + .transport_cb = tccb, + .transport_context = (void*)tmcc, + .transport_config = (void*)tmcf, + .client_id = WH_TEST_DEFAULT_CLIENT_ID, + .connect_cb = _timeoutTestConnectCb, + }}; + whClientConfig c_conf[1] = {{ + .comm = cc_conf, + .respTimeoutConfig = &timeoutCfg, + }}; + whClientContext client[1] = {0}; + + /* Server configuration */ + whTransportServerCb tscb[1] = {WH_TRANSPORT_MEM_SERVER_CB}; + whTransportMemServerContext tmsc[1] = {0}; + whCommServerConfig cs_conf[1] = {{ + .transport_cb = tscb, + .transport_context = (void*)tmsc, + .transport_config = (void*)tmcf, + .server_id = 124, + }}; + + /* Flash/NVM configuration */ + uint8_t flash_memory[TIMEOUT_TEST_FLASH_RAM_SIZE] = {0}; + whFlashRamsimCtx fc[1] = {0}; + whFlashRamsimCfg fc_conf[1] = {{ + .size = TIMEOUT_TEST_FLASH_RAM_SIZE, + .sectorSize = TIMEOUT_TEST_FLASH_SECTOR_SIZE, + .pageSize = TIMEOUT_TEST_FLASH_PAGE_SIZE, + .erasedByte = ~(uint8_t)0, + .memory = flash_memory, + }}; + const whFlashCb fcb[1] = {WH_FLASH_RAMSIM_CB}; + + whTestNvmBackendUnion nvm_setup; + whNvmConfig n_conf[1] = {0}; + whNvmContext nvm[1] = {{0}}; + + WH_TEST_RETURN_ON_FAIL(whTest_NvmCfgBackend( + WH_NVM_TEST_BACKEND_FLASH, &nvm_setup, n_conf, fc_conf, fc, fcb)); + + whServerCryptoContext crypto[1] = {{ + .devId = INVALID_DEVID, + }}; + + whServerConfig s_conf[1] = {{ + .comm_config = cs_conf, + .nvm = nvm, + .crypto = crypto, + .devId = INVALID_DEVID, + }}; + whServerContext server[1] = {0}; + + timeoutTestServerCtx = server; + + WH_TEST_RETURN_ON_FAIL(wolfCrypt_Init()); + WH_TEST_RETURN_ON_FAIL(wh_Nvm_Init(nvm, n_conf)); + WH_TEST_RETURN_ON_FAIL(wc_InitRng_ex(crypto->rng, NULL, INVALID_DEVID)); + + /* Server must be initialized before client (connect callback) */ + WH_TEST_RETURN_ON_FAIL(wh_Server_Init(server, s_conf)); + WH_TEST_RETURN_ON_FAIL(wh_Client_Init(client, c_conf)); + + /* CommInit handshake */ + WH_TEST_RETURN_ON_FAIL(wh_Client_CommInitRequest(client)); + WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server)); + WH_TEST_RETURN_ON_FAIL(wh_Client_CommInitResponse(client, NULL, NULL)); + + /* Set up AES CBC encryption */ + { + Aes aes[1]; + uint8_t key[AES_BLOCK_SIZE] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, + 0x0D, 0x0E, 0x0F, 0x10}; + uint8_t iv[AES_BLOCK_SIZE] = {0}; + uint8_t plain[AES_BLOCK_SIZE] = {0xAA}; + uint8_t cipher[AES_BLOCK_SIZE] = {0}; + + WH_TEST_RETURN_ON_FAIL(wc_AesInit(aes, NULL, WH_DEV_ID)); + WH_TEST_RETURN_ON_FAIL( + wc_AesSetKey(aes, key, sizeof(key), iv, AES_ENCRYPTION)); + + /* Call AES CBC encrypt WITHOUT having server handle the request. + * The client should time out waiting for the response. */ + rc = wh_Client_AesCbc(client, aes, 1, plain, sizeof(plain), cipher); + WH_TEST_ASSERT_RETURN(rc == WH_ERROR_TIMEOUT); + + wc_AesFree(aes); + } + + /* Cleanup: server still has the unhandled request in the transport buffer. + * Handle it before closing so the transport is in a clean state. */ + (void)wh_Server_HandleRequestMessage(server); + + WH_TEST_RETURN_ON_FAIL(wh_Client_CommCloseRequest(client)); + WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server)); + WH_TEST_RETURN_ON_FAIL(wh_Client_CommCloseResponse(client)); + + WH_TEST_RETURN_ON_FAIL(wh_Server_Cleanup(server)); + WH_TEST_RETURN_ON_FAIL(wh_Client_Cleanup(client)); + + wc_FreeRng(crypto->rng); + wh_Nvm_Cleanup(nvm); + wolfCrypt_Cleanup(); + + return WH_ERROR_OK; +} + +/* Callback that overrides expiration on the first invocation by resetting and + * restarting the timeout. On the second invocation it allows expiration. The + * cbCtx points to an int counter tracking how many times the callback fired. */ +static void _timeoutOverrideCb(whTimeoutCtx* ctx, int* isExpired) +{ + int* counter = (int*)ctx->cbCtx; + if (counter == NULL) { + return; + } + + (*counter)++; + + if (*counter <= 1) { + /* First expiration: override and restart the timer */ + *isExpired = 0; + wh_Timeout_Start(ctx); + } + /* Subsequent expirations: let it expire normally */ +} + +static int whTest_TimeoutAesCbcOverride(void) +{ + int rc = 0; + int cb_count = 0; + + /* Transport memory configuration */ + uint8_t req[TIMEOUT_TEST_BUFFER_SIZE] = {0}; + uint8_t resp[TIMEOUT_TEST_BUFFER_SIZE] = {0}; + whTransportMemConfig tmcf[1] = {{ + .req = (whTransportMemCsr*)req, + .req_size = sizeof(req), + .resp = (whTransportMemCsr*)resp, + .resp_size = sizeof(resp), + }}; + + /* Client configuration with timeout and override callback */ + whTimeoutConfig timeoutCfg = { + .timeoutUs = 1, + .expiredCb = _timeoutOverrideCb, + .cbCtx = &cb_count, + }; + + whTransportClientCb tccb[1] = {WH_TRANSPORT_MEM_CLIENT_CB}; + whTransportMemClientContext tmcc[1] = {0}; + whCommClientConfig cc_conf[1] = {{ + .transport_cb = tccb, + .transport_context = (void*)tmcc, + .transport_config = (void*)tmcf, + .client_id = WH_TEST_DEFAULT_CLIENT_ID, + .connect_cb = _timeoutTestConnectCb, + }}; + whClientConfig c_conf[1] = {{ + .comm = cc_conf, + .respTimeoutConfig = &timeoutCfg, + }}; + whClientContext client[1] = {0}; + + /* Server configuration */ + whTransportServerCb tscb[1] = {WH_TRANSPORT_MEM_SERVER_CB}; + whTransportMemServerContext tmsc[1] = {0}; + whCommServerConfig cs_conf[1] = {{ + .transport_cb = tscb, + .transport_context = (void*)tmsc, + .transport_config = (void*)tmcf, + .server_id = 124, + }}; + + /* Flash/NVM configuration */ + uint8_t flash_memory[TIMEOUT_TEST_FLASH_RAM_SIZE] = {0}; + whFlashRamsimCtx fc[1] = {0}; + whFlashRamsimCfg fc_conf[1] = {{ + .size = TIMEOUT_TEST_FLASH_RAM_SIZE, + .sectorSize = TIMEOUT_TEST_FLASH_SECTOR_SIZE, + .pageSize = TIMEOUT_TEST_FLASH_PAGE_SIZE, + .erasedByte = ~(uint8_t)0, + .memory = flash_memory, + }}; + const whFlashCb fcb[1] = {WH_FLASH_RAMSIM_CB}; + + whTestNvmBackendUnion nvm_setup; + whNvmConfig n_conf[1] = {0}; + whNvmContext nvm[1] = {{0}}; + + WH_TEST_RETURN_ON_FAIL(whTest_NvmCfgBackend( + WH_NVM_TEST_BACKEND_FLASH, &nvm_setup, n_conf, fc_conf, fc, fcb)); + + whServerCryptoContext crypto[1] = {{ + .devId = INVALID_DEVID, + }}; + + whServerConfig s_conf[1] = {{ + .comm_config = cs_conf, + .nvm = nvm, + .crypto = crypto, + .devId = INVALID_DEVID, + }}; + whServerContext server[1] = {0}; + + timeoutTestServerCtx = server; + + WH_TEST_RETURN_ON_FAIL(wolfCrypt_Init()); + WH_TEST_RETURN_ON_FAIL(wh_Nvm_Init(nvm, n_conf)); + WH_TEST_RETURN_ON_FAIL(wc_InitRng_ex(crypto->rng, NULL, INVALID_DEVID)); + + /* Server must be initialized before client (connect callback) */ + WH_TEST_RETURN_ON_FAIL(wh_Server_Init(server, s_conf)); + WH_TEST_RETURN_ON_FAIL(wh_Client_Init(client, c_conf)); + + /* CommInit handshake */ + WH_TEST_RETURN_ON_FAIL(wh_Client_CommInitRequest(client)); + WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server)); + WH_TEST_RETURN_ON_FAIL(wh_Client_CommInitResponse(client, NULL, NULL)); + + /* Set up AES CBC encryption */ + { + Aes aes[1]; + uint8_t key[AES_BLOCK_SIZE] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, + 0x0D, 0x0E, 0x0F, 0x10}; + uint8_t iv[AES_BLOCK_SIZE] = {0}; + uint8_t plain[AES_BLOCK_SIZE] = {0xAA}; + uint8_t cipher[AES_BLOCK_SIZE] = {0}; + + WH_TEST_RETURN_ON_FAIL(wc_AesInit(aes, NULL, WH_DEV_ID)); + WH_TEST_RETURN_ON_FAIL( + wc_AesSetKey(aes, key, sizeof(key), iv, AES_ENCRYPTION)); + + /* Call AES CBC encrypt WITHOUT having server handle the request. + * The override callback will suppress the first expiration, reset and + * restart the timer. On the second expiration it lets it through. */ + rc = wh_Client_AesCbc(client, aes, 1, plain, sizeof(plain), cipher); + WH_TEST_ASSERT_RETURN(rc == WH_ERROR_TIMEOUT); + + /* The callback should have fired twice: once overridden, once expired + */ + WH_TEST_ASSERT_RETURN(cb_count == 2); + + wc_AesFree(aes); + } + + /* Cleanup */ + (void)wh_Server_HandleRequestMessage(server); + + WH_TEST_RETURN_ON_FAIL(wh_Client_CommCloseRequest(client)); + WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server)); + WH_TEST_RETURN_ON_FAIL(wh_Client_CommCloseResponse(client)); + + WH_TEST_RETURN_ON_FAIL(wh_Server_Cleanup(server)); + WH_TEST_RETURN_ON_FAIL(wh_Client_Cleanup(client)); + + wc_FreeRng(crypto->rng); + wh_Nvm_Cleanup(nvm); + wolfCrypt_Cleanup(); + + return WH_ERROR_OK; +} + +#endif /* !WOLFHSM_CFG_NO_CRYPTO && HAVE_AES_CBC */ + +int whTest_Timeout(void) +{ + int cb_count = 0; + whTimeoutConfig cfg; + whTimeoutCtx timeout[1]; + + cfg.timeoutUs = 1; + cfg.expiredCb = whTest_TimeoutCb; + cfg.cbCtx = &cb_count; + + wh_Timeout_Init(timeout, &cfg); + WH_TEST_ASSERT_RETURN(timeout->startUs == 0); + WH_TEST_ASSERT_RETURN(timeout->timeoutUs == cfg.timeoutUs); + WH_TEST_ASSERT_RETURN(timeout->expiredCb == cfg.expiredCb); + WH_TEST_ASSERT_RETURN(timeout->cbCtx == cfg.cbCtx); + + wh_Timeout_Start(timeout); + WH_TEST_ASSERT_RETURN(timeout->timeoutUs > 0); + + wh_Timeout_Stop(timeout); + WH_TEST_ASSERT_RETURN(timeout->startUs == 0); + WH_TEST_ASSERT_RETURN(timeout->timeoutUs == 0); + + /* No expiration when disabled */ + WH_TEST_ASSERT_RETURN(wh_Timeout_Expired(timeout) == 0); + + /* Test expired callback fires and increments counter */ + cb_count = 0; + wh_Timeout_Init(timeout, &cfg); + wh_Timeout_Start(timeout); + /* timeoutUs is 1 us, so spin until expired */ + while (wh_Timeout_Expired(timeout) == 0) + ; + WH_TEST_ASSERT_RETURN(cb_count > 0); + + WH_TEST_ASSERT_RETURN(wh_Timeout_Init(0, 0) == WH_ERROR_BADARGS); + WH_TEST_ASSERT_RETURN(wh_Timeout_Set(0, 0) == WH_ERROR_BADARGS); + WH_TEST_ASSERT_RETURN(wh_Timeout_Start(0) == WH_ERROR_BADARGS); + WH_TEST_ASSERT_RETURN(wh_Timeout_Stop(0) == WH_ERROR_BADARGS); + WH_TEST_ASSERT_RETURN(wh_Timeout_Expired(0) == 0); + +#if !defined(WOLFHSM_CFG_NO_CRYPTO) && defined(HAVE_AES_CBC) + WH_TEST_RETURN_ON_FAIL(whTest_TimeoutAesCbc()); + WH_TEST_RETURN_ON_FAIL(whTest_TimeoutAesCbcOverride()); +#endif + + return 0; +} + +#endif /* WOLFHSM_CFG_ENABLE_TIMEOUT */ diff --git a/test/wh_test_timeout.h b/test/wh_test_timeout.h new file mode 100644 index 000000000..d8ebebb36 --- /dev/null +++ b/test/wh_test_timeout.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHSM. + * + * wolfHSM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHSM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfHSM. If not, see . + */ +/* + * test/wh_test_timeout.h + * + */ + +#ifndef TEST_WH_TEST_TIMEOUT_H_ +#define TEST_WH_TEST_TIMEOUT_H_ + +/** + * Runs timeout module tests. + * + * @return 0 on success and a non-zero error code on failure. + */ +int whTest_Timeout(void); + +#endif /* TEST_WH_TEST_TIMEOUT_H_ */ diff --git a/wolfhsm/wh_client.h b/wolfhsm/wh_client.h index 2990ed297..6336c8b0b 100644 --- a/wolfhsm/wh_client.h +++ b/wolfhsm/wh_client.h @@ -48,6 +48,7 @@ /* Component includes */ #include "wolfhsm/wh_comm.h" +#include "wolfhsm/wh_timeout.h" #include "wolfhsm/wh_message_customcb.h" #ifdef WOLFHSM_CFG_DMA #include "wolfhsm/wh_dma.h" @@ -108,6 +109,9 @@ typedef struct { struct whClientContext_t { uint16_t last_req_id; uint16_t last_req_kind; +#ifdef WOLFHSM_CFG_ENABLE_TIMEOUT + whTimeoutCtx respTimeout; +#endif #ifdef WOLFHSM_CFG_DMA whClientDmaContext dma; #endif /* WOLFHSM_CFG_DMA */ @@ -119,6 +123,9 @@ struct whClientConfig_t { #ifdef WOLFHSM_CFG_DMA whClientDmaConfig* dmaConfig; #endif /* WOLFHSM_CFG_DMA */ +#ifdef WOLFHSM_CFG_ENABLE_TIMEOUT + whTimeoutConfig* respTimeoutConfig; +#endif /* WOLFHSM_CFG_ENABLE_TIMEOUT*/ }; typedef struct whClientConfig_t whClientConfig; @@ -177,7 +184,22 @@ int wh_Client_SendRequest(whClientContext* c, uint16_t group, uint16_t action, int wh_Client_RecvResponse(whClientContext* c, uint16_t* out_group, uint16_t* out_action, uint16_t* out_size, void* data); - +/** + * Receives a response from the server with a timeout window. The timeout + * duration is specified by the respTimeout field in the client context. + * + * @param c The client context. + * @param out_group Pointer to store the received group value. + * @param out_action Pointer to store the received action value. + * @param out_size Pointer to store the received size value. + * @param data Pointer to store the received data. + * @return 0 if successful, WH_ERROR_TIMEOUT on expiration, or a negative value + * if an error occurred. + */ +int wh_Client_RecvResponseBlockingWithTimeout(whClientContext* c, + uint16_t* out_group, + uint16_t* out_action, + uint16_t* out_size, void* data); /** Comm component functions */ diff --git a/wolfhsm/wh_error.h b/wolfhsm/wh_error.h index 5ce75cdde..50d522691 100644 --- a/wolfhsm/wh_error.h +++ b/wolfhsm/wh_error.h @@ -45,6 +45,7 @@ enum WH_ERROR_ENUM { compile-time configuration */ WH_ERROR_USAGE = -2009, /* Operation not permitted based on object/key usage flags */ + WH_ERROR_TIMEOUT = -2010, /* Timeout occurred. */ /* NVM and keystore specific status returns */ WH_ERROR_LOCKED = -2100, /* Unlock and retry if necessary */ diff --git a/wolfhsm/wh_settings.h b/wolfhsm/wh_settings.h index 701a18e0d..e9e54be88 100644 --- a/wolfhsm/wh_settings.h +++ b/wolfhsm/wh_settings.h @@ -57,6 +57,9 @@ * WOLFHSM_CFG_ENABLE_SERVER - If defined, include server-specific * functionality * + * WOLFHSM_CFG_ENABLE_TIMEOUT - If defined, include client-side support for + * blocking request timeouts + * * WOLFHSM_CFG_NVM_OBJECT_COUNT - Number of objects in ram and disk directories * Default: 32 * diff --git a/wolfhsm/wh_timeout.h b/wolfhsm/wh_timeout.h new file mode 100644 index 000000000..fedeb8b77 --- /dev/null +++ b/wolfhsm/wh_timeout.h @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHSM. + * + * wolfHSM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHSM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfHSM. If not, see . + */ +/* + * wolfhsm/wh_timeout.h + * + * Generic timeout helpers based on WH_GETTIME_US(). + */ + +#ifndef WOLFHSM_WH_TIMEOUT_H_ +#define WOLFHSM_WH_TIMEOUT_H_ + +/* Pick up compile-time configuration */ +#include "wolfhsm/wh_settings.h" + +#define WH_MSEC_TO_USEC(usec) ((usec) * (1000ULL)) +#define WH_SEC_TO_USEC(sec) ((sec) * (1000000ULL)) +#define WH_MIN_TO_USEC(min) ((min) * (WH_SEC_TO_USEC(60))) + +#include + +/* Forward declare so the callback typedef can reference it */ +typedef struct whTimeoutCtx whTimeoutCtx; +typedef void (*whTimeoutExpiredCb)(whTimeoutCtx* ctx, int* isExpired); + +struct whTimeoutCtx { + uint64_t startUs; + uint64_t timeoutUs; + whTimeoutExpiredCb expiredCb; + void* cbCtx; +}; + +typedef struct { + uint64_t timeoutUs; + whTimeoutExpiredCb expiredCb; + void* cbCtx; +} whTimeoutConfig; + +/** + * Initialize a timeout context from a configuration. + * + * @param timeout The timeout context to initialize. + * @param config The timeout configuration to apply. + * @return 0 on success, WH_ERROR_BADARGS on invalid input. + */ +int wh_Timeout_Init(whTimeoutCtx* timeout, const whTimeoutConfig* config); + +/** + * Configure a timeout value. + * + * @param timeout The timeout context to update. + * @param timeoutUs Timeout duration in microseconds; 0 disables the timeout. + * @return 0 on success, WH_ERROR_BADARGS on invalid input. + */ +int wh_Timeout_Set(whTimeoutCtx* timeout, uint64_t timeoutUs); + +/** + * Start or reset a timeout window using the configured timeoutUs. + * + * @param timeout The timeout context to start. + * @return 0 on success, WH_ERROR_BADARGS on invalid input. + */ +int wh_Timeout_Start(whTimeoutCtx* timeout); + +/** + * Disable a timeout and clear its bookkeeping. + * + * @param timeout The timeout context to stop. + * @return 0 on success, WH_ERROR_BADARGS on invalid input. + */ +int wh_Timeout_Stop(whTimeoutCtx* timeout); + +/** + * Check whether a timeout has expired. + * + * If the timeout is expired and an expired callback is configured, the + * callback is invoked before returning. + * + * @param timeout The timeout context to check. + * @return 1 if expired, 0 if not expired or disabled. + */ +int wh_Timeout_Expired(whTimeoutCtx* timeout); + +#endif /* !WOLFHSM_WH_TIMEOUT_H_ */