From 4a912ead56ba5eee4a80e3ca49e271d80453546b Mon Sep 17 00:00:00 2001 From: Alex Lanzano Date: Wed, 28 Jan 2026 17:27:25 -0500 Subject: [PATCH 01/17] Implement timeout capability. Apply timeout to crypto response --- src/wh_client.c | 34 +++++ src/wh_client_crypto.c | 282 ++++++++++++++------------------------ src/wh_timeout.c | 96 +++++++++++++ test/config/wolfhsm_cfg.h | 2 + test/wh_test.c | 4 + test/wh_test_timeout.c | 74 ++++++++++ test/wh_test_timeout.h | 34 +++++ wolfhsm/wh_client.h | 24 ++++ wolfhsm/wh_error.h | 1 + wolfhsm/wh_settings.h | 3 + wolfhsm/wh_timeout.h | 94 +++++++++++++ 11 files changed, 467 insertions(+), 181 deletions(-) create mode 100644 src/wh_timeout.c create mode 100644 test/wh_test_timeout.c create mode 100644 test/wh_test_timeout.h create mode 100644 wolfhsm/wh_timeout.h diff --git a/src/wh_client.c b/src/wh_client.c index 2d6c2f867..fd6d80d59 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 != 0) { + return rc; + } + } +#endif + rc = wh_CommClient_Init(c->comm, config->comm); #ifndef WOLFHSM_CFG_NO_CRYPTO @@ -195,6 +204,31 @@ int wh_Client_RecvResponse(whClientContext *c, return rc; } +int wh_Client_RecvResponseTimeout(whClientContext *c, + uint16_t *out_group, uint16_t *out_action, + uint16_t *out_size, void* data, whTimeoutCtx *timeout) +{ + int ret; + + if ((c == NULL) || (timeout == NULL)) { + return WH_ERROR_BADARGS; + } + + 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; +} + int wh_Client_CommInitRequest(whClientContext* c) { whMessageCommInitRequest msg = {0}; diff --git a/src/wh_client_crypto.c b/src/wh_client_crypto.c index 40bab2b33..210fc4abc 100644 --- a/src/wh_client_crypto.c +++ b/src/wh_client_crypto.c @@ -169,6 +169,24 @@ 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_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 /* 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, @@ -240,10 +258,8 @@ 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 */ @@ -318,10 +334,8 @@ 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) { @@ -421,10 +435,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) { @@ -880,10 +891,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) { @@ -993,15 +1001,9 @@ int wh_Client_AesCbcDma(whClientContext* ctx, Aes* aes, int enc, WH_DEBUG_VERBOSE_HEXDUMP("[client] AESCBC DMA req packet: \n", dataPtr, req_len); if (ret == WH_ERROR_OK) { - ret = wh_Client_SendRequest(ctx, group, action, req_len, dataPtr); - } - if (ret == WH_ERROR_OK) { - uint16_t resLen = 0; - do { - ret = - wh_Client_RecvResponse(ctx, &group, &action, &resLen, dataPtr); - } while (ret == WH_ERROR_NOTREADY); - + /* Response packet */ + uint16_t res_len = 0; + ret = _recvCryptoResponse(ctx, &group, &action, &res_len, dataPtr); if (ret == WH_ERROR_OK) { /* Get response */ whMessageCrypto_AesCbcDmaResponse* res; @@ -1244,10 +1246,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 == WH_ERROR_OK) { 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 */ @@ -1419,10 +1418,7 @@ int wh_Client_AesGcmDma(whClientContext* ctx, Aes* aes, int enc, } if (ret == WH_ERROR_OK) { 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 */ @@ -1611,10 +1607,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 @@ -1775,10 +1769,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 @@ -1911,10 +1903,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 @@ -2065,10 +2055,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 */ @@ -2143,10 +2131,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) @@ -2288,10 +2274,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); } @@ -2447,10 +2431,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) { @@ -2616,10 +2598,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; @@ -2761,10 +2741,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; @@ -2899,10 +2877,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; @@ -3035,10 +3011,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) { @@ -3182,10 +3156,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) { @@ -3370,10 +3342,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); @@ -3534,10 +3503,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 */ @@ -3646,10 +3613,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 */ @@ -3770,10 +3735,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); @@ -3938,9 +3900,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); @@ -4130,10 +4090,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 = @@ -4161,7 +4119,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, @@ -4263,10 +4220,9 @@ 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 = @@ -4366,10 +4322,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 */ @@ -4522,10 +4476,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) { @@ -4551,10 +4503,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 */ @@ -4652,10 +4602,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 */ @@ -4807,10 +4755,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) { @@ -4837,10 +4783,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 */ @@ -4933,10 +4877,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 */ @@ -5088,10 +5030,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) { @@ -5118,10 +5058,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 */ @@ -5215,10 +5153,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 */ @@ -5381,10 +5317,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) { @@ -5411,10 +5345,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 */ @@ -5589,10 +5521,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 @@ -5751,10 +5681,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 @@ -5887,10 +5815,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 */ @@ -6056,10 +5982,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( @@ -6216,10 +6140,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 @@ -6352,10 +6274,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..3c92ab3a4 --- /dev/null +++ b/src/wh_timeout.c @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2025 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" + +#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(const 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)) { + timeout->expiredCb(timeout->cbCtx); + } + return expired; +} 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 c97a36897..73f4eccf2 100644 --- a/test/wh_test.c +++ b/test/wh_test.c @@ -43,6 +43,7 @@ #include "wh_test_lock.h" #include "wh_test_posix_threadsafe_stress.h" #include "wh_test_crypto_affinity.h" +#include "wh_test_timeout.h" #if defined(WOLFHSM_CFG_CERTIFICATE_MANAGER) #include "wh_test_cert.h" @@ -74,6 +75,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..7dff3c5e5 --- /dev/null +++ b/test/wh_test_timeout.c @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2024 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 "wolfhsm/wh_settings.h" +#include "wolfhsm/wh_timeout.h" +#include "wolfhsm/wh_error.h" + +#include "wh_test_common.h" +#include "wh_test_timeout.h" + +static void whTest_TimeoutCb(void* ctx) +{ + int* counter = (int*)ctx; + if (counter != NULL) { + (*counter)++; + } +} + +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); + + 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); + + return 0; +} diff --git a/test/wh_test_timeout.h b/test/wh_test_timeout.h new file mode 100644 index 000000000..a863887fc --- /dev/null +++ b/test/wh_test_timeout.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2024 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 f56bdbfbe..ef80055b7 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" @@ -109,6 +110,9 @@ struct whClientContext_t { uint16_t last_req_id; uint16_t last_req_kind; uint32_t cryptoAffinity; +#ifdef WOLFHSM_CFG_ENABLE_TIMEOUT + whTimeoutCtx respTimeout[1]; +#endif #ifdef WOLFHSM_CFG_DMA whClientDmaContext dma; #endif /* WOLFHSM_CFG_DMA */ @@ -120,6 +124,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; @@ -178,6 +185,23 @@ 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); +#ifdef WOLFHSM_CFG_ENABLE_TIMEOUT +/** + * Receives a response from the server with a timeout window. + * + * @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. + * @param timeout The timeout context to use. + * @return 0 if successful, WH_ERROR_TIMEOUT on expiration, or a negative value + * if an error occurred. + */ +int wh_Client_RecvResponseTimeout(whClientContext* c, uint16_t* out_group, + uint16_t* out_action, uint16_t* out_size, + void* data, whTimeoutCtx* timeout); +#endif /* WOLFHSM_CFG_ENABLE_TIMEOUT */ /** Comm component functions */ diff --git a/wolfhsm/wh_error.h b/wolfhsm/wh_error.h index 5ce75cdde..09ccd11df 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..180ea4535 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 timeout helpers and + * client response timeout support. + * * 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..fbbc946b5 --- /dev/null +++ b/wolfhsm/wh_timeout.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2025 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(sec) (sec * 1000ULL) +#define WH_SEC_TO_USEC(sec) (sec * 1000000ULL) +#define WH_MIN_TO_USEC(sec) (sec * WH_SEC_TO_USEC(60)) + +#include + +typedef void (*whTimeoutExpiredCb)(void* ctx); + +typedef struct { + uint64_t startUs; + uint64_t timeoutUs; + whTimeoutExpiredCb expiredCb; + void* cbCtx; +} whTimeoutCtx; + +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. + * + * @param timeout The timeout context to check. + * @return 1 if expired, 0 if not expired or disabled. + */ +int wh_Timeout_Expired(const whTimeoutCtx* timeout); + +#endif /* !WOLFHSM_WH_TIMEOUT_H_ */ From c47a70ec2d1e222440cc8f10f483b0ace5186dac Mon Sep 17 00:00:00 2001 From: Alex Lanzano Date: Fri, 30 Jan 2026 15:19:47 -0500 Subject: [PATCH 02/17] Address comments on #278 --- wolfhsm/wh_timeout.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/wolfhsm/wh_timeout.h b/wolfhsm/wh_timeout.h index fbbc946b5..a086bd019 100644 --- a/wolfhsm/wh_timeout.h +++ b/wolfhsm/wh_timeout.h @@ -28,9 +28,9 @@ /* Pick up compile-time configuration */ #include "wolfhsm/wh_settings.h" -#define WH_MSEC_TO_USEC(sec) (sec * 1000ULL) +#define WH_MSEC_TO_USEC(usec) (usec * 1000ULL) #define WH_SEC_TO_USEC(sec) (sec * 1000000ULL) -#define WH_MIN_TO_USEC(sec) (sec * WH_SEC_TO_USEC(60)) +#define WH_MIN_TO_USEC(min) (min * WH_SEC_TO_USEC(60)) #include @@ -86,6 +86,9 @@ 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. */ From 9b9d74aec40c992f8ac5975d4200b59ec86ba1a4 Mon Sep 17 00:00:00 2001 From: Alex Lanzano Date: Wed, 4 Feb 2026 14:51:07 -0500 Subject: [PATCH 03/17] Add draft documentation for timeout feature --- docs/draft/timeout.md | 88 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 docs/draft/timeout.md 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. From e3ffa0f2d13ffe85b2b6dba73a293efac95fa1f7 Mon Sep 17 00:00:00 2001 From: Alex Lanzano Date: Thu, 5 Feb 2026 10:43:03 -0500 Subject: [PATCH 04/17] Update wolfhsm/wh_settings.h Co-authored-by: Brett Nicholas <7547222+bigbrett@users.noreply.github.com> --- wolfhsm/wh_settings.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/wolfhsm/wh_settings.h b/wolfhsm/wh_settings.h index 180ea4535..0744aaa54 100644 --- a/wolfhsm/wh_settings.h +++ b/wolfhsm/wh_settings.h @@ -57,8 +57,7 @@ * WOLFHSM_CFG_ENABLE_SERVER - If defined, include server-specific * functionality * - * WOLFHSM_CFG_ENABLE_TIMEOUT - If defined, include timeout helpers and - * client response timeout support. + * 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 From b024c3408a9ab54f056efdbce2d17249ef8aa514 Mon Sep 17 00:00:00 2001 From: Alex Lanzano Date: Thu, 5 Feb 2026 12:30:50 -0500 Subject: [PATCH 05/17] Address comments on #278 --- src/wh_client.c | 15 ++++++++++----- src/wh_client_crypto.c | 3 +-- src/wh_timeout.c | 2 +- test/wh_test_timeout.c | 2 +- test/wh_test_timeout.h | 2 +- wolfhsm/wh_client.h | 15 ++++++--------- wolfhsm/wh_timeout.h | 2 +- 7 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/wh_client.c b/src/wh_client.c index fd6d80d59..a6b1c8566 100644 --- a/src/wh_client.c +++ b/src/wh_client.c @@ -79,8 +79,8 @@ int wh_Client_Init(whClientContext* c, const whClientConfig* config) #ifdef WOLFHSM_CFG_ENABLE_TIMEOUT if (config->respTimeoutConfig != NULL) { - rc = wh_Timeout_Init(c->respTimeout, config->respTimeoutConfig); - if (rc != 0) { + rc = wh_Timeout_Init(&c->respTimeout, config->respTimeoutConfig); + if (rc != WH_ERROR_OK) { return rc; } } @@ -204,16 +204,21 @@ int wh_Client_RecvResponse(whClientContext *c, return rc; } -int wh_Client_RecvResponseTimeout(whClientContext *c, +int wh_Client_RecvResponseBlockingWithTimeout(whClientContext *c, uint16_t *out_group, uint16_t *out_action, - uint16_t *out_size, void* data, whTimeoutCtx *timeout) + uint16_t *out_size, void* data) { int ret; + whTimeoutCtx* timeout; - if ((c == NULL) || (timeout == NULL)) { + if (c == NULL) { return WH_ERROR_BADARGS; } +#ifdef WOLFHSM_CFG_ENABLE_TIMEOUT + timeout = &c->respTimeout; +#endif + ret = wh_Timeout_Start(timeout); if (ret != WH_ERROR_OK) { return ret; diff --git a/src/wh_client_crypto.c b/src/wh_client_crypto.c index 210fc4abc..37829f5c4 100644 --- a/src/wh_client_crypto.c +++ b/src/wh_client_crypto.c @@ -176,8 +176,7 @@ static int _recvCryptoResponse(whClientContext* ctx, int ret; #ifdef WOLFHSM_CFG_ENABLE_TIMEOUT - ret = wh_Client_RecvResponseTimeout(ctx, group, action, size, data, - ctx->respTimeout); + ret = wh_Client_RecvResponseBlockingWithTimeout(ctx, group, action, size, data); #else do { ret = wh_Client_RecvResponse(ctx, group, action, size, data); diff --git a/src/wh_timeout.c b/src/wh_timeout.c index 3c92ab3a4..58aa25fc4 100644 --- a/src/wh_timeout.c +++ b/src/wh_timeout.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2025 wolfSSL Inc. + * Copyright (C) 2026 wolfSSL Inc. * * This file is part of wolfHSM. * diff --git a/test/wh_test_timeout.c b/test/wh_test_timeout.c index 7dff3c5e5..f347472a1 100644 --- a/test/wh_test_timeout.c +++ b/test/wh_test_timeout.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 wolfSSL Inc. + * Copyright (C) 2026 wolfSSL Inc. * * This file is part of wolfHSM. * diff --git a/test/wh_test_timeout.h b/test/wh_test_timeout.h index a863887fc..d8ebebb36 100644 --- a/test/wh_test_timeout.h +++ b/test/wh_test_timeout.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 wolfSSL Inc. + * Copyright (C) 2026 wolfSSL Inc. * * This file is part of wolfHSM. * diff --git a/wolfhsm/wh_client.h b/wolfhsm/wh_client.h index ef80055b7..561cb4f46 100644 --- a/wolfhsm/wh_client.h +++ b/wolfhsm/wh_client.h @@ -111,7 +111,7 @@ struct whClientContext_t { uint16_t last_req_kind; uint32_t cryptoAffinity; #ifdef WOLFHSM_CFG_ENABLE_TIMEOUT - whTimeoutCtx respTimeout[1]; + whTimeoutCtx respTimeout; #endif #ifdef WOLFHSM_CFG_DMA whClientDmaContext dma; @@ -185,24 +185,21 @@ 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); -#ifdef WOLFHSM_CFG_ENABLE_TIMEOUT /** - * Receives a response from the server with a timeout window. + * 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. - * @param timeout The timeout context to use. * @return 0 if successful, WH_ERROR_TIMEOUT on expiration, or a negative value * if an error occurred. */ -int wh_Client_RecvResponseTimeout(whClientContext* c, uint16_t* out_group, - uint16_t* out_action, uint16_t* out_size, - void* data, whTimeoutCtx* timeout); -#endif /* WOLFHSM_CFG_ENABLE_TIMEOUT */ - +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_timeout.h b/wolfhsm/wh_timeout.h index a086bd019..a2e846a0b 100644 --- a/wolfhsm/wh_timeout.h +++ b/wolfhsm/wh_timeout.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2025 wolfSSL Inc. + * Copyright (C) 2026 wolfSSL Inc. * * This file is part of wolfHSM. * From 58f260cf0ef2d82cfd103ea6d058d0a47c2e3e9e Mon Sep 17 00:00:00 2001 From: Alex Lanzano Date: Thu, 5 Feb 2026 13:36:23 -0500 Subject: [PATCH 06/17] git-clang-format changes --- src/wh_client.c | 13 +++++++------ src/wh_client_crypto.c | 23 ++++++++++------------- src/wh_timeout.c | 12 ++++++------ test/wh_test_timeout.c | 6 +++--- wolfhsm/wh_client.h | 7 ++++--- wolfhsm/wh_error.h | 2 +- wolfhsm/wh_settings.h | 3 ++- wolfhsm/wh_timeout.h | 10 +++++----- 8 files changed, 38 insertions(+), 38 deletions(-) diff --git a/src/wh_client.c b/src/wh_client.c index a6b1c8566..4f1e2470a 100644 --- a/src/wh_client.c +++ b/src/wh_client.c @@ -204,20 +204,20 @@ int wh_Client_RecvResponse(whClientContext *c, return rc; } -int wh_Client_RecvResponseBlockingWithTimeout(whClientContext *c, - uint16_t *out_group, uint16_t *out_action, - uint16_t *out_size, void* data) +#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; + int ret; whTimeoutCtx* timeout; if (c == NULL) { return WH_ERROR_BADARGS; } -#ifdef WOLFHSM_CFG_ENABLE_TIMEOUT timeout = &c->respTimeout; -#endif ret = wh_Timeout_Start(timeout); if (ret != WH_ERROR_OK) { @@ -233,6 +233,7 @@ int wh_Client_RecvResponseBlockingWithTimeout(whClientContext *c, return ret; } +#endif /* WOLFHSM_CFG_ENABLE_TIMEOUT */ int wh_Client_CommInitRequest(whClientContext* c) { diff --git a/src/wh_client_crypto.c b/src/wh_client_crypto.c index 37829f5c4..b33415f58 100644 --- a/src/wh_client_crypto.c +++ b/src/wh_client_crypto.c @@ -169,14 +169,14 @@ 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) +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); + ret = wh_Client_RecvResponseBlockingWithTimeout(ctx, group, action, size, + data); #else do { ret = wh_Client_RecvResponse(ctx, group, action, size, data); @@ -257,8 +257,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) { - ret = _recvCryptoResponse(ctx, &group, &action, &res_len, - dataPtr); + ret = _recvCryptoResponse(ctx, &group, &action, &res_len, dataPtr); } if (ret == WH_ERROR_OK) { /* Get response */ @@ -333,8 +332,7 @@ int wh_Client_RngGenerateDma(whClientContext* ctx, uint8_t* out, uint32_t size) if (ret == WH_ERROR_OK) { /* Wait for and receive the response */ - ret = _recvCryptoResponse(ctx, NULL, NULL, &respSz, - (uint8_t*)dataPtr); + ret = _recvCryptoResponse(ctx, NULL, NULL, &respSz, (uint8_t*)dataPtr); } if (ret == WH_ERROR_OK) { @@ -2597,8 +2595,8 @@ static int _Ed25519MakeKey(whClientContext* ctx, whKeyId* inout_key_id, return ret; } uint16_t res_len = 0; - ret = _recvCryptoResponse(ctx, &group, &action, &res_len, - (uint8_t*)dataPtr); + ret = + _recvCryptoResponse(ctx, &group, &action, &res_len, (uint8_t*)dataPtr); if (ret != WH_ERROR_OK) { return ret; @@ -4089,8 +4087,8 @@ int wh_Client_Cmac(whClientContext* ctx, Cmac* cmac, CmacType type, uint16_t res_len = 0; - ret = _recvCryptoResponse(ctx, &group, &action, &res_len, - (uint8_t*)dataPtr); + ret = _recvCryptoResponse(ctx, &group, &action, &res_len, + (uint8_t*)dataPtr); if (ret == WH_ERROR_OK) { /* Get response */ ret = @@ -4222,7 +4220,6 @@ int wh_Client_CmacDma(whClientContext* ctx, Cmac* cmac, CmacType type, ret = _recvCryptoResponse(ctx, NULL, NULL, &respSz, (uint8_t*)dataPtr); - if (ret == WH_ERROR_OK) { ret = _getCryptoResponse(dataPtr, WC_ALGO_TYPE_CMAC, (uint8_t**)&res); diff --git a/src/wh_timeout.c b/src/wh_timeout.c index 58aa25fc4..2dfc3add8 100644 --- a/src/wh_timeout.c +++ b/src/wh_timeout.c @@ -32,10 +32,10 @@ int wh_Timeout_Init(whTimeoutCtx* timeout, const whTimeoutConfig* config) return WH_ERROR_BADARGS; } - timeout->startUs = 0; + timeout->startUs = 0; timeout->timeoutUs = config->timeoutUs; timeout->expiredCb = config->expiredCb; - timeout->cbCtx = config->cbCtx; + timeout->cbCtx = config->cbCtx; return WH_ERROR_OK; } @@ -68,7 +68,7 @@ int wh_Timeout_Stop(whTimeoutCtx* timeout) return WH_ERROR_BADARGS; } - timeout->startUs = 0; + timeout->startUs = 0; timeout->timeoutUs = 0; return WH_ERROR_OK; @@ -76,8 +76,8 @@ int wh_Timeout_Stop(whTimeoutCtx* timeout) int wh_Timeout_Expired(const whTimeoutCtx* timeout) { - uint64_t nowUs = 0; - int expired = 0; + uint64_t nowUs = 0; + int expired = 0; if (timeout == NULL) { return 0; @@ -87,7 +87,7 @@ int wh_Timeout_Expired(const whTimeoutCtx* timeout) return 0; } - nowUs = WH_GETTIME_US(); + nowUs = WH_GETTIME_US(); expired = (nowUs - timeout->startUs) >= timeout->timeoutUs; if (expired && (timeout->expiredCb != NULL)) { timeout->expiredCb(timeout->cbCtx); diff --git a/test/wh_test_timeout.c b/test/wh_test_timeout.c index f347472a1..c6457cef7 100644 --- a/test/wh_test_timeout.c +++ b/test/wh_test_timeout.c @@ -40,13 +40,13 @@ static void whTest_TimeoutCb(void* ctx) int whTest_Timeout(void) { - int cb_count = 0; + int cb_count = 0; whTimeoutConfig cfg; - whTimeoutCtx timeout[1]; + whTimeoutCtx timeout[1]; cfg.timeoutUs = 1; cfg.expiredCb = whTest_TimeoutCb; - cfg.cbCtx = &cb_count; + cfg.cbCtx = &cb_count; wh_Timeout_Init(timeout, &cfg); WH_TEST_ASSERT_RETURN(timeout->startUs == 0); diff --git a/wolfhsm/wh_client.h b/wolfhsm/wh_client.h index 561cb4f46..c09a297ce 100644 --- a/wolfhsm/wh_client.h +++ b/wolfhsm/wh_client.h @@ -197,9 +197,10 @@ int wh_Client_RecvResponse(whClientContext* c, uint16_t* out_group, * @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); +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 09ccd11df..50d522691 100644 --- a/wolfhsm/wh_error.h +++ b/wolfhsm/wh_error.h @@ -45,7 +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. */ + 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 0744aaa54..1119e16e4 100644 --- a/wolfhsm/wh_settings.h +++ b/wolfhsm/wh_settings.h @@ -57,7 +57,8 @@ * 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_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 index a2e846a0b..16fb8c6c2 100644 --- a/wolfhsm/wh_timeout.h +++ b/wolfhsm/wh_timeout.h @@ -37,16 +37,16 @@ typedef void (*whTimeoutExpiredCb)(void* ctx); typedef struct { - uint64_t startUs; - uint64_t timeoutUs; + uint64_t startUs; + uint64_t timeoutUs; whTimeoutExpiredCb expiredCb; - void* cbCtx; + void* cbCtx; } whTimeoutCtx; typedef struct { - uint64_t timeoutUs; + uint64_t timeoutUs; whTimeoutExpiredCb expiredCb; - void* cbCtx; + void* cbCtx; } whTimeoutConfig; /** From cd50c93eaf3ab553bc0ffe69a165532a92e1162a Mon Sep 17 00:00:00 2001 From: Alex Lanzano Date: Fri, 6 Feb 2026 15:06:07 -0500 Subject: [PATCH 07/17] Address comments --- src/wh_timeout.c | 9 +- test/wh_test_timeout.c | 353 ++++++++++++++++++++++++++++++++++++++++- wolfhsm/wh_settings.h | 2 +- wolfhsm/wh_timeout.h | 16 +- 4 files changed, 368 insertions(+), 12 deletions(-) diff --git a/src/wh_timeout.c b/src/wh_timeout.c index 2dfc3add8..055d06ce8 100644 --- a/src/wh_timeout.c +++ b/src/wh_timeout.c @@ -23,6 +23,8 @@ /* 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" @@ -74,7 +76,7 @@ int wh_Timeout_Stop(whTimeoutCtx* timeout) return WH_ERROR_OK; } -int wh_Timeout_Expired(const whTimeoutCtx* timeout) +int wh_Timeout_Expired(whTimeoutCtx* timeout) { uint64_t nowUs = 0; int expired = 0; @@ -90,7 +92,10 @@ int wh_Timeout_Expired(const whTimeoutCtx* timeout) nowUs = WH_GETTIME_US(); expired = (nowUs - timeout->startUs) >= timeout->timeoutUs; if (expired && (timeout->expiredCb != NULL)) { - timeout->expiredCb(timeout->cbCtx); + /* Allow the callback to overwrite the expired value */ + timeout->expiredCb(timeout, &expired); } return expired; } + +#endif /* WOLFHSM_CFG_ENABLE_TIMEOUT */ diff --git a/test/wh_test_timeout.c b/test/wh_test_timeout.c index c6457cef7..8a372f60e 100644 --- a/test/wh_test_timeout.c +++ b/test/wh_test_timeout.c @@ -22,22 +22,355 @@ */ #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" -static void whTest_TimeoutCb(void* ctx) +#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) { - int* counter = (int*)ctx; + (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; @@ -64,11 +397,27 @@ int whTest_Timeout(void) /* 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/wolfhsm/wh_settings.h b/wolfhsm/wh_settings.h index 1119e16e4..e9e54be88 100644 --- a/wolfhsm/wh_settings.h +++ b/wolfhsm/wh_settings.h @@ -58,7 +58,7 @@ * functionality * * WOLFHSM_CFG_ENABLE_TIMEOUT - If defined, include client-side support for - blocking request timeouts + * 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 index 16fb8c6c2..fedeb8b77 100644 --- a/wolfhsm/wh_timeout.h +++ b/wolfhsm/wh_timeout.h @@ -28,20 +28,22 @@ /* 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)) +#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 -typedef void (*whTimeoutExpiredCb)(void* ctx); +/* Forward declare so the callback typedef can reference it */ +typedef struct whTimeoutCtx whTimeoutCtx; +typedef void (*whTimeoutExpiredCb)(whTimeoutCtx* ctx, int* isExpired); -typedef struct { +struct whTimeoutCtx { uint64_t startUs; uint64_t timeoutUs; whTimeoutExpiredCb expiredCb; void* cbCtx; -} whTimeoutCtx; +}; typedef struct { uint64_t timeoutUs; @@ -92,6 +94,6 @@ int wh_Timeout_Stop(whTimeoutCtx* timeout); * @param timeout The timeout context to check. * @return 1 if expired, 0 if not expired or disabled. */ -int wh_Timeout_Expired(const whTimeoutCtx* timeout); +int wh_Timeout_Expired(whTimeoutCtx* timeout); #endif /* !WOLFHSM_WH_TIMEOUT_H_ */ From 93e7ba5104d4e6b133f27f32b8b9b9da64b0d6b6 Mon Sep 17 00:00:00 2001 From: Alex Lanzano Date: Tue, 17 Feb 2026 15:46:10 -0500 Subject: [PATCH 08/17] Allow timeout expired callback to return an error Change the whTimeoutExpiredCb return type from void to int so callbacks can signal errors. wh_Timeout_Expired now propagates any non-zero callback return value to the caller. Update tests to match the new signature and add WH_TEST_PRINT to timeout test functions. Document the expiration override mechanism in both the doxygen headers and the timeout draft docs. --- docs/draft/timeout.md | 35 ++++++++++++++++++++++++++++++++++- src/wh_client_crypto.c | 4 ++++ src/wh_timeout.c | 9 +++++++-- test/wh_test_timeout.c | 11 ++++++++--- wolfhsm/wh_timeout.h | 24 +++++++++++++++++++++--- 5 files changed, 74 insertions(+), 9 deletions(-) diff --git a/docs/draft/timeout.md b/docs/draft/timeout.md index 815720f85..57c09653c 100644 --- a/docs/draft/timeout.md +++ b/docs/draft/timeout.md @@ -82,7 +82,40 @@ Client App _recvCryptoResponse wh_Timeout 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 +## 5. Overriding Expiration via the Callback + +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. + +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. + +```c +static int myOverrideCb(whTimeoutCtx* ctx, int* isExpired) +{ + int* retryCount = (int*)ctx->cbCtx; + if (retryCount == NULL) { + return WH_ERROR_BADARGS; + } + + (*retryCount)++; + + if (*retryCount <= 1) { + /* First expiration: suppress and restart the timer */ + *isExpired = 0; + wh_Timeout_Start(ctx); + } + /* Subsequent expirations: allow the timeout to fire */ + return WH_ERROR_OK; +} + +int retryCount = 0; +whTimeoutConfig timeoutCfg = { + .timeoutUs = WH_SEC_TO_USEC(5), + .expiredCb = myOverrideCb, + .cbCtx = &retryCount, +}; +``` + +## 6. 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_crypto.c b/src/wh_client_crypto.c index b33415f58..9652480eb 100644 --- a/src/wh_client_crypto.c +++ b/src/wh_client_crypto.c @@ -997,6 +997,10 @@ int wh_Client_AesCbcDma(whClientContext* ctx, Aes* aes, int enc, /* Send request and receive response */ WH_DEBUG_VERBOSE_HEXDUMP("[client] AESCBC DMA req packet: \n", dataPtr, req_len); + if (ret == WH_ERROR_OK) { + ret = wh_Client_SendRequest(ctx, group, action, req_len, dataPtr); + } + if (ret == WH_ERROR_OK) { /* Response packet */ uint16_t res_len = 0; diff --git a/src/wh_timeout.c b/src/wh_timeout.c index 055d06ce8..dc085f538 100644 --- a/src/wh_timeout.c +++ b/src/wh_timeout.c @@ -80,6 +80,7 @@ int wh_Timeout_Expired(whTimeoutCtx* timeout) { uint64_t nowUs = 0; int expired = 0; + int ret = 0; if (timeout == NULL) { return 0; @@ -92,8 +93,12 @@ int wh_Timeout_Expired(whTimeoutCtx* timeout) 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); + /* Allow the callback to overwrite the expired value. If the callback + * returns an error, propagate it to the caller. */ + ret = timeout->expiredCb(timeout, &expired); + if (ret != WH_ERROR_OK) { + return ret; + } } return expired; } diff --git a/test/wh_test_timeout.c b/test/wh_test_timeout.c index 8a372f60e..8c79205ca 100644 --- a/test/wh_test_timeout.c +++ b/test/wh_test_timeout.c @@ -49,13 +49,14 @@ #endif /* HAVE_AES_CBC */ #endif /* !WOLFHSM_CFG_NO_CRYPTO */ -static void whTest_TimeoutCb(whTimeoutCtx* ctx, int* isExpired) +static int whTest_TimeoutCb(whTimeoutCtx* ctx, int* isExpired) { (void)isExpired; int* counter = (int*)ctx->cbCtx; if (counter != NULL) { (*counter)++; } + return WH_ERROR_OK; } #if !defined(WOLFHSM_CFG_NO_CRYPTO) && defined(HAVE_AES_CBC) @@ -82,6 +83,7 @@ static int _timeoutTestConnectCb(void* context, whCommConnected connected) static int whTest_TimeoutAesCbc(void) { + WH_TEST_PRINT("Testing timeout AES CBC...\n"); int rc = 0; /* Transport memory configuration */ @@ -215,11 +217,11 @@ static int whTest_TimeoutAesCbc(void) /* 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) +static int _timeoutOverrideCb(whTimeoutCtx* ctx, int* isExpired) { int* counter = (int*)ctx->cbCtx; if (counter == NULL) { - return; + return WH_ERROR_BADARGS; } (*counter)++; @@ -230,10 +232,12 @@ static void _timeoutOverrideCb(whTimeoutCtx* ctx, int* isExpired) wh_Timeout_Start(ctx); } /* Subsequent expirations: let it expire normally */ + return WH_ERROR_OK; } static int whTest_TimeoutAesCbcOverride(void) { + WH_TEST_PRINT("Testing timeout AES CBC with override callback...\n"); int rc = 0; int cb_count = 0; @@ -373,6 +377,7 @@ static int whTest_TimeoutAesCbcOverride(void) int whTest_Timeout(void) { + WH_TEST_PRINT("Testing timeout...\n"); int cb_count = 0; whTimeoutConfig cfg; whTimeoutCtx timeout[1]; diff --git a/wolfhsm/wh_timeout.h b/wolfhsm/wh_timeout.h index fedeb8b77..5d6b76660 100644 --- a/wolfhsm/wh_timeout.h +++ b/wolfhsm/wh_timeout.h @@ -36,7 +36,20 @@ /* Forward declare so the callback typedef can reference it */ typedef struct whTimeoutCtx whTimeoutCtx; -typedef void (*whTimeoutExpiredCb)(whTimeoutCtx* ctx, int* isExpired); + +/** + * Callback invoked when a timeout expires. The callback may override the + * expiration by setting *isExpired to 0 (e.g. to extend the timeout by calling + * wh_Timeout_Start() to restart the timer). Returning a non-zero error code + * from the callback will cause wh_Timeout_Expired() to propagate that error + * to its caller. + * + * @param ctx The timeout context that expired. + * @param isExpired Pointer to the expired flag; set to 0 to suppress + * expiration. + * @return 0 on success, or a negative error code to signal failure. + */ +typedef int (*whTimeoutExpiredCb)(whTimeoutCtx* ctx, int* isExpired); struct whTimeoutCtx { uint64_t startUs; @@ -89,10 +102,15 @@ 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. + * callback is invoked before returning. The callback receives a pointer to the + * expired flag and may set it to 0 to override (suppress) the expiration, for + * example to extend the deadline by calling wh_Timeout_Start() to restart the + * timer. If the callback returns a non-zero error code, that error is + * propagated directly to the caller. * * @param timeout The timeout context to check. - * @return 1 if expired, 0 if not expired or disabled. + * @return 1 if expired, 0 if not expired or disabled, or negative error code + * from the expired callback. */ int wh_Timeout_Expired(whTimeoutCtx* timeout); From 04ebfbf42ef3efd2419162e25962e851fc086d25 Mon Sep 17 00:00:00 2001 From: Alex Lanzano Date: Tue, 3 Mar 2026 10:16:15 -0500 Subject: [PATCH 09/17] Refactor timeout feature to use callbacks --- docs/draft/timeout.md | 74 +++++++++------ port/posix/posix_timeout.c | 159 +++++++++++++++++++++++++++++++ port/posix/posix_timeout.h | 73 ++++++++++++++ src/wh_client.c | 17 +++- src/wh_timeout.c | 116 +++++++++++++++++----- test/Makefile | 2 +- test/wh_test_timeout.c | 86 +++++++++++------ wolfhsm/wh_client.h | 2 +- wolfhsm/wh_timeout.h | 190 +++++++++++++++++++++++++++---------- 9 files changed, 584 insertions(+), 135 deletions(-) create mode 100644 port/posix/posix_timeout.c create mode 100644 port/posix/posix_timeout.h diff --git a/docs/draft/timeout.md b/docs/draft/timeout.md index 57c09653c..2dde50279 100644 --- a/docs/draft/timeout.md +++ b/docs/draft/timeout.md @@ -2,26 +2,35 @@ ## 1. Configuration at Init Time -When creating a client, you provide a `whTimeoutConfig` specifying the timeout duration and an optional callback: +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. + +When creating a client, you provide a `whTimeoutConfig` specifying the platform callbacks, platform context, and an optional application-level expired callback: ```c +/* Platform-specific setup (e.g. POSIX) */ +posixTimeoutContext posixCtx = {0}; +posixTimeoutConfig posixCfg = {.timeoutUs = WH_SEC_TO_USEC(5)}; +whTimeoutCb timeoutCbTable = POSIX_TIMEOUT_CB; + whTimeoutConfig timeoutCfg = { - .timeoutUs = WH_SEC_TO_USEC(5), /* 5-second timeout */ - .expiredCb = myTimeoutHandler, /* optional callback on expiry */ - .cbCtx = myAppContext, /* context passed to callback */ + .cb = &timeoutCbTable, /* platform callback table */ + .context = &posixCtx, /* platform context */ + .config = &posixCfg, /* platform-specific config */ + .expiredCb = myTimeoutHandler, /* optional app callback on expiry */ + .expiredCtx = myAppContext, /* context passed to app callback */ }; whClientConfig clientCfg = { .comm = &commConfig, - .respTimeoutConfig = &timeoutCfg, /* attach timeout config */ + .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"). +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. +If `respTimeoutConfig` is NULL (or `cb` is NULL), the timeout is disabled and all operations become no-ops (timeout 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: +Before the timeout feature, every crypto function in `wh_client_crypto.c` had this pattern after sending a request: ```c /* Old pattern -- infinite busy-wait */ do { @@ -30,7 +39,7 @@ do { ``` 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`): +This is replaced with a single helper `_recvCryptoResponse()` (`src/wh_client_crypto.c`): ```c static int _recvCryptoResponse(whClientContext* ctx, uint16_t* group, uint16_t* action, @@ -38,8 +47,8 @@ static int _recvCryptoResponse(whClientContext* ctx, { int ret; #ifdef WOLFHSM_CFG_ENABLE_TIMEOUT - ret = wh_Client_RecvResponseTimeout(ctx, group, action, size, data, - ctx->respTimeout); + ret = wh_Client_RecvResponseBlockingWithTimeout(ctx, group, action, + size, data); #else do { ret = wh_Client_RecvResponse(ctx, group, action, size, data); @@ -49,31 +58,30 @@ static int _recvCryptoResponse(whClientContext* ctx, } ``` -When timeout is enabled, it delegates to `wh_Client_RecvResponseTimeout`. When disabled, the old infinite-loop behavior is preserved. +When timeout is enabled, it delegates to `wh_Client_RecvResponseBlockingWithTimeout`. 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`. +`wh_Client_RecvResponseBlockingWithTimeout` (`src/wh_client.c`) does this: +1. **Starts the timer** -- calls `wh_Timeout_Start()` which delegates to the platform `start` callback (e.g. captures the current time). 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` + - Delegates to the platform `expired` callback to check elapsed time + - If expired: invokes the application `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 +Client App _recvCryptoResponse whTimeout | | | |-- wh_Client_AesCbc() --------> | | - | |-- wh_Timeout_Start --------> capture time + | |-- wh_Timeout_Start --------> cb->start() | | | | |-- RecvResponse (NOTREADY) | - | |-- Expired? ------------------> no + | |-- Expired? -------> cb->expired() -> no | |-- RecvResponse (NOTREADY) | - | |-- Expired? ------------------> no + | |-- Expired? -------> cb->expired() -> no | | ... | | |-- RecvResponse (NOTREADY) | - | |-- Expired? ------------------> YES + | |-- Expired? -------> cb->expired() -> YES | | |-- expiredCb() |<-- WH_ERROR_TIMEOUT -----------| | ``` @@ -84,14 +92,14 @@ The `expiredCb` fires *before* the error is returned, so you can use it for logg ## 5. Overriding Expiration via the Callback -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. +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. 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. ```c -static int myOverrideCb(whTimeoutCtx* ctx, int* isExpired) +static int myOverrideCb(whTimeout* timeout, int* isExpired) { - int* retryCount = (int*)ctx->cbCtx; + int* retryCount = (int*)timeout->expiredCtx; if (retryCount == NULL) { return WH_ERROR_BADARGS; } @@ -101,21 +109,27 @@ static int myOverrideCb(whTimeoutCtx* ctx, int* isExpired) if (*retryCount <= 1) { /* First expiration: suppress and restart the timer */ *isExpired = 0; - wh_Timeout_Start(ctx); + wh_Timeout_Start(timeout); } /* Subsequent expirations: allow the timeout to fire */ return WH_ERROR_OK; } int retryCount = 0; +posixTimeoutContext posixCtx = {0}; +posixTimeoutConfig posixCfg = {.timeoutUs = WH_SEC_TO_USEC(5)}; +whTimeoutCb timeoutCbTable = POSIX_TIMEOUT_CB; + whTimeoutConfig timeoutCfg = { - .timeoutUs = WH_SEC_TO_USEC(5), - .expiredCb = myOverrideCb, - .cbCtx = &retryCount, + .cb = &timeoutCbTable, + .context = &posixCtx, + .config = &posixCfg, + .expiredCb = myOverrideCb, + .expiredCtx = &retryCount, }; ``` ## 6. 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. +- **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. diff --git a/port/posix/posix_timeout.c b/port/posix/posix_timeout.c new file mode 100644 index 000000000..3be623d55 --- /dev/null +++ b/port/posix/posix_timeout.c @@ -0,0 +1,159 @@ +/* + * 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 . + */ +/* + * port/posix/posix_timeout.c + * + * POSIX implementation of the wolfHSM timeout abstraction. + * Uses posixGetTime() from posix_time.h for time measurement. + */ + +#include "wolfhsm/wh_settings.h" + +#ifdef WOLFHSM_CFG_ENABLE_TIMEOUT + +#include + +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_timeout.h" + +#include "port/posix/posix_time.h" +#include "port/posix/posix_timeout.h" + +int posixTimeout_Init(void* context, const void* config) +{ + posixTimeoutContext* ctx = (posixTimeoutContext*)context; + const posixTimeoutConfig* cfg = (const posixTimeoutConfig*)config; + + if (ctx == NULL) { + return WH_ERROR_BADARGS; + } + + /* Already initialized? */ + if (ctx->initialized) { + return WH_ERROR_OK; + } + + ctx->startUs = 0; + ctx->timeoutUs = (cfg != NULL) ? cfg->timeoutUs : 0; + ctx->started = 0; + + ctx->initialized = 1; + return WH_ERROR_OK; +} + +int posixTimeout_Cleanup(void* context) +{ + posixTimeoutContext* ctx = (posixTimeoutContext*)context; + + if (ctx == NULL) { + return WH_ERROR_BADARGS; + } + + /* Not initialized? */ + if (!ctx->initialized) { + return WH_ERROR_OK; + } + + ctx->startUs = 0; + ctx->timeoutUs = 0; + ctx->started = 0; + ctx->initialized = 0; + + return WH_ERROR_OK; +} + +int posixTimeout_Set(void* context, uint64_t timeoutUs) +{ + posixTimeoutContext* ctx = (posixTimeoutContext*)context; + + if (ctx == NULL) { + return WH_ERROR_BADARGS; + } + + if (!ctx->initialized) { + return WH_ERROR_NOTREADY; + } + + ctx->timeoutUs = timeoutUs; + + return WH_ERROR_OK; +} + +int posixTimeout_Start(void* context) +{ + posixTimeoutContext* ctx = (posixTimeoutContext*)context; + + if (ctx == NULL) { + return WH_ERROR_BADARGS; + } + + if (!ctx->initialized) { + return WH_ERROR_NOTREADY; + } + + ctx->startUs = posixGetTime(); + ctx->started = 1; + + return WH_ERROR_OK; +} + +int posixTimeout_Stop(void* context) +{ + posixTimeoutContext* ctx = (posixTimeoutContext*)context; + + if (ctx == NULL) { + return WH_ERROR_BADARGS; + } + + if (!ctx->initialized) { + return WH_ERROR_NOTREADY; + } + + ctx->startUs = 0; + ctx->started = 0; + + return WH_ERROR_OK; +} + +int posixTimeout_Expired(void* context, int* expired) +{ + posixTimeoutContext* ctx = (posixTimeoutContext*)context; + uint64_t nowUs; + + if ((ctx == NULL) || (expired == NULL)) { + return WH_ERROR_BADARGS; + } + + if (!ctx->initialized) { + return WH_ERROR_NOTREADY; + } + + /* Not started or no timeout configured = not expired */ + if (!ctx->started || (ctx->timeoutUs == 0)) { + *expired = 0; + return WH_ERROR_OK; + } + + nowUs = posixGetTime(); + *expired = ((nowUs - ctx->startUs) >= ctx->timeoutUs) ? 1 : 0; + + return WH_ERROR_OK; +} + +#endif /* WOLFHSM_CFG_ENABLE_TIMEOUT */ diff --git a/port/posix/posix_timeout.h b/port/posix/posix_timeout.h new file mode 100644 index 000000000..5bc818c71 --- /dev/null +++ b/port/posix/posix_timeout.h @@ -0,0 +1,73 @@ +/* + * 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 . + */ +/* + * port/posix/posix_timeout.h + * + * POSIX implementation of the wolfHSM timeout abstraction. + * Uses posixGetTime() for time measurement. + */ + +#ifndef PORT_POSIX_POSIX_TIMEOUT_H_ +#define PORT_POSIX_POSIX_TIMEOUT_H_ + +#include "wolfhsm/wh_settings.h" + +#ifdef WOLFHSM_CFG_ENABLE_TIMEOUT + +#include + +#include "wolfhsm/wh_timeout.h" + +/* Configuration for POSIX timeout backend */ +typedef struct posixTimeoutConfig_t { + uint64_t timeoutUs; /* Timeout duration in microseconds; 0 = no timeout */ +} posixTimeoutConfig; + +/* Context structure holding timer state */ +typedef struct posixTimeoutContext_t { + uint64_t startUs; /* Snapshot of start time */ + uint64_t timeoutUs; /* Configured timeout duration */ + int started; /* 1 if timer is running, 0 otherwise */ + int initialized; /* 1 if initialized, 0 otherwise */ +} posixTimeoutContext; + +/* Callback functions matching whTimeoutCb interface */ +int posixTimeout_Init(void* context, const void* config); +int posixTimeout_Cleanup(void* context); +int posixTimeout_Set(void* context, uint64_t timeoutUs); +int posixTimeout_Start(void* context); +int posixTimeout_Stop(void* context); +int posixTimeout_Expired(void* context, int* expired); + +/* Convenience macro for callback table initialization */ +/* clang-format off */ +#define POSIX_TIMEOUT_CB \ + { \ + .init = posixTimeout_Init, \ + .cleanup = posixTimeout_Cleanup, \ + .set = posixTimeout_Set, \ + .start = posixTimeout_Start, \ + .stop = posixTimeout_Stop, \ + .expired = posixTimeout_Expired, \ + } +/* clang-format on */ + +#endif /* WOLFHSM_CFG_ENABLE_TIMEOUT */ + +#endif /* !PORT_POSIX_POSIX_TIMEOUT_H_ */ diff --git a/src/wh_client.c b/src/wh_client.c index 4f1e2470a..c89a2fd61 100644 --- a/src/wh_client.c +++ b/src/wh_client.c @@ -138,6 +138,10 @@ int wh_Client_Cleanup(whClientContext* c) (void)wolfCrypt_Cleanup(); #endif /* !WOLFHSM_CFG_NO_CRYPTO */ +#ifdef WOLFHSM_CFG_ENABLE_TIMEOUT + (void)wh_Timeout_Cleanup(&c->respTimeout); +#endif + (void)wh_CommClient_Cleanup(c->comm); memset(c, 0, sizeof(*c)); @@ -210,8 +214,8 @@ int wh_Client_RecvResponseBlockingWithTimeout(whClientContext* c, uint16_t* out_action, uint16_t* out_size, void* data) { - int ret; - whTimeoutCtx* timeout; + int ret; + whTimeout* timeout; if (c == NULL) { return WH_ERROR_BADARGS; @@ -219,6 +223,15 @@ int wh_Client_RecvResponseBlockingWithTimeout(whClientContext* c, timeout = &c->respTimeout; + /* If no timeout configured, fall back to standard blocking loop */ + if (timeout->cb == NULL) { + do { + ret = wh_Client_RecvResponse(c, out_group, out_action, out_size, + data); + } while (ret == WH_ERROR_NOTREADY); + return ret; + } + ret = wh_Timeout_Start(timeout); if (ret != WH_ERROR_OK) { return ret; diff --git a/src/wh_timeout.c b/src/wh_timeout.c index dc085f538..f95f8c820 100644 --- a/src/wh_timeout.c +++ b/src/wh_timeout.c @@ -18,6 +18,9 @@ */ /* * src/wh_timeout.c + * + * Platform-agnostic timeout abstraction. Each wrapper validates arguments, + * checks initialization state, and delegates to platform callbacks. */ /* Pick up compile-time configuration */ @@ -25,73 +28,141 @@ #ifdef WOLFHSM_CFG_ENABLE_TIMEOUT +#include /* For NULL */ +#include /* For memset */ + #include "wolfhsm/wh_timeout.h" #include "wolfhsm/wh_error.h" -int wh_Timeout_Init(whTimeoutCtx* timeout, const whTimeoutConfig* config) +int wh_Timeout_Init(whTimeout* timeout, const whTimeoutConfig* config) { - if ((timeout == NULL) || (config == NULL)) { + int ret = WH_ERROR_OK; + + if (timeout == NULL) { return WH_ERROR_BADARGS; } - timeout->startUs = 0; - timeout->timeoutUs = config->timeoutUs; - timeout->expiredCb = config->expiredCb; - timeout->cbCtx = config->cbCtx; + /* Allow NULL config for disabled mode (no-op timeout) */ + if ((config == NULL) || (config->cb == NULL)) { + timeout->cb = NULL; + timeout->context = NULL; + timeout->expiredCb = NULL; + timeout->expiredCtx = NULL; + timeout->initialized = 1; /* Mark as initialized even in no-op mode */ + return WH_ERROR_OK; + } + + timeout->cb = config->cb; + timeout->context = config->context; + timeout->expiredCb = config->expiredCb; + timeout->expiredCtx = config->expiredCtx; + /* Initialize the platform timeout if callback provided */ + if (timeout->cb->init != NULL) { + ret = timeout->cb->init(timeout->context, config->config); + if (ret != WH_ERROR_OK) { + timeout->cb = NULL; + timeout->context = NULL; + /* Do not set initialized on failure */ + return ret; + } + } + + timeout->initialized = 1; return WH_ERROR_OK; } -int wh_Timeout_Set(whTimeoutCtx* timeout, uint64_t timeoutUs) +int wh_Timeout_Cleanup(whTimeout* timeout) { if (timeout == NULL) { return WH_ERROR_BADARGS; } - timeout->timeoutUs = timeoutUs; + if ((timeout->cb != NULL) && (timeout->cb->cleanup != NULL)) { + (void)timeout->cb->cleanup(timeout->context); + } + + /* Zero the entire structure to make post-cleanup state distinguishable */ + memset(timeout, 0, sizeof(*timeout)); return WH_ERROR_OK; } -int wh_Timeout_Start(whTimeoutCtx* timeout) +int wh_Timeout_Set(whTimeout* timeout, uint64_t timeoutUs) { if (timeout == NULL) { return WH_ERROR_BADARGS; } - timeout->startUs = WH_GETTIME_US(); + if (timeout->initialized == 0) { + return WH_ERROR_BADARGS; + } - return WH_ERROR_OK; + /* No-op if not configured (no callbacks) */ + if ((timeout->cb == NULL) || (timeout->cb->set == NULL)) { + return WH_ERROR_OK; + } + + return timeout->cb->set(timeout->context, timeoutUs); +} + +int wh_Timeout_Start(whTimeout* timeout) +{ + if (timeout == NULL) { + return WH_ERROR_BADARGS; + } + + if (timeout->initialized == 0) { + return WH_ERROR_BADARGS; + } + + /* No-op if not configured (no callbacks) */ + if ((timeout->cb == NULL) || (timeout->cb->start == NULL)) { + return WH_ERROR_OK; + } + + return timeout->cb->start(timeout->context); } -int wh_Timeout_Stop(whTimeoutCtx* timeout) +int wh_Timeout_Stop(whTimeout* timeout) { if (timeout == NULL) { return WH_ERROR_BADARGS; } - timeout->startUs = 0; - timeout->timeoutUs = 0; + if (timeout->initialized == 0) { + return WH_ERROR_BADARGS; + } - return WH_ERROR_OK; + /* No-op if not configured (no callbacks) */ + if ((timeout->cb == NULL) || (timeout->cb->stop == NULL)) { + return WH_ERROR_OK; + } + + return timeout->cb->stop(timeout->context); } -int wh_Timeout_Expired(whTimeoutCtx* timeout) +int wh_Timeout_Expired(whTimeout* timeout) { - uint64_t nowUs = 0; - int expired = 0; - int ret = 0; + int expired = 0; + int ret = 0; if (timeout == NULL) { return 0; } - if (timeout->timeoutUs == 0) { + /* Not initialized or no callbacks = never expired */ + if ((timeout->initialized == 0) || (timeout->cb == NULL) || + (timeout->cb->expired == NULL)) { return 0; } - nowUs = WH_GETTIME_US(); - expired = (nowUs - timeout->startUs) >= timeout->timeoutUs; + ret = timeout->cb->expired(timeout->context, &expired); + if (ret != WH_ERROR_OK) { + return ret; + } + + /* If expired and application callback is set, invoke it */ if (expired && (timeout->expiredCb != NULL)) { /* Allow the callback to overwrite the expired value. If the callback * returns an error, propagate it to the caller. */ @@ -100,6 +171,7 @@ int wh_Timeout_Expired(whTimeoutCtx* timeout) return ret; } } + return expired; } diff --git a/test/Makefile b/test/Makefile index 259218045..623492cd5 100644 --- a/test/Makefile +++ b/test/Makefile @@ -36,7 +36,7 @@ DEF += -DWOLFHSM_CFG_TEST_POSIX ARCHFLAGS ?= # Enable extra C compiler warnings -CFLAGS_EXTRA = -Werror -Wall -Wextra +CFLAGS_EXTRA = -Werror -Wall -Wextra -g # Place functions / data into separate sections to allow unused code removal CFLAGS_EXTRA += -ffunction-sections -fdata-sections diff --git a/test/wh_test_timeout.c b/test/wh_test_timeout.c index 8c79205ca..95c4fe886 100644 --- a/test/wh_test_timeout.c +++ b/test/wh_test_timeout.c @@ -31,6 +31,8 @@ #include "wolfhsm/wh_timeout.h" #include "wolfhsm/wh_error.h" +#include "port/posix/posix_timeout.h" + #include "wh_test_common.h" #include "wh_test_timeout.h" @@ -49,10 +51,10 @@ #endif /* HAVE_AES_CBC */ #endif /* !WOLFHSM_CFG_NO_CRYPTO */ -static int whTest_TimeoutCb(whTimeoutCtx* ctx, int* isExpired) +static int whTest_TimeoutCb(whTimeout* timeout, int* isExpired) { (void)isExpired; - int* counter = (int*)ctx->cbCtx; + int* counter = (int*)timeout->expiredCtx; if (counter != NULL) { (*counter)++; } @@ -97,10 +99,15 @@ static int whTest_TimeoutAesCbc(void) }}; /* Client configuration with timeout */ - whTimeoutConfig timeoutCfg = { - .timeoutUs = 1, - .expiredCb = NULL, - .cbCtx = NULL, + posixTimeoutContext posixCtx = {0}; + posixTimeoutConfig posixCfg = {.timeoutUs = 1}; + whTimeoutCb timeoutCbTable = POSIX_TIMEOUT_CB; + whTimeoutConfig timeoutCfg = { + .cb = &timeoutCbTable, + .context = &posixCtx, + .config = &posixCfg, + .expiredCb = NULL, + .expiredCtx = NULL, }; whTransportClientCb tccb[1] = {WH_TRANSPORT_MEM_CLIENT_CB}; @@ -216,10 +223,11 @@ static int whTest_TimeoutAesCbc(void) /* 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 int _timeoutOverrideCb(whTimeoutCtx* ctx, int* isExpired) + * expiredCtx points to an int counter tracking how many times the callback + * fired. */ +static int _timeoutOverrideCb(whTimeout* timeout, int* isExpired) { - int* counter = (int*)ctx->cbCtx; + int* counter = (int*)timeout->expiredCtx; if (counter == NULL) { return WH_ERROR_BADARGS; } @@ -229,7 +237,7 @@ static int _timeoutOverrideCb(whTimeoutCtx* ctx, int* isExpired) if (*counter <= 1) { /* First expiration: override and restart the timer */ *isExpired = 0; - wh_Timeout_Start(ctx); + wh_Timeout_Start(timeout); } /* Subsequent expirations: let it expire normally */ return WH_ERROR_OK; @@ -252,10 +260,15 @@ static int whTest_TimeoutAesCbcOverride(void) }}; /* Client configuration with timeout and override callback */ - whTimeoutConfig timeoutCfg = { - .timeoutUs = 1, - .expiredCb = _timeoutOverrideCb, - .cbCtx = &cb_count, + posixTimeoutContext posixCtx = {0}; + posixTimeoutConfig posixCfg = {.timeoutUs = 1}; + whTimeoutCb timeoutCbTable = POSIX_TIMEOUT_CB; + whTimeoutConfig timeoutCfg = { + .cb = &timeoutCbTable, + .context = &posixCtx, + .config = &posixCfg, + .expiredCb = _timeoutOverrideCb, + .expiredCtx = &cb_count, }; whTransportClientCb tccb[1] = {WH_TRANSPORT_MEM_CLIENT_CB}; @@ -378,32 +391,38 @@ static int whTest_TimeoutAesCbcOverride(void) int whTest_Timeout(void) { WH_TEST_PRINT("Testing timeout...\n"); - int cb_count = 0; - whTimeoutConfig cfg; - whTimeoutCtx timeout[1]; - - cfg.timeoutUs = 1; - cfg.expiredCb = whTest_TimeoutCb; - cfg.cbCtx = &cb_count; + int cb_count = 0; + posixTimeoutContext posixCtx = {0}; + posixTimeoutConfig posixCfg = {.timeoutUs = 1}; + whTimeoutCb timeoutCbTable = POSIX_TIMEOUT_CB; + whTimeoutConfig cfg = { + .cb = &timeoutCbTable, + .context = &posixCtx, + .config = &posixCfg, + .expiredCb = whTest_TimeoutCb, + .expiredCtx = &cb_count, + }; + whTimeout timeout[1]; wh_Timeout_Init(timeout, &cfg); - WH_TEST_ASSERT_RETURN(timeout->startUs == 0); - WH_TEST_ASSERT_RETURN(timeout->timeoutUs == cfg.timeoutUs); + WH_TEST_ASSERT_RETURN(posixCtx.startUs == 0); + WH_TEST_ASSERT_RETURN(posixCtx.timeoutUs == posixCfg.timeoutUs); WH_TEST_ASSERT_RETURN(timeout->expiredCb == cfg.expiredCb); - WH_TEST_ASSERT_RETURN(timeout->cbCtx == cfg.cbCtx); + WH_TEST_ASSERT_RETURN(timeout->expiredCtx == cfg.expiredCtx); wh_Timeout_Start(timeout); - WH_TEST_ASSERT_RETURN(timeout->timeoutUs > 0); + WH_TEST_ASSERT_RETURN(posixCtx.started == 1); wh_Timeout_Stop(timeout); - WH_TEST_ASSERT_RETURN(timeout->startUs == 0); - WH_TEST_ASSERT_RETURN(timeout->timeoutUs == 0); + WH_TEST_ASSERT_RETURN(posixCtx.startUs == 0); + WH_TEST_ASSERT_RETURN(posixCtx.started == 0); - /* No expiration when disabled */ + /* No expiration when stopped */ WH_TEST_ASSERT_RETURN(wh_Timeout_Expired(timeout) == 0); /* Test expired callback fires and increments counter */ cb_count = 0; + memset(&posixCtx, 0, sizeof(posixCtx)); wh_Timeout_Init(timeout, &cfg); wh_Timeout_Start(timeout); /* timeoutUs is 1 us, so spin until expired */ @@ -411,11 +430,22 @@ int whTest_Timeout(void) ; WH_TEST_ASSERT_RETURN(cb_count > 0); + wh_Timeout_Cleanup(timeout); + + /* Test no-op mode (NULL config = timeout disabled, never expires) */ + wh_Timeout_Init(timeout, NULL); + WH_TEST_ASSERT_RETURN(wh_Timeout_Start(timeout) == WH_ERROR_OK); + WH_TEST_ASSERT_RETURN(wh_Timeout_Expired(timeout) == 0); + WH_TEST_ASSERT_RETURN(wh_Timeout_Stop(timeout) == WH_ERROR_OK); + wh_Timeout_Cleanup(timeout); + + /* Test bad arguments */ 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); + WH_TEST_ASSERT_RETURN(wh_Timeout_Cleanup(0) == WH_ERROR_BADARGS); #if !defined(WOLFHSM_CFG_NO_CRYPTO) && defined(HAVE_AES_CBC) WH_TEST_RETURN_ON_FAIL(whTest_TimeoutAesCbc()); diff --git a/wolfhsm/wh_client.h b/wolfhsm/wh_client.h index c09a297ce..0018eca6e 100644 --- a/wolfhsm/wh_client.h +++ b/wolfhsm/wh_client.h @@ -111,7 +111,7 @@ struct whClientContext_t { uint16_t last_req_kind; uint32_t cryptoAffinity; #ifdef WOLFHSM_CFG_ENABLE_TIMEOUT - whTimeoutCtx respTimeout; + whTimeout respTimeout; #endif #ifdef WOLFHSM_CFG_DMA whClientDmaContext dma; diff --git a/wolfhsm/wh_timeout.h b/wolfhsm/wh_timeout.h index 5d6b76660..0285adbb0 100644 --- a/wolfhsm/wh_timeout.h +++ b/wolfhsm/wh_timeout.h @@ -19,7 +19,16 @@ /* * wolfhsm/wh_timeout.h * - * Generic timeout helpers based on WH_GETTIME_US(). + * Platform-agnostic timeout abstraction using a callback-based mechanism + * that allows platform-specific implementations (POSIX, RTOS, bare-metal, + * etc.) without introducing OS dependencies in the core wolfHSM code. + * + * When WOLFHSM_CFG_ENABLE_TIMEOUT is defined: + * - Timeout operations use platform callbacks for actual time measurement + * - NULL config results in no-op timeout (never expires) + * + * When WOLFHSM_CFG_ENABLE_TIMEOUT is not defined: + * - No timeout types or functions are available */ #ifndef WOLFHSM_WH_TIMEOUT_H_ @@ -28,90 +37,169 @@ /* Pick up compile-time configuration */ #include "wolfhsm/wh_settings.h" +#include + +/* Time conversion macros */ #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; +/** + * Platform callback function signatures. + * + * All callbacks receive a user-provided context pointer (from whTimeoutConfig). + * Return: WH_ERROR_OK on success, negative error code on failure. + */ + +/** Initialize timeout resources - called once during setup */ +typedef int (*whTimeoutInitCb)(void* context, const void* config); + +/** Cleanup timeout resources - called once during teardown */ +typedef int (*whTimeoutCleanupCb)(void* context); + +/** Set the timeout duration in microseconds */ +typedef int (*whTimeoutSetCb)(void* context, uint64_t timeoutUs); + +/** Start or restart the timeout timer */ +typedef int (*whTimeoutStartCb)(void* context); + +/** Stop the timeout timer */ +typedef int (*whTimeoutStopCb)(void* context); + +/** + * Check whether the timeout has expired. + * Writes 1 to *expired if elapsed, 0 if not. + * Return: WH_ERROR_OK on success, negative error code on failure. + */ +typedef int (*whTimeoutCheckExpiredCb)(void* context, int* expired); + +/** + * Timeout callback table. + * + * Platforms provide implementations of these callbacks. If the entire + * callback table is NULL, all timeout operations become no-ops + * (disabled mode). Individual callbacks may also be NULL to skip + * specific operations. + */ +typedef struct whTimeoutCb_t { + whTimeoutInitCb init; /* Initialize timeout resources */ + whTimeoutCleanupCb cleanup; /* Free timeout resources */ + whTimeoutSetCb set; /* Set timeout duration */ + whTimeoutStartCb start; /* Start/restart timer */ + whTimeoutStopCb stop; /* Stop timer */ + whTimeoutCheckExpiredCb expired; /* Check if timer expired */ +} whTimeoutCb; + + +/* Forward declare so the application callback typedef can reference it */ +typedef struct whTimeout_t whTimeout; /** - * Callback invoked when a timeout expires. The callback may override the - * expiration by setting *isExpired to 0 (e.g. to extend the timeout by calling - * wh_Timeout_Start() to restart the timer). Returning a non-zero error code - * from the callback will cause wh_Timeout_Expired() to propagate that error - * to its caller. + * Application-level callback invoked when a timeout expires. The callback may + * override the expiration by setting *isExpired to 0 (e.g. to extend the + * timeout by calling wh_Timeout_Start() to restart the timer). Returning a + * non-zero error code from the callback will cause wh_Timeout_Expired() to + * propagate that error to its caller. * - * @param ctx The timeout context that expired. + * @param timeout The timeout instance that expired. * @param isExpired Pointer to the expired flag; set to 0 to suppress * expiration. * @return 0 on success, or a negative error code to signal failure. */ -typedef int (*whTimeoutExpiredCb)(whTimeoutCtx* ctx, int* isExpired); +typedef int (*whTimeoutExpiredCb)(whTimeout* timeout, int* isExpired); -struct whTimeoutCtx { - uint64_t startUs; - uint64_t timeoutUs; - whTimeoutExpiredCb expiredCb; - void* cbCtx; +/** + * Timeout instance structure. + * + * Holds callback table, platform-specific context, and an optional + * application-level expired callback. The context pointer is passed to all + * platform callbacks. + */ +struct whTimeout_t { + const whTimeoutCb* cb; /* Platform callbacks (may be NULL) */ + void* context; /* Platform context */ + whTimeoutExpiredCb expiredCb; /* Application expired callback */ + void* expiredCtx; /* Application callback context */ + int initialized; + uint8_t WH_PAD[4]; }; -typedef struct { - uint64_t timeoutUs; - whTimeoutExpiredCb expiredCb; - void* cbCtx; +/** + * Timeout configuration for initialization. + */ +typedef struct whTimeoutConfig_t { + const whTimeoutCb* cb; /* Callback table */ + void* context; /* Platform context */ + const void* config; /* Backend-specific config */ + whTimeoutExpiredCb expiredCb; /* Application expired callback */ + void* expiredCtx; /* Application callback context */ } whTimeoutConfig; + /** - * Initialize a timeout context from a configuration. + * @brief Initializes a timeout instance. + * + * If config is NULL or config->cb is NULL, the timeout is disabled + * (no-op mode) and all operations become no-ops with Expired() always + * returning 0. * - * @param timeout The timeout context to initialize. - * @param config The timeout configuration to apply. - * @return 0 on success, WH_ERROR_BADARGS on invalid input. + * @param[in] timeout Pointer to the timeout structure. Must not be NULL. + * @param[in] config Pointer to the timeout configuration (may be NULL for + * no-op mode). + * @return WH_ERROR_OK on success, WH_ERROR_BADARGS if timeout is NULL, + * or negative error code on callback failure. */ -int wh_Timeout_Init(whTimeoutCtx* timeout, const whTimeoutConfig* config); +int wh_Timeout_Init(whTimeout* timeout, const whTimeoutConfig* config); /** - * Configure a timeout value. + * @brief Cleans up a timeout instance. + * + * Calls the cleanup callback and then zeros the entire structure. + * Idempotent - calling cleanup on an already cleaned up or uninitialized + * timeout returns WH_ERROR_OK. * - * @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. + * @param[in] timeout Pointer to the timeout structure. + * @return WH_ERROR_OK on success, WH_ERROR_BADARGS if timeout is NULL. */ -int wh_Timeout_Set(whTimeoutCtx* timeout, uint64_t timeoutUs); +int wh_Timeout_Cleanup(whTimeout* timeout); /** - * Start or reset a timeout window using the configured timeoutUs. + * @brief Set the timeout duration. * - * @param timeout The timeout context to start. - * @return 0 on success, WH_ERROR_BADARGS on invalid input. + * @param[in] timeout The timeout instance. + * @param[in] timeoutUs Timeout duration in microseconds; 0 disables. + * @return WH_ERROR_OK on success, WH_ERROR_BADARGS on invalid input. */ -int wh_Timeout_Start(whTimeoutCtx* timeout); +int wh_Timeout_Set(whTimeout* timeout, uint64_t timeoutUs); /** - * Disable a timeout and clear its bookkeeping. + * @brief Start or reset a timeout window. * - * @param timeout The timeout context to stop. - * @return 0 on success, WH_ERROR_BADARGS on invalid input. + * @param[in] timeout The timeout instance. + * @return WH_ERROR_OK on success, WH_ERROR_BADARGS on invalid input. */ -int wh_Timeout_Stop(whTimeoutCtx* timeout); +int wh_Timeout_Start(whTimeout* timeout); /** - * Check whether a timeout has expired. - * - * If the timeout is expired and an expired callback is configured, the - * callback is invoked before returning. The callback receives a pointer to the - * expired flag and may set it to 0 to override (suppress) the expiration, for - * example to extend the deadline by calling wh_Timeout_Start() to restart the - * timer. If the callback returns a non-zero error code, that error is - * propagated directly to the caller. - * - * @param timeout The timeout context to check. - * @return 1 if expired, 0 if not expired or disabled, or negative error code - * from the expired callback. + * @brief Stop a timeout and clear its timer state. + * + * @param[in] timeout The timeout instance. + * @return WH_ERROR_OK on success, WH_ERROR_BADARGS on invalid input. + */ +int wh_Timeout_Stop(whTimeout* timeout); + +/** + * @brief Check whether a timeout has expired. + * + * Delegates to the platform callback to check expiration. If expired and + * an application expired callback is configured, the callback is invoked + * before returning. The callback may set *isExpired to 0 to override + * (suppress) the expiration. + * + * @param[in] timeout The timeout instance. + * @return 1 if expired, 0 if not expired or disabled, or negative error code. */ -int wh_Timeout_Expired(whTimeoutCtx* timeout); +int wh_Timeout_Expired(whTimeout* timeout); #endif /* !WOLFHSM_WH_TIMEOUT_H_ */ From dc44ce010e2df37ddd5eea97d347ab893dfd3840 Mon Sep 17 00:00:00 2001 From: Alex Lanzano Date: Tue, 3 Mar 2026 10:56:39 -0500 Subject: [PATCH 10/17] Move response timeout logic into comm layer --- docs/draft/timeout.md | 80 ++++-------- src/wh_client.c | 53 -------- src/wh_client_crypto.c | 278 ++++++++++++++++++++++++++--------------- src/wh_comm.c | 24 ++++ test/wh_test_timeout.c | 24 ++-- wolfhsm/wh_client.h | 21 ---- wolfhsm/wh_comm.h | 10 ++ 7 files changed, 252 insertions(+), 238 deletions(-) diff --git a/docs/draft/timeout.md b/docs/draft/timeout.md index 2dde50279..4f56e05bc 100644 --- a/docs/draft/timeout.md +++ b/docs/draft/timeout.md @@ -4,7 +4,7 @@ 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. -When creating a client, you provide a `whTimeoutConfig` specifying the platform callbacks, platform context, and an optional application-level expired callback: +The timeout lives in the comm layer. When creating a client, you provide a `whTimeoutConfig` in the `whCommClientConfig`: ```c /* Platform-specific setup (e.g. POSIX) */ posixTimeoutContext posixCtx = {0}; @@ -18,62 +18,36 @@ whTimeoutConfig timeoutCfg = { .expiredCb = myTimeoutHandler, /* optional app callback on expiry */ .expiredCtx = myAppContext, /* context passed to app callback */ }; -whClientConfig clientCfg = { - .comm = &commConfig, +whCommClientConfig commConfig = { + .transport_cb = &transportCb, + .transport_context = &transportCtx, + .transport_config = &transportCfg, + .client_id = 1, .respTimeoutConfig = &timeoutCfg, /* attach timeout config */ }; +whClientConfig clientCfg = { + .comm = &commConfig, +}; wh_Client_Init(&clientCtx, &clientCfg); ``` -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. -If `respTimeoutConfig` is NULL (or `cb` is NULL), the timeout is disabled and all operations become no-ops (timeout never expires). +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. +If `respTimeoutConfig` is NULL (or `cb` is NULL), the timeout enters no-op mode and never expires. -## 2. What Happens During a Crypto Call +## 2. How the Timeout Works -Before the timeout feature, 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); -``` +The timeout is handled transparently in the comm layer: -If the server never responded, the client would spin forever. -This is replaced with a single helper `_recvCryptoResponse()` (`src/wh_client_crypto.c`): -```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_RecvResponseBlockingWithTimeout(ctx, group, action, - size, data); -#else - do { - ret = wh_Client_RecvResponse(ctx, group, action, size, data); - } while (ret == WH_ERROR_NOTREADY); -#endif - return ret; -} -``` +1. **`wh_CommClient_SendRequest`**: After a successful send, starts the response timer via `wh_Timeout_Start()`. +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()`. + +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. -When timeout is enabled, it delegates to `wh_Client_RecvResponseBlockingWithTimeout`. When disabled, the old infinite-loop behavior is preserved. - -## 3. The Timeout Receive Loop -`wh_Client_RecvResponseBlockingWithTimeout` (`src/wh_client.c`) does this: -1. **Starts the timer** -- calls `wh_Timeout_Start()` which delegates to the platform `start` callback (e.g. captures the current time). -2. **Polls for a response** -- calls `wh_Client_RecvResponse()` in a loop. -3. **On each `WH_ERROR_NOTREADY`**, checks `wh_Timeout_Expired()`: - - Delegates to the platform `expired` callback to check elapsed time - - If expired: invokes the application `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 whTimeout +Client App CommClient whTimeout | | | - |-- wh_Client_AesCbc() --------> | | - | |-- wh_Timeout_Start --------> cb->start() + |-- wh_Client_AesCbc() -------->| | + | |-- SendRequest ------> cb->start() | | | | |-- RecvResponse (NOTREADY) | | |-- Expired? -------> cb->expired() -> no @@ -86,11 +60,12 @@ Client App _recvCryptoResponse whTimeout |<-- 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. +## 3. What the Client Sees + +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. 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. Overriding Expiration via the Callback +## 4. Overriding Expiration via the Callback 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. @@ -129,7 +104,6 @@ whTimeoutConfig timeoutCfg = { }; ``` -## 6. 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()` to change the duration between calls, but there's no per-operation override. +## 5. Design Notes +- **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. +- **Timer starts on send, checks on receive.** The timer window begins when a request is successfully sent, measuring the full round-trip wait. diff --git a/src/wh_client.c b/src/wh_client.c index c89a2fd61..2d6c2f867 100644 --- a/src/wh_client.c +++ b/src/wh_client.c @@ -77,15 +77,6 @@ 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 @@ -138,10 +129,6 @@ int wh_Client_Cleanup(whClientContext* c) (void)wolfCrypt_Cleanup(); #endif /* !WOLFHSM_CFG_NO_CRYPTO */ -#ifdef WOLFHSM_CFG_ENABLE_TIMEOUT - (void)wh_Timeout_Cleanup(&c->respTimeout); -#endif - (void)wh_CommClient_Cleanup(c->comm); memset(c, 0, sizeof(*c)); @@ -208,46 +195,6 @@ 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; - whTimeout* timeout; - - if (c == NULL) { - return WH_ERROR_BADARGS; - } - - timeout = &c->respTimeout; - - /* If no timeout configured, fall back to standard blocking loop */ - if (timeout->cb == NULL) { - do { - ret = wh_Client_RecvResponse(c, out_group, out_action, out_size, - data); - } while (ret == WH_ERROR_NOTREADY); - return ret; - } - - 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 9652480eb..40bab2b33 100644 --- a/src/wh_client_crypto.c +++ b/src/wh_client_crypto.c @@ -169,23 +169,6 @@ 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, @@ -257,7 +240,10 @@ 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) { - ret = _recvCryptoResponse(ctx, &group, &action, &res_len, dataPtr); + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, + dataPtr); + } while (ret == WH_ERROR_NOTREADY); } if (ret == WH_ERROR_OK) { /* Get response */ @@ -332,7 +318,10 @@ int wh_Client_RngGenerateDma(whClientContext* ctx, uint8_t* out, uint32_t size) if (ret == WH_ERROR_OK) { /* Wait for and receive the response */ - ret = _recvCryptoResponse(ctx, NULL, NULL, &respSz, (uint8_t*)dataPtr); + do { + ret = wh_Client_RecvResponse(ctx, NULL, NULL, &respSz, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); } if (ret == WH_ERROR_OK) { @@ -432,7 +421,10 @@ 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; - ret = _recvCryptoResponse(ctx, &group, &action, &res_len, dataPtr); + do { + ret = + wh_Client_RecvResponse(ctx, &group, &action, &res_len, dataPtr); + } while (ret == WH_ERROR_NOTREADY); if (ret == WH_ERROR_OK) { ret = _getCryptoResponse(dataPtr, type, (uint8_t**)&res); if (ret == WH_ERROR_OK) { @@ -888,7 +880,10 @@ 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; - ret = _recvCryptoResponse(ctx, &group, &action, &res_len, dataPtr); + do { + ret = + wh_Client_RecvResponse(ctx, &group, &action, &res_len, dataPtr); + } while (ret == WH_ERROR_NOTREADY); if (ret == WH_ERROR_OK) { ret = _getCryptoResponse(dataPtr, type, (uint8_t**)&res); if (ret == WH_ERROR_OK) { @@ -1000,11 +995,13 @@ int wh_Client_AesCbcDma(whClientContext* ctx, Aes* aes, int enc, if (ret == WH_ERROR_OK) { ret = wh_Client_SendRequest(ctx, group, action, req_len, dataPtr); } - if (ret == WH_ERROR_OK) { - /* Response packet */ - uint16_t res_len = 0; - ret = _recvCryptoResponse(ctx, &group, &action, &res_len, dataPtr); + uint16_t resLen = 0; + do { + ret = + wh_Client_RecvResponse(ctx, &group, &action, &resLen, dataPtr); + } while (ret == WH_ERROR_NOTREADY); + if (ret == WH_ERROR_OK) { /* Get response */ whMessageCrypto_AesCbcDmaResponse* res; @@ -1247,7 +1244,10 @@ 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 == WH_ERROR_OK) { uint16_t res_len = 0; - ret = _recvCryptoResponse(ctx, &group, &action, &res_len, dataPtr); + do { + ret = + wh_Client_RecvResponse(ctx, &group, &action, &res_len, dataPtr); + } while (ret == WH_ERROR_NOTREADY); if (ret == WH_ERROR_OK) { /* Get response */ @@ -1419,7 +1419,10 @@ int wh_Client_AesGcmDma(whClientContext* ctx, Aes* aes, int enc, } if (ret == WH_ERROR_OK) { uint16_t resLen = 0; - ret = _recvCryptoResponse(ctx, &group, &action, &resLen, dataPtr); + do { + ret = + wh_Client_RecvResponse(ctx, &group, &action, &resLen, dataPtr); + } while (ret == WH_ERROR_NOTREADY); if (ret == WH_ERROR_OK) { /* Get response */ @@ -1608,8 +1611,10 @@ static int _EccMakeKey(whClientContext* ctx, int size, int curveId, if (ret == WH_ERROR_OK) { /* Response Message */ uint16_t res_len; - ret = _recvCryptoResponse(ctx, &group, &action, &res_len, - (uint8_t*)dataPtr); + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); if (ret == WH_ERROR_OK) { /* Get response structure pointer, validates generic header @@ -1770,8 +1775,10 @@ int wh_Client_EccSharedSecret(whClientContext* ctx, ecc_key* priv_key, uint16_t res_len; /* Recv Response */ - ret = _recvCryptoResponse(ctx, &group, &action, &res_len, - (uint8_t*)dataPtr); + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); WH_DEBUG_CLIENT_VERBOSE("resp packet recv. ret:%d\n", ret); if (ret == WH_ERROR_OK) { /* Get response structure pointer, validates generic header @@ -1904,8 +1911,10 @@ int wh_Client_EccSign(whClientContext* ctx, ecc_key* key, const uint8_t* hash, uint16_t res_len = 0; /* Recv Response */ - ret = _recvCryptoResponse(ctx, &group, &action, &res_len, - (uint8_t*)dataPtr); + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); if (ret == WH_ERROR_OK) { /* Get response structure pointer, validates generic header @@ -2056,8 +2065,10 @@ int wh_Client_EccVerify(whClientContext* ctx, ecc_key* key, const uint8_t* sig, uint16_t res_len = 0; /* Recv Response */ - ret = _recvCryptoResponse(ctx, &group, &action, &res_len, - (uint8_t*)dataPtr); + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); if (ret == WH_ERROR_OK) { /* Get response structure pointer, validates generic header * rc */ @@ -2132,8 +2143,10 @@ int wh_Client_EccCheckPubKey(whClientContext* ctx, ecc_key* key, (uint8_t*)packet); /* read response */ if (ret == 0) { - ret = _recvCryptoResponse(ctx, &group, &action, &dataSz, - (uint8_t*)packet); + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &dataSz, + (uint8_t*)packet); + } while (ret == WH_ERROR_NOTREADY); } if (ret == 0) { if (packet->rc != 0) @@ -2275,8 +2288,10 @@ 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) { - ret = _recvCryptoResponse(ctx, &group, &action, &data_len, - (uint8_t*)dataPtr); + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &data_len, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); } @@ -2432,8 +2447,10 @@ int wh_Client_Curve25519SharedSecret(whClientContext* ctx, pub_evict = prv_evict = 0; /* Recv Response */ - ret = _recvCryptoResponse(ctx, &group, &action, &res_len, - (uint8_t*)dataPtr); + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); WH_DEBUG_CLIENT_VERBOSE("resp packet recv. ret:%d\n", ret); if (ret == WH_ERROR_OK) { @@ -2599,8 +2616,10 @@ static int _Ed25519MakeKey(whClientContext* ctx, whKeyId* inout_key_id, return ret; } uint16_t res_len = 0; - ret = - _recvCryptoResponse(ctx, &group, &action, &res_len, (uint8_t*)dataPtr); + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); if (ret != WH_ERROR_OK) { return ret; @@ -2742,8 +2761,10 @@ int wh_Client_Ed25519Sign(whClientContext* ctx, ed25519_key* key, evict = 0; uint16_t res_len = 0; - ret = _recvCryptoResponse(ctx, &group, &action, &res_len, - (uint8_t*)dataPtr); + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); if (group != WH_MESSAGE_GROUP_CRYPTO || action != WC_ALGO_TYPE_PK) { ret = WH_ERROR_ABORTED; @@ -2878,8 +2899,10 @@ int wh_Client_Ed25519Verify(whClientContext* ctx, ed25519_key* key, evict = 0; uint16_t res_len = 0; - ret = _recvCryptoResponse(ctx, &group, &action, &res_len, - (uint8_t*)dataPtr); + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); if (group != WH_MESSAGE_GROUP_CRYPTO || action != WC_ALGO_TYPE_PK) { ret = WH_ERROR_ABORTED; @@ -3012,8 +3035,10 @@ int wh_Client_Ed25519SignDma(whClientContext* ctx, ed25519_key* key, evict = 0; uint16_t res_len = 0; - ret = _recvCryptoResponse(ctx, &group, &action, &res_len, - (uint8_t*)dataPtr); + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); if (group != WH_MESSAGE_GROUP_CRYPTO_DMA || action != WC_ALGO_TYPE_PK) { @@ -3157,8 +3182,10 @@ int wh_Client_Ed25519VerifyDma(whClientContext* ctx, ed25519_key* key, evict = 0; uint16_t res_len = 0; - ret = _recvCryptoResponse(ctx, &group, &action, &res_len, - (uint8_t*)dataPtr); + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); if (group != WH_MESSAGE_GROUP_CRYPTO_DMA || action != WC_ALGO_TYPE_PK) { @@ -3343,7 +3370,10 @@ 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; - ret = _recvCryptoResponse(ctx, &group, &action, &res_len, dataPtr); + do { + ret = + wh_Client_RecvResponse(ctx, &group, &action, &res_len, dataPtr); + } while (ret == WH_ERROR_NOTREADY); WH_DEBUG_CLIENT_VERBOSE("RSA KeyGen Res recv: ret:%d, res_len: %u\n", ret, (unsigned int)res_len); @@ -3504,8 +3534,10 @@ int wh_Client_RsaFunction(whClientContext* ctx, RsaKey* key, int rsa_type, uint16_t res_len = 0; /* Recv Response */ - ret = _recvCryptoResponse(ctx, &group, &action, &res_len, - (uint8_t*)dataPtr); + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); if (ret == WH_ERROR_OK) { /* Get response */ @@ -3614,8 +3646,10 @@ int wh_Client_RsaGetSize(whClientContext* ctx, const RsaKey* key, int* out_size) uint16_t res_len = 0; /* Recv Response */ - ret = _recvCryptoResponse(ctx, &group, &action, &res_len, - (uint8_t*)dataPtr); + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); if (ret == WH_ERROR_OK) { /* Get response */ @@ -3736,7 +3770,10 @@ static int _HkdfMakeKey(whClientContext* ctx, int hashType, whKeyId keyIdIn, if (ret == 0) { uint16_t res_len = 0; - ret = _recvCryptoResponse(ctx, &group, &action, &res_len, dataPtr); + do { + ret = + wh_Client_RecvResponse(ctx, &group, &action, &res_len, dataPtr); + } while (ret == WH_ERROR_NOTREADY); WH_DEBUG_CLIENT_VERBOSE("HKDF Res recv: ret:%d, res_len: %u\n", ret, (unsigned int)res_len); @@ -3901,7 +3938,9 @@ static int _CmacKdfMakeKey(whClientContext* ctx, whKeyId saltKeyId, } uint16_t res_len = 0; - ret = _recvCryptoResponse(ctx, &group, &action, &res_len, dataPtr); + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, dataPtr); + } while (ret == WH_ERROR_NOTREADY); if (ret == WH_ERROR_OK) { ret = _getCryptoResponse(dataPtr, WC_ALGO_TYPE_KDF, (uint8_t**)&res); @@ -4091,8 +4130,10 @@ int wh_Client_Cmac(whClientContext* ctx, Cmac* cmac, CmacType type, uint16_t res_len = 0; - ret = _recvCryptoResponse(ctx, &group, &action, &res_len, - (uint8_t*)dataPtr); + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); if (ret == WH_ERROR_OK) { /* Get response */ ret = @@ -4120,6 +4161,7 @@ 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, @@ -4221,9 +4263,11 @@ int wh_Client_CmacDma(whClientContext* ctx, Cmac* cmac, CmacType type, } uint16_t respSz = 0; - ret = _recvCryptoResponse(ctx, NULL, NULL, &respSz, - (uint8_t*)dataPtr); - + do { + ret = wh_Client_RecvResponse(ctx, NULL, NULL, &respSz, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); + if (ret == WH_ERROR_OK) { ret = _getCryptoResponse(dataPtr, WC_ALGO_TYPE_CMAC, (uint8_t**)&res); @@ -4322,8 +4366,10 @@ static int _xferSha256BlockAndUpdateDigest(whClientContext* ctx, WH_DEBUG_CLIENT_VERBOSE(" ret = %d\n", ret); if (ret == 0) { - ret = _recvCryptoResponse(ctx, &group, &action, &dataSz, - (uint8_t*)dataPtr); + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &dataSz, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); } if (ret == 0) { /* Get response */ @@ -4476,8 +4522,10 @@ int wh_Client_Sha256Dma(whClientContext* ctx, wc_Sha256* sha, const uint8_t* in, (uint8_t*)dataPtr); if (ret == WH_ERROR_OK) { - ret = _recvCryptoResponse(ctx, NULL, NULL, &respSz, - (uint8_t*)dataPtr); + do { + ret = wh_Client_RecvResponse(ctx, NULL, NULL, &respSz, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); } if (ret == WH_ERROR_OK) { @@ -4503,8 +4551,10 @@ int wh_Client_Sha256Dma(whClientContext* ctx, wc_Sha256* sha, const uint8_t* in, (uint8_t*)dataPtr); if (ret == WH_ERROR_OK) { - ret = _recvCryptoResponse(ctx, NULL, NULL, &respSz, - (uint8_t*)dataPtr); + do { + ret = wh_Client_RecvResponse(ctx, NULL, NULL, &respSz, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); } /* Copy out the final hash value */ @@ -4602,8 +4652,10 @@ static int _xferSha224BlockAndUpdateDigest(whClientContext* ctx, WH_DEBUG_CLIENT_VERBOSE(" ret = %d\n", ret); if (ret == 0) { - ret = _recvCryptoResponse(ctx, &group, &action, &dataSz, - (uint8_t*)dataPtr); + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &dataSz, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); } if (ret == 0) { /* Get response */ @@ -4755,8 +4807,10 @@ int wh_Client_Sha224Dma(whClientContext* ctx, wc_Sha224* sha, const uint8_t* in, (uint8_t*)dataPtr); if (ret == WH_ERROR_OK) { - ret = _recvCryptoResponse(ctx, NULL, NULL, &respSz, - (uint8_t*)dataPtr); + do { + ret = wh_Client_RecvResponse(ctx, NULL, NULL, &respSz, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); } if (ret == WH_ERROR_OK) { @@ -4783,8 +4837,10 @@ int wh_Client_Sha224Dma(whClientContext* ctx, wc_Sha224* sha, const uint8_t* in, (uint8_t*)dataPtr); if (ret == WH_ERROR_OK) { - ret = _recvCryptoResponse(ctx, NULL, NULL, &respSz, - (uint8_t*)dataPtr); + do { + ret = wh_Client_RecvResponse(ctx, NULL, NULL, &respSz, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); } /* Copy out the final hash value */ @@ -4877,8 +4933,10 @@ static int _xferSha384BlockAndUpdateDigest(whClientContext* ctx, WH_DEBUG_CLIENT_VERBOSE(" ret = %d\n", ret); if (ret == 0) { - ret = _recvCryptoResponse(ctx, &group, &action, &dataSz, - (uint8_t*)dataPtr); + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &dataSz, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); } if (ret == 0) { /* Get response */ @@ -5030,8 +5088,10 @@ int wh_Client_Sha384Dma(whClientContext* ctx, wc_Sha384* sha, const uint8_t* in, (uint8_t*)dataPtr); if (ret == WH_ERROR_OK) { - ret = _recvCryptoResponse(ctx, NULL, NULL, &respSz, - (uint8_t*)dataPtr); + do { + ret = wh_Client_RecvResponse(ctx, NULL, NULL, &respSz, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); } if (ret == WH_ERROR_OK) { @@ -5058,8 +5118,10 @@ int wh_Client_Sha384Dma(whClientContext* ctx, wc_Sha384* sha, const uint8_t* in, (uint8_t*)dataPtr); if (ret == WH_ERROR_OK) { - ret = _recvCryptoResponse(ctx, NULL, NULL, &respSz, - (uint8_t*)dataPtr); + do { + ret = wh_Client_RecvResponse(ctx, NULL, NULL, &respSz, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); } /* Copy out the final hash value */ @@ -5153,8 +5215,10 @@ static int _xferSha512BlockAndUpdateDigest(whClientContext* ctx, WH_DEBUG_CLIENT_VERBOSE(" ret = %d\n", ret); if (ret == 0) { - ret = _recvCryptoResponse(ctx, &group, &action, &dataSz, - (uint8_t*)dataPtr); + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &dataSz, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); } if (ret == 0) { /* Get response */ @@ -5317,8 +5381,10 @@ int wh_Client_Sha512Dma(whClientContext* ctx, wc_Sha512* sha, const uint8_t* in, (uint8_t*)dataPtr); if (ret == WH_ERROR_OK) { - ret = _recvCryptoResponse(ctx, NULL, NULL, &respSz, - (uint8_t*)dataPtr); + do { + ret = wh_Client_RecvResponse(ctx, NULL, NULL, &respSz, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); } if (ret == WH_ERROR_OK) { @@ -5345,8 +5411,10 @@ int wh_Client_Sha512Dma(whClientContext* ctx, wc_Sha512* sha, const uint8_t* in, (uint8_t*)dataPtr); if (ret == WH_ERROR_OK) { - ret = _recvCryptoResponse(ctx, NULL, NULL, &respSz, - (uint8_t*)dataPtr); + do { + ret = wh_Client_RecvResponse(ctx, NULL, NULL, &respSz, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); } /* Copy out the final hash value */ @@ -5521,8 +5589,10 @@ static int _MlDsaMakeKey(whClientContext* ctx, int size, int level, (unsigned int)req->sz, ret); if (ret == 0) { uint16_t res_len; - ret = _recvCryptoResponse(ctx, &group, &action, &res_len, - (uint8_t*)dataPtr); + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); if (ret == WH_ERROR_OK) { /* Get response structure pointer, validates generic header @@ -5681,8 +5751,10 @@ int wh_Client_MlDsaSign(whClientContext* ctx, const byte* in, word32 in_len, uint16_t res_len = 0; /* Recv Response */ - ret = _recvCryptoResponse(ctx, &group, &action, &res_len, - (uint8_t*)dataPtr); + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); if (ret == WH_ERROR_OK) { /* Get response structure pointer, validates generic header @@ -5815,8 +5887,10 @@ int wh_Client_MlDsaVerify(whClientContext* ctx, const byte* sig, word32 sig_len, uint16_t res_len = 0; /* Recv Response */ - ret = _recvCryptoResponse(ctx, &group, &action, &res_len, - (uint8_t*)dataPtr); + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); if (ret == 0) { /* Get response structure pointer, validates generic header * rc */ @@ -5982,8 +6056,10 @@ static int _MlDsaMakeKeyDma(whClientContext* ctx, int level, } if (ret == WH_ERROR_OK) { uint16_t res_len; - ret = _recvCryptoResponse(ctx, &group, &action, &res_len, - (uint8_t*)dataPtr); + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); } (void)wh_Client_DmaProcessClientAddress( @@ -6140,8 +6216,10 @@ int wh_Client_MlDsaSignDma(whClientContext* ctx, const byte* in, word32 in_len, uint16_t res_len = 0; /* Recv Response */ - ret = _recvCryptoResponse(ctx, &group, &action, &res_len, - (uint8_t*)dataPtr); + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); if (ret == WH_ERROR_OK) { /* Get response structure pointer, validates generic header @@ -6274,8 +6352,10 @@ int wh_Client_MlDsaVerifyDma(whClientContext* ctx, const byte* sig, uint16_t res_len = 0; /* Recv Response */ - ret = _recvCryptoResponse(ctx, &group, &action, &res_len, - (uint8_t*)dataPtr); + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); if (ret == WH_ERROR_OK) { /* Get response structure pointer, validates generic header diff --git a/src/wh_comm.c b/src/wh_comm.c index b8e4f6474..5f96cdcf4 100644 --- a/src/wh_comm.c +++ b/src/wh_comm.c @@ -89,6 +89,13 @@ int wh_CommClient_Init(whCommClient* context, const whCommClientConfig* config) if (context->connect_cb != NULL) { rc = context->connect_cb(context, WH_COMM_CONNECTED); } + +#ifdef WOLFHSM_CFG_ENABLE_TIMEOUT + if (rc == 0) { + rc = wh_Timeout_Init(&context->respTimeout, + config->respTimeoutConfig); + } +#endif } return rc; } @@ -126,6 +133,9 @@ int wh_CommClient_SendRequest(whCommClient* context, uint16_t magic, if (rc == 0) { context->seq++; if (out_seq != NULL) *out_seq = context->seq; +#ifdef WOLFHSM_CFG_ENABLE_TIMEOUT + (void)wh_Timeout_Start(&context->respTimeout); +#endif } return rc; } @@ -154,6 +164,9 @@ int wh_CommClient_RecvResponse(whCommClient* context, &size, context->packet); if (rc == 0) { +#ifdef WOLFHSM_CFG_ENABLE_TIMEOUT + (void)wh_Timeout_Stop(&context->respTimeout); +#endif if (size < sizeof(*context->hdr)) { /* Size is too small */ rc = WH_ERROR_ABORTED; @@ -174,6 +187,13 @@ int wh_CommClient_RecvResponse(whCommClient* context, if (out_size != NULL) *out_size = data_size; } } +#ifdef WOLFHSM_CFG_ENABLE_TIMEOUT + else if (rc == WH_ERROR_NOTREADY) { + if (wh_Timeout_Expired(&context->respTimeout)) { + rc = WH_ERROR_TIMEOUT; + } + } +#endif return rc; } @@ -201,6 +221,10 @@ int wh_CommClient_Cleanup(whCommClient* context) (void)context->connect_cb(context, WH_COMM_DISCONNECTED); } +#ifdef WOLFHSM_CFG_ENABLE_TIMEOUT + (void)wh_Timeout_Cleanup(&context->respTimeout); +#endif + if ( (context->transport_cb != NULL) && (context->transport_cb->Cleanup != NULL)) { rc = context->transport_cb->Cleanup(context->transport_context); diff --git a/test/wh_test_timeout.c b/test/wh_test_timeout.c index 95c4fe886..41a0894ce 100644 --- a/test/wh_test_timeout.c +++ b/test/wh_test_timeout.c @@ -113,15 +113,15 @@ static int whTest_TimeoutAesCbc(void) 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, + .transport_cb = tccb, + .transport_context = (void*)tmcc, + .transport_config = (void*)tmcf, + .client_id = WH_TEST_DEFAULT_CLIENT_ID, + .connect_cb = _timeoutTestConnectCb, + .respTimeoutConfig = &timeoutCfg, }}; whClientConfig c_conf[1] = {{ .comm = cc_conf, - .respTimeoutConfig = &timeoutCfg, }}; whClientContext client[1] = {0}; @@ -274,15 +274,15 @@ static int whTest_TimeoutAesCbcOverride(void) 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, + .transport_cb = tccb, + .transport_context = (void*)tmcc, + .transport_config = (void*)tmcf, + .client_id = WH_TEST_DEFAULT_CLIENT_ID, + .connect_cb = _timeoutTestConnectCb, + .respTimeoutConfig = &timeoutCfg, }}; whClientConfig c_conf[1] = {{ .comm = cc_conf, - .respTimeoutConfig = &timeoutCfg, }}; whClientContext client[1] = {0}; diff --git a/wolfhsm/wh_client.h b/wolfhsm/wh_client.h index 0018eca6e..59fffd2b8 100644 --- a/wolfhsm/wh_client.h +++ b/wolfhsm/wh_client.h @@ -48,7 +48,6 @@ /* 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" @@ -124,9 +123,6 @@ 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; @@ -185,23 +181,6 @@ 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_comm.h b/wolfhsm/wh_comm.h index 4b77d58f7..f0547c205 100644 --- a/wolfhsm/wh_comm.h +++ b/wolfhsm/wh_comm.h @@ -45,6 +45,10 @@ #include /* For sized ints */ +#ifdef WOLFHSM_CFG_ENABLE_TIMEOUT +#include "wolfhsm/wh_timeout.h" +#endif + /** Packet content types */ /* Request/response packets are composed of a single fixed-length header * (whCommHeader) followed immediately by variable-length data between 0 and @@ -160,6 +164,9 @@ typedef struct { whCommSetConnectedCb connect_cb; uint8_t client_id; uint8_t WH_PAD[7]; +#ifdef WOLFHSM_CFG_ENABLE_TIMEOUT + whTimeoutConfig* respTimeoutConfig; +#endif } whCommClientConfig; /* Context structure for a client. Note the client context will track the @@ -180,6 +187,9 @@ typedef struct { uint8_t client_id; uint8_t server_id; uint8_t WH_PAD[4]; +#ifdef WOLFHSM_CFG_ENABLE_TIMEOUT + whTimeout respTimeout; +#endif } whCommClient; From 0dbb5cb3cce4aee13624a631f89ea21de4fbd396 Mon Sep 17 00:00:00 2001 From: Alex Lanzano Date: Tue, 3 Mar 2026 11:46:10 -0500 Subject: [PATCH 11/17] Address comments from copilot --- docs/draft/timeout.md | 3 +++ port/posix/posix_timeout.c | 20 ++++++++++++++++---- port/posix/posix_timeout.h | 2 +- src/wh_comm.c | 6 +++++- test/Makefile | 2 +- wolfhsm/wh_timeout.h | 2 +- 6 files changed, 27 insertions(+), 8 deletions(-) diff --git a/docs/draft/timeout.md b/docs/draft/timeout.md index 4f56e05bc..46080146f 100644 --- a/docs/draft/timeout.md +++ b/docs/draft/timeout.md @@ -11,6 +11,9 @@ posixTimeoutContext posixCtx = {0}; posixTimeoutConfig posixCfg = {.timeoutUs = WH_SEC_TO_USEC(5)}; whTimeoutCb timeoutCbTable = POSIX_TIMEOUT_CB; +/* NOTE: The callback table, platform context, and expiredCtx must remain valid + * for the lifetime of the whCommClient/whTimeout instance. Do not use stack + * locals that go out of scope while the client is still in use. */ whTimeoutConfig timeoutCfg = { .cb = &timeoutCbTable, /* platform callback table */ .context = &posixCtx, /* platform context */ diff --git a/port/posix/posix_timeout.c b/port/posix/posix_timeout.c index 3be623d55..1f1459393 100644 --- a/port/posix/posix_timeout.c +++ b/port/posix/posix_timeout.c @@ -20,7 +20,7 @@ * port/posix/posix_timeout.c * * POSIX implementation of the wolfHSM timeout abstraction. - * Uses posixGetTime() from posix_time.h for time measurement. + * Uses CLOCK_MONOTONIC for time measurement. */ #include "wolfhsm/wh_settings.h" @@ -28,13 +28,25 @@ #ifdef WOLFHSM_CFG_ENABLE_TIMEOUT #include +#include #include "wolfhsm/wh_error.h" #include "wolfhsm/wh_timeout.h" -#include "port/posix/posix_time.h" #include "port/posix/posix_timeout.h" +/* Use CLOCK_MONOTONIC for timeout measurement to avoid issues with wall-clock + * adjustments (NTP, manual changes, etc.) that could cause spurious expirations + * or overly long timeouts. */ +static uint64_t _getMonotonicTimeUs(void) +{ + struct timespec ts; + if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) { + return 0; + } + return (uint64_t)ts.tv_sec * 1000000ULL + (uint64_t)(ts.tv_nsec / 1000); +} + int posixTimeout_Init(void* context, const void* config) { posixTimeoutContext* ctx = (posixTimeoutContext*)context; @@ -107,7 +119,7 @@ int posixTimeout_Start(void* context) return WH_ERROR_NOTREADY; } - ctx->startUs = posixGetTime(); + ctx->startUs = _getMonotonicTimeUs(); ctx->started = 1; return WH_ERROR_OK; @@ -150,7 +162,7 @@ int posixTimeout_Expired(void* context, int* expired) return WH_ERROR_OK; } - nowUs = posixGetTime(); + nowUs = _getMonotonicTimeUs(); *expired = ((nowUs - ctx->startUs) >= ctx->timeoutUs) ? 1 : 0; return WH_ERROR_OK; diff --git a/port/posix/posix_timeout.h b/port/posix/posix_timeout.h index 5bc818c71..516e7742d 100644 --- a/port/posix/posix_timeout.h +++ b/port/posix/posix_timeout.h @@ -20,7 +20,7 @@ * port/posix/posix_timeout.h * * POSIX implementation of the wolfHSM timeout abstraction. - * Uses posixGetTime() for time measurement. + * Uses CLOCK_MONOTONIC for time measurement. */ #ifndef PORT_POSIX_POSIX_TIMEOUT_H_ diff --git a/src/wh_comm.c b/src/wh_comm.c index 5f96cdcf4..d84a4e21a 100644 --- a/src/wh_comm.c +++ b/src/wh_comm.c @@ -189,9 +189,13 @@ int wh_CommClient_RecvResponse(whCommClient* context, } #ifdef WOLFHSM_CFG_ENABLE_TIMEOUT else if (rc == WH_ERROR_NOTREADY) { - if (wh_Timeout_Expired(&context->respTimeout)) { + int expired = wh_Timeout_Expired(&context->respTimeout); + if (expired > 0) { rc = WH_ERROR_TIMEOUT; } + else if (expired < 0) { + rc = expired; + } } #endif return rc; diff --git a/test/Makefile b/test/Makefile index 623492cd5..259218045 100644 --- a/test/Makefile +++ b/test/Makefile @@ -36,7 +36,7 @@ DEF += -DWOLFHSM_CFG_TEST_POSIX ARCHFLAGS ?= # Enable extra C compiler warnings -CFLAGS_EXTRA = -Werror -Wall -Wextra -g +CFLAGS_EXTRA = -Werror -Wall -Wextra # Place functions / data into separate sections to allow unused code removal CFLAGS_EXTRA += -ffunction-sections -fdata-sections diff --git a/wolfhsm/wh_timeout.h b/wolfhsm/wh_timeout.h index 0285adbb0..53526805d 100644 --- a/wolfhsm/wh_timeout.h +++ b/wolfhsm/wh_timeout.h @@ -40,7 +40,7 @@ #include /* Time conversion macros */ -#define WH_MSEC_TO_USEC(usec) ((usec) * (1000ULL)) +#define WH_MSEC_TO_USEC(ms) ((ms) * (1000ULL)) #define WH_SEC_TO_USEC(sec) ((sec) * (1000000ULL)) #define WH_MIN_TO_USEC(min) ((min) * (WH_SEC_TO_USEC(60))) From f913dda55af6156963e4b649ad128fa7cf62787c Mon Sep 17 00:00:00 2001 From: Alex Lanzano Date: Fri, 6 Mar 2026 16:37:33 -0500 Subject: [PATCH 12/17] Address Brett's comments --- port/posix/posix_timeout.c | 36 +++--- port/posix/posix_timeout.h | 2 +- src/wh_comm.c | 2 +- src/wh_timeout.c | 7 +- test/wh_test.c | 11 +- test/wh_test_timeout.c | 224 +++++++++++++++++++++---------------- test/wh_test_timeout.h | 14 ++- wolfhsm/wh_comm.h | 2 - wolfhsm/wh_timeout.h | 9 +- 9 files changed, 179 insertions(+), 128 deletions(-) diff --git a/port/posix/posix_timeout.c b/port/posix/posix_timeout.c index 1f1459393..b72aee7ec 100644 --- a/port/posix/posix_timeout.c +++ b/port/posix/posix_timeout.c @@ -56,14 +56,9 @@ int posixTimeout_Init(void* context, const void* config) return WH_ERROR_BADARGS; } - /* Already initialized? */ - if (ctx->initialized) { - return WH_ERROR_OK; - } - ctx->startUs = 0; ctx->timeoutUs = (cfg != NULL) ? cfg->timeoutUs : 0; - ctx->started = 0; + ctx->running = 0; ctx->initialized = 1; return WH_ERROR_OK; @@ -77,14 +72,9 @@ int posixTimeout_Cleanup(void* context) return WH_ERROR_BADARGS; } - /* Not initialized? */ - if (!ctx->initialized) { - return WH_ERROR_OK; - } - ctx->startUs = 0; ctx->timeoutUs = 0; - ctx->started = 0; + ctx->running = 0; ctx->initialized = 0; return WH_ERROR_OK; @@ -99,7 +89,7 @@ int posixTimeout_Set(void* context, uint64_t timeoutUs) } if (!ctx->initialized) { - return WH_ERROR_NOTREADY; + return WH_ERROR_BADARGS; } ctx->timeoutUs = timeoutUs; @@ -116,11 +106,14 @@ int posixTimeout_Start(void* context) } if (!ctx->initialized) { - return WH_ERROR_NOTREADY; + return WH_ERROR_BADARGS; } ctx->startUs = _getMonotonicTimeUs(); - ctx->started = 1; + if (ctx->startUs == 0) { + return WH_ERROR_ABORTED; + } + ctx->running = 1; return WH_ERROR_OK; } @@ -134,11 +127,11 @@ int posixTimeout_Stop(void* context) } if (!ctx->initialized) { - return WH_ERROR_NOTREADY; + return WH_ERROR_BADARGS; } ctx->startUs = 0; - ctx->started = 0; + ctx->running = 0; return WH_ERROR_OK; } @@ -153,16 +146,19 @@ int posixTimeout_Expired(void* context, int* expired) } if (!ctx->initialized) { - return WH_ERROR_NOTREADY; + return WH_ERROR_BADARGS; } /* Not started or no timeout configured = not expired */ - if (!ctx->started || (ctx->timeoutUs == 0)) { + if (!ctx->running || (ctx->timeoutUs == 0)) { *expired = 0; return WH_ERROR_OK; } - nowUs = _getMonotonicTimeUs(); + nowUs = _getMonotonicTimeUs(); + if (nowUs == 0) { + return WH_ERROR_ABORTED; + } *expired = ((nowUs - ctx->startUs) >= ctx->timeoutUs) ? 1 : 0; return WH_ERROR_OK; diff --git a/port/posix/posix_timeout.h b/port/posix/posix_timeout.h index 516e7742d..8fd28b0d8 100644 --- a/port/posix/posix_timeout.h +++ b/port/posix/posix_timeout.h @@ -43,7 +43,7 @@ typedef struct posixTimeoutConfig_t { typedef struct posixTimeoutContext_t { uint64_t startUs; /* Snapshot of start time */ uint64_t timeoutUs; /* Configured timeout duration */ - int started; /* 1 if timer is running, 0 otherwise */ + int running; /* 1 if timer is running, 0 otherwise */ int initialized; /* 1 if initialized, 0 otherwise */ } posixTimeoutContext; diff --git a/src/wh_comm.c b/src/wh_comm.c index d84a4e21a..a7c87dbeb 100644 --- a/src/wh_comm.c +++ b/src/wh_comm.c @@ -134,7 +134,7 @@ int wh_CommClient_SendRequest(whCommClient* context, uint16_t magic, context->seq++; if (out_seq != NULL) *out_seq = context->seq; #ifdef WOLFHSM_CFG_ENABLE_TIMEOUT - (void)wh_Timeout_Start(&context->respTimeout); + rc = wh_Timeout_Start(&context->respTimeout); #endif } return rc; diff --git a/src/wh_timeout.c b/src/wh_timeout.c index f95f8c820..a548ff13a 100644 --- a/src/wh_timeout.c +++ b/src/wh_timeout.c @@ -79,7 +79,10 @@ int wh_Timeout_Cleanup(whTimeout* timeout) } if ((timeout->cb != NULL) && (timeout->cb->cleanup != NULL)) { - (void)timeout->cb->cleanup(timeout->context); + int ret = timeout->cb->cleanup(timeout->context); + if (ret != WH_ERROR_OK) { + return ret; + } } /* Zero the entire structure to make post-cleanup state distinguishable */ @@ -148,7 +151,7 @@ int wh_Timeout_Expired(whTimeout* timeout) int ret = 0; if (timeout == NULL) { - return 0; + return WH_ERROR_BADARGS; } /* Not initialized or no callbacks = never expired */ diff --git a/test/wh_test.c b/test/wh_test.c index 73f4eccf2..ac0d0a316 100644 --- a/test/wh_test.c +++ b/test/wh_test.c @@ -75,9 +75,6 @@ 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 @@ -129,6 +126,10 @@ int whTest_Unit(void) #endif /* !WOLFHSM_CFG_NO_CRYPTO */ +#if defined(WOLFHSM_CFG_ENABLE_TIMEOUT) && defined(WOLFHSM_CFG_TEST_POSIX) + WH_TEST_ASSERT(0 == whTest_TimeoutPosix()); +#endif + return 0; } #endif /* WOLFHSM_CFG_ENABLE_CLIENT && WOLFHSM_CFG_ENABLE_SERVER */ @@ -162,6 +163,10 @@ int whTest_ClientConfig(whClientConfig* clientCfg) WH_TEST_RETURN_ON_FAIL(whTest_WolfCryptTestCfg(clientCfg)); #endif /* WOLFHSM_CFG_TEST_WOLFCRYPTTEST */ +#if defined(WOLFHSM_CFG_ENABLE_TIMEOUT) + WH_TEST_RETURN_ON_FAIL(whTest_TimeoutClientConfig(clientCfg)); +#endif /* WOLFHSM_CFG_ENABLE_TIMEOUT */ + return WH_ERROR_OK; } diff --git a/test/wh_test_timeout.c b/test/wh_test_timeout.c index 41a0894ce..6bad2959e 100644 --- a/test/wh_test_timeout.c +++ b/test/wh_test_timeout.c @@ -22,7 +22,6 @@ */ #include -#include #include "wolfhsm/wh_settings.h" @@ -30,19 +29,21 @@ #include "wolfhsm/wh_timeout.h" #include "wolfhsm/wh_error.h" - -#include "port/posix/posix_timeout.h" +#include "wolfhsm/wh_comm.h" +#include "wolfhsm/wh_transport_mem.h" +#include "wolfhsm/wh_client.h" #include "wh_test_common.h" #include "wh_test_timeout.h" +#ifdef WOLFHSM_CFG_TEST_POSIX +#include "port/posix/posix_timeout.h" +#endif /* WOLFHSM_CFG_TEST_POSIX */ + #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" @@ -51,17 +52,8 @@ #endif /* HAVE_AES_CBC */ #endif /* !WOLFHSM_CFG_NO_CRYPTO */ -static int whTest_TimeoutCb(whTimeout* timeout, int* isExpired) -{ - (void)isExpired; - int* counter = (int*)timeout->expiredCtx; - if (counter != NULL) { - (*counter)++; - } - return WH_ERROR_OK; -} - -#if !defined(WOLFHSM_CFG_NO_CRYPTO) && defined(HAVE_AES_CBC) +#if defined(WOLFHSM_CFG_TEST_POSIX) && defined(WOLFHSM_CFG_ENABLE_SERVER) && \ + !defined(WOLFHSM_CFG_NO_CRYPTO) && defined(HAVE_AES_CBC) #define TIMEOUT_TEST_BUFFER_SIZE 4096 #define TIMEOUT_TEST_FLASH_RAM_SIZE (1024 * 1024) @@ -99,29 +91,29 @@ static int whTest_TimeoutAesCbc(void) }}; /* Client configuration with timeout */ - posixTimeoutContext posixCtx = {0}; - posixTimeoutConfig posixCfg = {.timeoutUs = 1}; - whTimeoutCb timeoutCbTable = POSIX_TIMEOUT_CB; - whTimeoutConfig timeoutCfg = { - .cb = &timeoutCbTable, - .context = &posixCtx, - .config = &posixCfg, - .expiredCb = NULL, - .expiredCtx = NULL, + posixTimeoutContext posixCtx = {0}; + posixTimeoutConfig posixCfg = {.timeoutUs = 1}; + whTimeoutCb timeoutCbTable = POSIX_TIMEOUT_CB; + whTimeoutConfig timeoutCfg = { + .cb = &timeoutCbTable, + .context = &posixCtx, + .config = &posixCfg, + .expiredCb = NULL, + .expiredCtx = 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, - .respTimeoutConfig = &timeoutCfg, + .transport_cb = tccb, + .transport_context = (void*)tmcc, + .transport_config = (void*)tmcf, + .client_id = WH_TEST_DEFAULT_CLIENT_ID, + .connect_cb = _timeoutTestConnectCb, + .respTimeoutConfig = &timeoutCfg, }}; whClientConfig c_conf[1] = {{ - .comm = cc_conf, + .comm = cc_conf, }}; whClientContext client[1] = {0}; @@ -260,29 +252,29 @@ static int whTest_TimeoutAesCbcOverride(void) }}; /* Client configuration with timeout and override callback */ - posixTimeoutContext posixCtx = {0}; - posixTimeoutConfig posixCfg = {.timeoutUs = 1}; - whTimeoutCb timeoutCbTable = POSIX_TIMEOUT_CB; - whTimeoutConfig timeoutCfg = { - .cb = &timeoutCbTable, - .context = &posixCtx, - .config = &posixCfg, - .expiredCb = _timeoutOverrideCb, - .expiredCtx = &cb_count, + posixTimeoutContext posixCtx = {0}; + posixTimeoutConfig posixCfg = {.timeoutUs = 1}; + whTimeoutCb timeoutCbTable = POSIX_TIMEOUT_CB; + whTimeoutConfig timeoutCfg = { + .cb = &timeoutCbTable, + .context = &posixCtx, + .config = &posixCfg, + .expiredCb = _timeoutOverrideCb, + .expiredCtx = &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, - .respTimeoutConfig = &timeoutCfg, + .transport_cb = tccb, + .transport_context = (void*)tmcc, + .transport_config = (void*)tmcf, + .client_id = WH_TEST_DEFAULT_CLIENT_ID, + .connect_cb = _timeoutTestConnectCb, + .respTimeoutConfig = &timeoutCfg, }}; whClientConfig c_conf[1] = {{ - .comm = cc_conf, + .comm = cc_conf, }}; whClientContext client[1] = {0}; @@ -386,73 +378,117 @@ static int whTest_TimeoutAesCbcOverride(void) return WH_ERROR_OK; } -#endif /* !WOLFHSM_CFG_NO_CRYPTO && HAVE_AES_CBC */ +#endif /* WOLFHSM_CFG_TEST_POSIX && WOLFHSM_CFG_ENABLE_SERVER && \ + !WOLFHSM_CFG_NO_CRYPTO && HAVE_AES_CBC */ -int whTest_Timeout(void) +/* Generic timeout API test - no platform dependencies */ +static int whTest_TimeoutApi(void) { - WH_TEST_PRINT("Testing timeout...\n"); - int cb_count = 0; - posixTimeoutContext posixCtx = {0}; - posixTimeoutConfig posixCfg = {.timeoutUs = 1}; - whTimeoutCb timeoutCbTable = POSIX_TIMEOUT_CB; - whTimeoutConfig cfg = { - .cb = &timeoutCbTable, - .context = &posixCtx, - .config = &posixCfg, - .expiredCb = whTest_TimeoutCb, - .expiredCtx = &cb_count, - }; + WH_TEST_PRINT("Testing timeout API...\n"); whTimeout timeout[1]; - wh_Timeout_Init(timeout, &cfg); - WH_TEST_ASSERT_RETURN(posixCtx.startUs == 0); - WH_TEST_ASSERT_RETURN(posixCtx.timeoutUs == posixCfg.timeoutUs); - WH_TEST_ASSERT_RETURN(timeout->expiredCb == cfg.expiredCb); - WH_TEST_ASSERT_RETURN(timeout->expiredCtx == cfg.expiredCtx); - - wh_Timeout_Start(timeout); - WH_TEST_ASSERT_RETURN(posixCtx.started == 1); - - wh_Timeout_Stop(timeout); - WH_TEST_ASSERT_RETURN(posixCtx.startUs == 0); - WH_TEST_ASSERT_RETURN(posixCtx.started == 0); - - /* No expiration when stopped */ - WH_TEST_ASSERT_RETURN(wh_Timeout_Expired(timeout) == 0); - - /* Test expired callback fires and increments counter */ - cb_count = 0; - memset(&posixCtx, 0, sizeof(posixCtx)); - 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_Timeout_Cleanup(timeout); - /* Test no-op mode (NULL config = timeout disabled, never expires) */ wh_Timeout_Init(timeout, NULL); + WH_TEST_ASSERT_RETURN(wh_Timeout_Set(timeout, 1000) == WH_ERROR_OK); WH_TEST_ASSERT_RETURN(wh_Timeout_Start(timeout) == WH_ERROR_OK); WH_TEST_ASSERT_RETURN(wh_Timeout_Expired(timeout) == 0); WH_TEST_ASSERT_RETURN(wh_Timeout_Stop(timeout) == WH_ERROR_OK); wh_Timeout_Cleanup(timeout); + /* Test Set on uninitialized timeout returns error */ + WH_TEST_ASSERT_RETURN(wh_Timeout_Set(timeout, 1000) == WH_ERROR_BADARGS); + /* Test bad arguments */ 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); + WH_TEST_ASSERT_RETURN(wh_Timeout_Expired(0) == WH_ERROR_BADARGS); WH_TEST_ASSERT_RETURN(wh_Timeout_Cleanup(0) == WH_ERROR_BADARGS); -#if !defined(WOLFHSM_CFG_NO_CRYPTO) && defined(HAVE_AES_CBC) + return WH_ERROR_OK; +} + +static int whTest_TimeoutResponse(whClientContext* client) +{ + WH_TEST_PRINT("Testing timeout response...\n"); + int rc = 0; + uint8_t echoData[] = "hello"; + uint8_t respData[sizeof(echoData)] = {0}; + uint16_t respLen = 0; + + /* Send an echo request into the void (no server will process it) */ + rc = wh_Client_EchoRequest(client, sizeof(echoData), echoData); + WH_TEST_ASSERT_RETURN(rc == WH_ERROR_OK); + + /* Poll for response - should time out */ + do { + rc = wh_Client_EchoResponse(client, &respLen, respData); + } while (rc == WH_ERROR_NOTREADY); + WH_TEST_ASSERT_RETURN(rc == WH_ERROR_TIMEOUT); + + return WH_ERROR_OK; +} + +int whTest_TimeoutClientConfig(whClientConfig* config) +{ + WH_TEST_PRINT("Testing timeout client config...\n"); + whClientContext client[1] = {0}; + + WH_TEST_RETURN_ON_FAIL(whTest_TimeoutApi()); + + if (config != NULL && config->comm != NULL && + config->comm->respTimeoutConfig != NULL) { + WH_TEST_RETURN_ON_FAIL(wh_Client_Init(client, config)); + WH_TEST_RETURN_ON_FAIL(whTest_TimeoutResponse(client)); + WH_TEST_RETURN_ON_FAIL(wh_Client_Cleanup(client)); + } + +#if defined(WOLFHSM_CFG_TEST_POSIX) && defined(WOLFHSM_CFG_ENABLE_SERVER) && \ + !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; + return WH_ERROR_OK; +} + +#ifdef WOLFHSM_CFG_TEST_POSIX +int whTest_TimeoutPosix(void) +{ + WH_TEST_PRINT("Testing timeout (POSIX)...\n"); + + uint8_t req[4096] = {0}; + uint8_t resp[4096] = {0}; + whTransportMemConfig tmcf[1] = {{ + .req = (whTransportMemCsr*)req, + .req_size = sizeof(req), + .resp = (whTransportMemCsr*)resp, + .resp_size = sizeof(resp), + }}; + posixTimeoutContext posixCtx = {0}; + posixTimeoutConfig posixCfg = {.timeoutUs = 1}; + whTimeoutCb timeoutCbTable = POSIX_TIMEOUT_CB; + whTimeoutConfig timeoutCfg = { + .cb = &timeoutCbTable, + .context = &posixCtx, + .config = &posixCfg, + }; + whTransportClientCb tccb[1] = {WH_TRANSPORT_MEM_CLIENT_CB}; + whTransportMemClientContext tmcc[1] = {0}; + whCommClientConfig ccConf[1] = {{ + .transport_cb = tccb, + .transport_context = (void*)tmcc, + .transport_config = (void*)tmcf, + .client_id = WH_TEST_DEFAULT_CLIENT_ID, + .respTimeoutConfig = &timeoutCfg, + }}; + whClientConfig cConf[1] = {{ + .comm = ccConf, + }}; + + return whTest_TimeoutClientConfig(cConf); } +#endif /* WOLFHSM_CFG_TEST_POSIX */ #endif /* WOLFHSM_CFG_ENABLE_TIMEOUT */ diff --git a/test/wh_test_timeout.h b/test/wh_test_timeout.h index d8ebebb36..22f999044 100644 --- a/test/wh_test_timeout.h +++ b/test/wh_test_timeout.h @@ -24,11 +24,21 @@ #ifndef TEST_WH_TEST_TIMEOUT_H_ #define TEST_WH_TEST_TIMEOUT_H_ +#include "wolfhsm/wh_client.h" + +/** + * Runs timeout module tests against the given client configuration. + * + * @param[in] config Client configuration with timeout support enabled. + * @return 0 on success and a non-zero error code on failure. + */ +int whTest_TimeoutClientConfig(whClientConfig* config); + /** - * Runs timeout module tests. + * Runs timeout tests using a default POSIX configuration. * * @return 0 on success and a non-zero error code on failure. */ -int whTest_Timeout(void); +int whTest_TimeoutPosix(void); #endif /* TEST_WH_TEST_TIMEOUT_H_ */ diff --git a/wolfhsm/wh_comm.h b/wolfhsm/wh_comm.h index f0547c205..a0cc24206 100644 --- a/wolfhsm/wh_comm.h +++ b/wolfhsm/wh_comm.h @@ -45,9 +45,7 @@ #include /* For sized ints */ -#ifdef WOLFHSM_CFG_ENABLE_TIMEOUT #include "wolfhsm/wh_timeout.h" -#endif /** Packet content types */ /* Request/response packets are composed of a single fixed-length header diff --git a/wolfhsm/wh_timeout.h b/wolfhsm/wh_timeout.h index 53526805d..756f2a3a3 100644 --- a/wolfhsm/wh_timeout.h +++ b/wolfhsm/wh_timeout.h @@ -122,7 +122,9 @@ struct whTimeout_t { whTimeoutExpiredCb expiredCb; /* Application expired callback */ void* expiredCtx; /* Application callback context */ int initialized; - uint8_t WH_PAD[4]; +#if UINTPTR_MAX == UINT64_MAX + uint8_t WH_PAD[4]; +#endif }; /** @@ -197,8 +199,9 @@ int wh_Timeout_Stop(whTimeout* timeout); * before returning. The callback may set *isExpired to 0 to override * (suppress) the expiration. * - * @param[in] timeout The timeout instance. - * @return 1 if expired, 0 if not expired or disabled, or negative error code. + * @param[in] timeout The timeout instance. Must not be NULL. + * @return 1 if expired, 0 if not expired or disabled, WH_ERROR_BADARGS if + * timeout is NULL, or negative error code on callback failure. */ int wh_Timeout_Expired(whTimeout* timeout); From 1cff09fdfe9cea819c13568771559f6945b20813 Mon Sep 17 00:00:00 2001 From: Alex Lanzano Date: Sat, 7 Mar 2026 11:12:39 -0500 Subject: [PATCH 13/17] Address copilot comments --- src/wh_comm.c | 4 +++- test/wh_test_timeout.c | 12 ++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/wh_comm.c b/src/wh_comm.c index a7c87dbeb..7d2aeba9d 100644 --- a/src/wh_comm.c +++ b/src/wh_comm.c @@ -133,10 +133,12 @@ int wh_CommClient_SendRequest(whCommClient* context, uint16_t magic, if (rc == 0) { context->seq++; if (out_seq != NULL) *out_seq = context->seq; + } #ifdef WOLFHSM_CFG_ENABLE_TIMEOUT + if (rc == 0) { rc = wh_Timeout_Start(&context->respTimeout); -#endif } +#endif return rc; } diff --git a/test/wh_test_timeout.c b/test/wh_test_timeout.c index 6bad2959e..eb03e8cda 100644 --- a/test/wh_test_timeout.c +++ b/test/wh_test_timeout.c @@ -77,8 +77,8 @@ static int _timeoutTestConnectCb(void* context, whCommConnected connected) static int whTest_TimeoutAesCbc(void) { - WH_TEST_PRINT("Testing timeout AES CBC...\n"); int rc = 0; + WH_TEST_PRINT("Testing timeout AES CBC...\n"); /* Transport memory configuration */ uint8_t req[TIMEOUT_TEST_BUFFER_SIZE] = {0}; @@ -229,7 +229,7 @@ static int _timeoutOverrideCb(whTimeout* timeout, int* isExpired) if (*counter <= 1) { /* First expiration: override and restart the timer */ *isExpired = 0; - wh_Timeout_Start(timeout); + return wh_Timeout_Start(timeout); } /* Subsequent expirations: let it expire normally */ return WH_ERROR_OK; @@ -237,9 +237,9 @@ static int _timeoutOverrideCb(whTimeout* timeout, int* isExpired) static int whTest_TimeoutAesCbcOverride(void) { - WH_TEST_PRINT("Testing timeout AES CBC with override callback...\n"); int rc = 0; int cb_count = 0; + WH_TEST_PRINT("Testing timeout AES CBC with override callback...\n"); /* Transport memory configuration */ uint8_t req[TIMEOUT_TEST_BUFFER_SIZE] = {0}; @@ -384,8 +384,8 @@ static int whTest_TimeoutAesCbcOverride(void) /* Generic timeout API test - no platform dependencies */ static int whTest_TimeoutApi(void) { - WH_TEST_PRINT("Testing timeout API...\n"); whTimeout timeout[1]; + WH_TEST_PRINT("Testing timeout API...\n"); /* Test no-op mode (NULL config = timeout disabled, never expires) */ wh_Timeout_Init(timeout, NULL); @@ -411,11 +411,11 @@ static int whTest_TimeoutApi(void) static int whTest_TimeoutResponse(whClientContext* client) { - WH_TEST_PRINT("Testing timeout response...\n"); int rc = 0; uint8_t echoData[] = "hello"; uint8_t respData[sizeof(echoData)] = {0}; uint16_t respLen = 0; + WH_TEST_PRINT("Testing timeout response...\n"); /* Send an echo request into the void (no server will process it) */ rc = wh_Client_EchoRequest(client, sizeof(echoData), echoData); @@ -432,8 +432,8 @@ static int whTest_TimeoutResponse(whClientContext* client) int whTest_TimeoutClientConfig(whClientConfig* config) { - WH_TEST_PRINT("Testing timeout client config...\n"); whClientContext client[1] = {0}; + WH_TEST_PRINT("Testing timeout client config...\n"); WH_TEST_RETURN_ON_FAIL(whTest_TimeoutApi()); From e0071926cb25908cc89c10f23657dc974a48f99e Mon Sep 17 00:00:00 2001 From: Brett Nicholas <7547222+bigbrett@users.noreply.github.com> Date: Tue, 10 Mar 2026 09:31:00 -0600 Subject: [PATCH 14/17] quick fixes after review --- test/wh_test_timeout.c | 4 ++-- wolfhsm/wh_timeout.h | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/test/wh_test_timeout.c b/test/wh_test_timeout.c index eb03e8cda..9b65a0281 100644 --- a/test/wh_test_timeout.c +++ b/test/wh_test_timeout.c @@ -418,8 +418,8 @@ static int whTest_TimeoutResponse(whClientContext* client) WH_TEST_PRINT("Testing timeout response...\n"); /* Send an echo request into the void (no server will process it) */ - rc = wh_Client_EchoRequest(client, sizeof(echoData), echoData); - WH_TEST_ASSERT_RETURN(rc == WH_ERROR_OK); + WH_TEST_RETURN_ON_FAIL( + wh_Client_EchoRequest(client, sizeof(echoData), echoData)); /* Poll for response - should time out */ do { diff --git a/wolfhsm/wh_timeout.h b/wolfhsm/wh_timeout.h index 756f2a3a3..6cd676af4 100644 --- a/wolfhsm/wh_timeout.h +++ b/wolfhsm/wh_timeout.h @@ -121,10 +121,8 @@ struct whTimeout_t { void* context; /* Platform context */ whTimeoutExpiredCb expiredCb; /* Application expired callback */ void* expiredCtx; /* Application callback context */ - int initialized; -#if UINTPTR_MAX == UINT64_MAX - uint8_t WH_PAD[4]; -#endif + int32_t initialized; + uint8_t WH_PAD[4]; }; /** From bdcf0fe5ca5ed7409660b057b810f468548e9ecd Mon Sep 17 00:00:00 2001 From: Brett Nicholas <7547222+bigbrett@users.noreply.github.com> Date: Tue, 10 Mar 2026 09:37:30 -0600 Subject: [PATCH 15/17] update docs --- docs/draft/timeout.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/draft/timeout.md b/docs/draft/timeout.md index 46080146f..4f9583fb0 100644 --- a/docs/draft/timeout.md +++ b/docs/draft/timeout.md @@ -1,5 +1,11 @@ # Timeout Functionality: Client Perspective +## Overview + +The response timeout feature is primarily designed to prevent the client from blocking indefinitely when the server fails to respond to a request. This is most relevant for **blocking operations** such as wolfCrypt cryptographic calls, where the client sends a request and polls for a response in a tight loop. Without a timeout, a non-responsive server would cause the client to hang forever. + +Since the timeout is checked inside `wh_CommClient_RecvResponse`, it **can** also apply to the split (async) API where the caller manually polls `RecvResponse`. However, in the async case the timeout is only evaluated each time the caller invokes `RecvResponse` -- it does not proactively notify the caller or fire asynchronously. If the caller is not actively polling, the timeout has no effect. + ## 1. Configuration at Init Time 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. @@ -44,7 +50,9 @@ The timeout is handled transparently in the comm layer: 1. **`wh_CommClient_SendRequest`**: After a successful send, starts the response timer via `wh_Timeout_Start()`. 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()`. -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. +For blocking (synchronous) client APIs, this means the internal `do { ... } while (ret == WH_ERROR_NOTREADY)` polling loop automatically gets timeout support -- the client will return `WH_ERROR_TIMEOUT` instead of spinning forever if the server does not respond within the configured deadline. + +For split (async) APIs where the application calls `SendRequest` and `RecvResponse` separately, the timeout check occurs each time `RecvResponse` is called and returns `WH_ERROR_NOTREADY`. The timeout does **not** interrupt the caller or provide out-of-band notification -- it is purely poll-based. ``` Client App CommClient whTimeout @@ -108,5 +116,7 @@ whTimeoutConfig timeoutCfg = { ``` ## 5. Design Notes +- **Primary use case: blocking wolfCrypt operations.** The timeout is designed to prevent indefinite hangs when the server fails to respond to blocking client API calls, which currently only exist when using the wolfCrypt API for crypto. These calls internally poll `RecvResponse` in a tight loop, and the timeout provides automatic protection against a non-responsive server. +- **Async API compatibility.** The timeout mechanism also works with the split wolfHSM `SendRequest`/`RecvResponse` API, but only checks for expiration when `RecvResponse` is called by the application. It is purely poll-driven, and there is no callback, signal, or interrupt that fires independently. If the application stops calling `RecvResponse`, the timeout will not trigger. - **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. - **Timer starts on send, checks on receive.** The timer window begins when a request is successfully sent, measuring the full round-trip wait. From 7c66bd507cf975082a2acc9d253c2ab78e30205b Mon Sep 17 00:00:00 2001 From: Brett Nicholas <7547222+bigbrett@users.noreply.github.com> Date: Tue, 10 Mar 2026 09:51:13 -0600 Subject: [PATCH 16/17] rebase changes --- test/wh_test_timeout.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/test/wh_test_timeout.c b/test/wh_test_timeout.c index 9b65a0281..78076057b 100644 --- a/test/wh_test_timeout.c +++ b/test/wh_test_timeout.c @@ -146,9 +146,7 @@ static int whTest_TimeoutAesCbc(void) 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, - }}; + whServerCryptoContext crypto[1] = {0}; whServerConfig s_conf[1] = {{ .comm_config = cs_conf, @@ -307,9 +305,7 @@ static int whTest_TimeoutAesCbcOverride(void) 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, - }}; + whServerCryptoContext crypto[1] = {0}; whServerConfig s_conf[1] = {{ .comm_config = cs_conf, From f650ef251a8c867eef247a627f2819cd84b0b7b4 Mon Sep 17 00:00:00 2001 From: Brett Nicholas <7547222+bigbrett@users.noreply.github.com> Date: Tue, 10 Mar 2026 10:06:59 -0600 Subject: [PATCH 17/17] remove unused timeout struct field in client context --- wolfhsm/wh_client.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/wolfhsm/wh_client.h b/wolfhsm/wh_client.h index 59fffd2b8..5fcac92fd 100644 --- a/wolfhsm/wh_client.h +++ b/wolfhsm/wh_client.h @@ -109,9 +109,6 @@ struct whClientContext_t { uint16_t last_req_id; uint16_t last_req_kind; uint32_t cryptoAffinity; -#ifdef WOLFHSM_CFG_ENABLE_TIMEOUT - whTimeout respTimeout; -#endif #ifdef WOLFHSM_CFG_DMA whClientDmaContext dma; #endif /* WOLFHSM_CFG_DMA */