diff --git a/src/internal.c b/src/internal.c index e32fe421eb..78bbeb034d 100644 --- a/src/internal.c +++ b/src/internal.c @@ -12178,8 +12178,8 @@ WC_MAYBE_UNUSED static int SendHandshakeMsg(WOLFSSL* ssl, byte* input, } -/* return bytes received, WOLFSSL_FATAL_ERROR on error, - * or BAD_FUNC_ARG if ssl is null */ +/* return bytes received, WANT_READ or WANT_WRITE to call again, + * WOLFSSL_FATAL_ERROR on error, or BAD_FUNC_ARG if ssl is null */ static int wolfSSLReceive(WOLFSSL* ssl, byte* buf, word32 sz) { int recvd; @@ -12265,8 +12265,14 @@ static int wolfSSLReceive(WOLFSSL* ssl, byte* buf, word32 sz) #ifdef WOLFSSL_DTLS #ifdef WOLFSSL_DTLS13 if (ssl->options.dtls && IsAtLeastTLSv1_3(ssl->version)) { - /* TODO: support WANT_WRITE here */ - if (Dtls13RtxTimeout(ssl) < 0) { + int rtxRet = Dtls13RtxTimeout(ssl); + if (rtxRet == WC_NO_ERR_TRACE(WANT_WRITE)) { + /* Record that this ACK or retransmit still owes a + * write, so the next flush sends it. */ + ssl->dtls13SendingAckOrRtx = 1; + return WC_NO_ERR_TRACE(WANT_WRITE); + } + if (rtxRet < 0) { WOLFSSL_MSG( "Error trying to retransmit DTLS buffered message"); return WOLFSSL_FATAL_ERROR; @@ -24328,6 +24334,11 @@ static int GetInputData_ex(WOLFSSL *ssl, word32 size, word32 readAhead) if (in == WC_NO_ERR_TRACE(WANT_READ)) return WC_NO_ERR_TRACE(WANT_READ); +#ifdef WOLFSSL_DTLS13 + if (in == WC_NO_ERR_TRACE(WANT_WRITE)) + return WC_NO_ERR_TRACE(WANT_WRITE); +#endif + if (in < 0) { WOLFSSL_ERROR_VERBOSE(SOCKET_ERROR_E); return SOCKET_ERROR_E; diff --git a/tests/api/test_dtls13.c b/tests/api/test_dtls13.c index 01a012c483..860cf04a84 100644 --- a/tests/api/test_dtls13.c +++ b/tests/api/test_dtls13.c @@ -2486,3 +2486,128 @@ int test_dtls13_reset_clears_alert_history(void) #endif return EXPECT_RESULT(); } + +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) +/* Reports a receive timeout for the first *ctx calls, then no data. A + * scheduled ACK takes one timeout of its own before the retransmit runs. */ +static int test_dtls13_rtx_timeout_read_cb(WOLFSSL *ssl, char *data, int sz, + void *ctx) +{ + int* timeouts = (int*)ctx; + (void)ssl; + (void)data; + (void)sz; + if (*timeouts > 0) { + (*timeouts)--; + return WOLFSSL_CBIO_ERR_TIMEOUT; + } + return WOLFSSL_CBIO_ERR_WANT_READ; +} +#endif + +/* A receive timeout makes DTLS 1.3 send an ACK or retransmit. When the + * transport cannot take it, the handshake reports want-write and the retry + * completes the send. */ +int test_dtls13_rtx_timeout_want_write(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL *ssl_c = NULL; + struct test_memio_ctx test_ctx; + int timeouts = 1; + int sentBefore; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, NULL, &ssl_c, NULL, + wolfDTLSv1_3_client_method, NULL), 0); + + /* Send the ClientHello flight and wait on the server. */ + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + sentBefore = test_ctx.s_len; + + /* The next receive reports a timeout while the transport refuses + * writes. */ + wolfSSL_SetIOReadCtx(ssl_c, &timeouts); + wolfSSL_SSLSetIORecv(ssl_c, test_dtls13_rtx_timeout_read_cb); + test_memio_simulate_want_write(&test_ctx, 1, 1); + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_WRITE); + ExpectIntEQ(test_ctx.s_len, sentBefore); + + /* With writes allowed again the retransmission reaches the peer. */ + test_memio_simulate_want_write(&test_ctx, 1, 0); + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntGT(test_ctx.s_len, sentBefore); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); +#endif + return EXPECT_RESULT(); +} + +/* The same timeout on the read path. wolfSSL_read() reports want-write and + * marks the record it held, which wolfSSL_dtls13_do_scheduled_work() sends. */ +int test_dtls13_rtx_timeout_want_write_read(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + const char msg[] = "read-path"; + const int msgLen = sizeof(msg); + char readBuf[sizeof(msg)]; + int timeouts = 2; + int sentBefore; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* Leave a KeyUpdate buffered so a timeout has something to retransmit. */ + ExpectIntEQ(wolfSSL_update_keys(ssl_c), WOLFSSL_SUCCESS); + sentBefore = test_ctx.s_len; + + /* The next receive reports a timeout while the transport refuses + * writes. */ + wolfSSL_SetIOReadCtx(ssl_c, &timeouts); + wolfSSL_SSLSetIORecv(ssl_c, test_dtls13_rtx_timeout_read_cb); + test_memio_simulate_want_write(&test_ctx, 1, 1); + XMEMSET(readBuf, 0, sizeof(readBuf)); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_WRITE); + ExpectIntEQ(test_ctx.s_len, sentBefore); + + /* Reading again reports no data rather than an error, and the record the + * timeout could not write is still owed. */ + test_memio_simulate_want_write(&test_ctx, 1, 0); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_dtls13_pending_work(ssl_c), 1); + + /* The pump sends it. */ + ExpectIntEQ(wolfSSL_dtls13_do_scheduled_work(ssl_c), WOLFSSL_SUCCESS); + ExpectIntGT(test_ctx.s_len, sentBefore); + ExpectIntEQ(wolfSSL_dtls13_pending_work(ssl_c), 0); + + /* The connection still carries application data. */ + wolfSSL_SSLSetIORecv(ssl_c, test_memio_read_cb); + wolfSSL_SetIOReadCtx(ssl_c, &test_ctx); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_write(ssl_s, msg, msgLen), msgLen); + XMEMSET(readBuf, 0, sizeof(readBuf)); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), msgLen); + ExpectStrEQ(readBuf, msg); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} diff --git a/tests/api/test_dtls13.h b/tests/api/test_dtls13.h index 5349fb0a04..b126dbad89 100644 --- a/tests/api/test_dtls13.h +++ b/tests/api/test_dtls13.h @@ -61,6 +61,8 @@ int test_dtls13_epoch_slot_reuse_replay(void); int test_dtls13_epoch_slot_reuse_decrypt_epoch(void); int test_dtls13_plaintext_ack_after_handshake(void); int test_dtls13_reset_clears_alert_history(void); +int test_dtls13_rtx_timeout_want_write(void); +int test_dtls13_rtx_timeout_want_write_read(void); #define TEST_DTLS13_DECLS \ TEST_DECL_GROUP("dtls13", test_dtls13_bad_epoch_ch), \ @@ -74,6 +76,8 @@ int test_dtls13_reset_clears_alert_history(void); TEST_DECL_GROUP("dtls13", test_dtls13_finished_send_error_propagation), \ TEST_DECL_GROUP("dtls13", test_dtls13_basic_connection_id), \ TEST_DECL_GROUP("dtls13", test_dtls13_hrr_want_write), \ + TEST_DECL_GROUP("dtls13", test_dtls13_rtx_timeout_want_write), \ + TEST_DECL_GROUP("dtls13", test_dtls13_rtx_timeout_want_write_read), \ TEST_DECL_GROUP("dtls13", test_dtls13_every_write_want_write), \ TEST_DECL_GROUP("dtls13", test_dtls13_epochs), \ TEST_DECL_GROUP("dtls13", test_dtls13_alert_with_pending_output), \