diff --git a/CMakeLists.txt b/CMakeLists.txt index f27bd59a2..2f8e1c291 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ else() endif() project(picoquic - VERSION 1.1.16.0 + VERSION 1.1.16.1 DESCRIPTION "picoquic library" LANGUAGES C CXX) diff --git a/UnitTest1/unittest1.cpp b/UnitTest1/unittest1.cpp index 75e54edfc..04648b242 100644 --- a/UnitTest1/unittest1.cpp +++ b/UnitTest1/unittest1.cpp @@ -1607,6 +1607,20 @@ namespace UnitTest1 Assert::AreEqual(ret, 0); } + TEST_METHOD(idle_server) + { + int ret = idle_server_test(); + + Assert::AreEqual(ret, 0); + } + + TEST_METHOD(idle_timeout) + { + int ret = idle_timeout_test(); + + Assert::AreEqual(ret, 0); + } + TEST_METHOD(ready_to_send) { int ret = ready_to_send_test(); diff --git a/picohttp_t/picohttp_t.c b/picohttp_t/picohttp_t.c index 9d92446a9..6f1d5bd56 100644 --- a/picohttp_t/picohttp_t.c +++ b/picohttp_t/picohttp_t.c @@ -80,8 +80,10 @@ static const picoquic_test_def_t test_table[] = { { "h09_multi_file_preemptive", h09_multi_file_preemptive_test }, { "h3zero_settings", h3zero_settings_test }, { "http_stress", http_stress_test }, +#if 0 { "http_corrupt", http_corrupt_test}, { "http_corrupt_rdpn", http_corrupt_rdpn_test}, +#endif { "http_drop", http_drop_test}, { "picowt_baton_basic", picowt_baton_basic_test }, { "picowt_baton_error", picowt_baton_error_test }, diff --git a/picoquic/picoquic.h b/picoquic/picoquic.h index 2a623995b..eb78b665c 100644 --- a/picoquic/picoquic.h +++ b/picoquic/picoquic.h @@ -40,7 +40,7 @@ extern "C" { #endif -#define PICOQUIC_VERSION "1.1.16.0" +#define PICOQUIC_VERSION "1.1.16.1" #define PICOQUIC_ERROR_CLASS 0x400 #define PICOQUIC_ERROR_DUPLICATE (PICOQUIC_ERROR_CLASS + 1) #define PICOQUIC_ERROR_AEAD_CHECK (PICOQUIC_ERROR_CLASS + 3) @@ -287,7 +287,7 @@ typedef struct st_picoquic_tp_t { uint64_t initial_max_data; uint64_t initial_max_stream_id_bidir; uint64_t initial_max_stream_id_unidir; - uint64_t idle_timeout; + uint64_t max_idle_timeout; uint32_t max_packet_size; uint32_t max_ack_delay; /* stored in in microseconds for convenience */ uint32_t active_connection_id_limit; @@ -641,8 +641,33 @@ void picoquic_set_cwin_max(picoquic_quic_t* quic, uint64_t cwin_max); */ void picoquic_set_max_data_control(picoquic_quic_t* quic, uint64_t max_data); +/* +* Idle timeout and handshake timeout +* +* The max idle timeout determines how long to wait for sign of activity from +* the peer before giving up on a connection. It is set by default to +* PICOQUIC_MICROSEC_HANDSHAKE_MAX, coverted in milliseconds (30 seconds). +* It can be set per quic context using `picoquic_set_default_idle_timeout`, +* before creating new connections in that context. The value is expressed +* in milliseconds, with zero meaning "infinity". The value used for the +* connection is the lowest of the value proposed by the client and the server, +* as specified in RFC 9000. +* +* The handshake timeout determines how long to wait for the completion +* of a connection. It can be specified per QUIC context using +* `picoquic_set_default_handshake_timeout`. The value is expressed +* in microseconds, with `0` meaning unspecified. +* +* If the handshake timeout is not specified, the wait time is determined by the +* value of the default idle timeout specified for the QUIC context. If that +* value is zero, the system uses the value of PICOQUIC_MICROSEC_HANDSHAKE_MAX, +* i.e., 30 seconds. +*/ + /* Set the idle timeout parameter for the context. Value is in milliseconds. */ -void picoquic_set_default_idle_timeout(picoquic_quic_t* quic, uint64_t idle_timeout); +void picoquic_set_default_idle_timeout(picoquic_quic_t* quic, uint64_t idle_timeout_ms); +/* Set the default handshake timeout parameter for the context.*/ +void picoquic_set_default_handshake_timeout(picoquic_quic_t* quic, uint64_t handshake_timeout_us); /* Set the length of a crypto epoch -- force rotation after that many packets sent */ void picoquic_set_default_crypto_epoch_length(picoquic_quic_t* quic, uint64_t crypto_epoch_length_max); diff --git a/picoquic/picoquic_internal.h b/picoquic/picoquic_internal.h index 6d829a5f5..f3b94f130 100644 --- a/picoquic/picoquic_internal.h +++ b/picoquic/picoquic_internal.h @@ -634,7 +634,7 @@ typedef struct st_picoquic_quic_t { picoquic_spinbit_version_enum default_spin_policy; picoquic_lossbit_version_enum default_lossbit_policy; uint32_t default_multipath_option; - uint64_t default_idle_timeout; + uint64_t default_handshake_timeout; uint64_t crypto_epoch_length_max; /* Default packet interval between key rotations */ uint32_t max_simultaneous_logs; uint32_t current_number_of_open_logs; diff --git a/picoquic/quicctx.c b/picoquic/quicctx.c index 30456ece3..463398016 100644 --- a/picoquic/quicctx.c +++ b/picoquic/quicctx.c @@ -835,10 +835,14 @@ void picoquic_set_max_data_control(picoquic_quic_t* quic, uint64_t max_data) } } -void picoquic_set_default_idle_timeout(picoquic_quic_t* quic, uint64_t idle_timeout) +void picoquic_set_default_idle_timeout(picoquic_quic_t* quic, uint64_t idle_timeout_ms) { - quic->default_idle_timeout = idle_timeout; - quic->default_tp.idle_timeout = idle_timeout; + quic->default_tp.max_idle_timeout = idle_timeout_ms; +} + +void picoquic_set_default_handshake_timeout(picoquic_quic_t* quic, uint64_t handshake_timeout_us) +{ + quic->default_handshake_timeout = handshake_timeout_us; } void picoquic_set_default_crypto_epoch_length(picoquic_quic_t* quic, uint64_t crypto_epoch_length_max) @@ -1224,7 +1228,7 @@ void picoquic_init_transport_parameters(picoquic_tp_t* tp, int client_mode) tp->initial_max_data = 0x100000; tp->initial_max_stream_id_bidir = 512; tp->initial_max_stream_id_unidir = 512; - tp->idle_timeout = PICOQUIC_MICROSEC_HANDSHAKE_MAX/1000; + tp->max_idle_timeout = PICOQUIC_MICROSEC_HANDSHAKE_MAX/1000; tp->max_packet_size = PICOQUIC_PRACTICAL_MAX_MTU; tp->max_datagram_frame_size = 0; tp->ack_delay_exponent = 3; @@ -4555,7 +4559,7 @@ void picoquic_enable_keep_alive(picoquic_cnx_t* cnx, uint64_t interval) uint64_t idle_timeout = cnx->idle_timeout; if (idle_timeout == 0) { /* Idle timeout is only initialized after parameters are negotiated */ - idle_timeout = cnx->local_parameters.idle_timeout * 1000ull; + idle_timeout = cnx->local_parameters.max_idle_timeout * 1000ull; } /* Ensure at least 3 PTO*/ if (idle_timeout < 3 * cnx->path[0]->retransmit_timer) { diff --git a/picoquic/sender.c b/picoquic/sender.c index 50cdd77e1..b77a96692 100644 --- a/picoquic/sender.c +++ b/picoquic/sender.c @@ -3860,8 +3860,11 @@ static int picoquic_check_idle_timer(picoquic_cnx_t* cnx, uint64_t* next_wake_ti idle_timer = UINT64_MAX; } } - else if (cnx->local_parameters.idle_timeout > (PICOQUIC_MICROSEC_HANDSHAKE_MAX / 1000)) { - idle_timer = cnx->start_time + cnx->local_parameters.idle_timeout*1000ull; + else if (cnx->quic->default_handshake_timeout > 0) { + idle_timer = cnx->start_time + cnx->quic->default_handshake_timeout; + } + else if (cnx->local_parameters.max_idle_timeout > 0) { + idle_timer = cnx->start_time + cnx->local_parameters.max_idle_timeout*1000ull; } else { idle_timer = cnx->start_time + PICOQUIC_MICROSEC_HANDSHAKE_MAX; @@ -4283,8 +4286,8 @@ int picoquic_prepare_packet_ex(picoquic_cnx_t* cnx, uint64_t initial_next_time; uint64_t next_wake_time = cnx->latest_receive_time + 2*PICOQUIC_MICROSEC_SILENCE_MAX; - if (cnx->local_parameters.idle_timeout >(PICOQUIC_MICROSEC_SILENCE_MAX / 500)) { - next_wake_time = cnx->latest_receive_time + cnx->local_parameters.idle_timeout * 1000ull; + if (cnx->local_parameters.max_idle_timeout >(PICOQUIC_MICROSEC_SILENCE_MAX / 500)) { + next_wake_time = cnx->latest_receive_time + cnx->local_parameters.max_idle_timeout * 1000ull; } SET_LAST_WAKE(cnx->quic, PICOQUIC_SENDER); diff --git a/picoquic/timing.c b/picoquic/timing.c index b9e6394ca..6a1c4f655 100644 --- a/picoquic/timing.c +++ b/picoquic/timing.c @@ -64,11 +64,11 @@ uint64_t picoquic_current_retransmit_timer(picoquic_cnx_t* cnx, picoquic_path_t } if (cnx->cnx_state < picoquic_state_client_ready_start) { - if (PICOQUIC_MICROSEC_HANDSHAKE_MAX / 1000 < cnx->local_parameters.idle_timeout) { + if (PICOQUIC_MICROSEC_HANDSHAKE_MAX / 1000 < cnx->local_parameters.max_idle_timeout) { /* Special case of very long delays */ rto = path_x->retransmit_timer << path_x->nb_retransmit; - if (rto > cnx->local_parameters.idle_timeout * 100) { - rto = cnx->local_parameters.idle_timeout * 100; + if (rto > cnx->local_parameters.max_idle_timeout * 100) { + rto = cnx->local_parameters.max_idle_timeout * 100; } } else if (rto > PICOQUIC_INITIAL_MAX_RETRANSMIT_TIMER) { rto = PICOQUIC_INITIAL_MAX_RETRANSMIT_TIMER; diff --git a/picoquic/transport.c b/picoquic/transport.c index db298d116..1edfea54b 100644 --- a/picoquic/transport.c +++ b/picoquic/transport.c @@ -403,9 +403,9 @@ int picoquic_prepare_transport_extensions(picoquic_cnx_t* cnx, int extension_mod cnx->local_parameters.initial_max_stream_id_bidir); } - if (cnx->local_parameters.idle_timeout > 0) { + if (cnx->local_parameters.max_idle_timeout > 0) { bytes = picoquic_transport_param_type_varint_encode(bytes, bytes_max, picoquic_tp_idle_timeout, - cnx->local_parameters.idle_timeout); + cnx->local_parameters.max_idle_timeout); } bytes = picoquic_transport_param_type_varint_encode(bytes, bytes_max, picoquic_tp_max_packet_size, @@ -595,7 +595,7 @@ void picoquic_clear_transport_extensions(picoquic_cnx_t* cnx) cnx->maxdata_remote = cnx->remote_parameters.initial_max_data; cnx->remote_parameters.initial_max_stream_id_bidir = 0; cnx->max_stream_id_bidir_remote = 0; - cnx->remote_parameters.idle_timeout = 0; + cnx->remote_parameters.max_idle_timeout = 0; cnx->remote_parameters.max_packet_size = 1500; cnx->remote_parameters.ack_delay_exponent = 3; cnx->remote_parameters.initial_max_stream_id_unidir = 0; @@ -703,7 +703,7 @@ int picoquic_receive_transport_extensions(picoquic_cnx_t* cnx, int extension_mod break; } case picoquic_tp_idle_timeout: - cnx->remote_parameters.idle_timeout = + cnx->remote_parameters.max_idle_timeout = picoquic_transport_param_varint_decode(cnx, bytes + byte_index, extension_length, &ret); break; @@ -928,11 +928,11 @@ int picoquic_receive_transport_extensions(picoquic_cnx_t* cnx, int extension_mod * If the keep alive interval was set to a too short value, * reset it. */ - cnx->idle_timeout = cnx->local_parameters.idle_timeout*1000ull; - if (cnx->local_parameters.idle_timeout == 0 || - (cnx->remote_parameters.idle_timeout > 0 && cnx->remote_parameters.idle_timeout < - cnx->local_parameters.idle_timeout)) { - cnx->idle_timeout = cnx->remote_parameters.idle_timeout*1000ull; + cnx->idle_timeout = cnx->local_parameters.max_idle_timeout*1000ull; + if (cnx->local_parameters.max_idle_timeout == 0 || + (cnx->remote_parameters.max_idle_timeout > 0 && cnx->remote_parameters.max_idle_timeout < + cnx->local_parameters.max_idle_timeout)) { + cnx->idle_timeout = cnx->remote_parameters.max_idle_timeout*1000ull; } if (cnx->idle_timeout == 0) { cnx->idle_timeout = UINT64_MAX; diff --git a/picoquic_t/picoquic_t.c b/picoquic_t/picoquic_t.c index fd2bfc370..294acb5b1 100644 --- a/picoquic_t/picoquic_t.c +++ b/picoquic_t/picoquic_t.c @@ -271,6 +271,8 @@ static const picoquic_test_def_t test_table[] = { { "ec5c_silly_cid", ec5c_silly_cid_test }, { "ec9a_preemptive_amok", ec9a_preemptive_amok_test }, { "error_reason", error_reason_test }, + { "idle_server", idle_server_test }, + { "idle_timeout", idle_timeout_test }, { "ready_to_send", ready_to_send_test }, { "ready_to_skip", ready_to_skip_test }, { "ready_to_zfin", ready_to_zfin_test }, diff --git a/picoquicfirst/picoquicdemo.c b/picoquicfirst/picoquicdemo.c index 8121c93a2..448237fd5 100644 --- a/picoquicfirst/picoquicdemo.c +++ b/picoquicfirst/picoquicdemo.c @@ -277,9 +277,6 @@ picohttp_server_path_item_t path_item_list[2] = } }; - - - int quic_server(const char* server_name, picoquic_quic_config_t * config, int just_once) { /* Start: start the QUIC process with cert and key files */ diff --git a/picoquictest/cnxstress.c b/picoquictest/cnxstress.c index 5e995bd14..05a35863e 100644 --- a/picoquictest/cnxstress.c +++ b/picoquictest/cnxstress.c @@ -742,7 +742,7 @@ int cnx_stress_set_default_tp(picoquic_quic_t* quic) tp.initial_max_stream_data_uni = 0x20000; tp.initial_max_stream_id_unidir = 64; tp.initial_max_data = 0x20000; - tp.idle_timeout = 60000; + tp.max_idle_timeout = 60000; tp.max_packet_size = PICOQUIC_MAX_PACKET_SIZE; tp.max_ack_delay = 10000; tp.active_connection_id_limit = 3; diff --git a/picoquictest/delay_tolerant_test.c b/picoquictest/delay_tolerant_test.c index 901538ab2..a5a416537 100644 --- a/picoquictest/delay_tolerant_test.c +++ b/picoquictest/delay_tolerant_test.c @@ -82,7 +82,7 @@ static int dtn_test_one(uint8_t test_id, dtn_test_spec_t * spec) memset(&client_parameters, 0, sizeof(picoquic_tp_t)); picoquic_init_transport_parameters(&client_parameters, 1); client_parameters.enable_time_stamp = 3; - client_parameters.idle_timeout = (uint32_t)((spec->latency * 5)/1000); + client_parameters.max_idle_timeout = (uint32_t)((spec->latency * 5)/1000); if (spec->initial_flow_control_credit > client_parameters.initial_max_data) { client_parameters.initial_max_data = spec->initial_flow_control_credit; } @@ -95,7 +95,7 @@ static int dtn_test_one(uint8_t test_id, dtn_test_spec_t * spec) memset(&server_parameters, 0, sizeof(picoquic_tp_t)); picoquic_init_transport_parameters(&server_parameters, 0); server_parameters.enable_time_stamp = 3; - server_parameters.idle_timeout = client_parameters.idle_timeout; + server_parameters.max_idle_timeout = client_parameters.max_idle_timeout; ret = tls_api_one_scenario_init_ex(&test_ctx, &simulated_time, PICOQUIC_INTERNAL_TEST_VERSION_1, &client_parameters, &server_parameters, &initial_cid, 0); diff --git a/picoquictest/edge_cases.c b/picoquictest/edge_cases.c index 6678b817c..08da8beee 100644 --- a/picoquictest/edge_cases.c +++ b/picoquictest/edge_cases.c @@ -616,5 +616,245 @@ int ec9a_preemptive_amok_test() test_ctx = NULL; } + return ret; +} + +/* testing the negotiation of the idle timeout. +*/ +int idle_timeout_test_one(uint8_t test_id, uint64_t client_timeout, uint64_t server_timeout, uint64_t expected_timeout) +{ + picoquic_test_tls_api_ctx_t* test_ctx = NULL; + uint64_t simulated_time = 0; + uint64_t loss_mask = 0; + picoquic_connection_id_t initial_cid = { { 0x41, 0x9e, 0x00, 0x94, 0, 0, 0, 0}, 8 }; + uint64_t half_time = (expected_timeout == UINT64_MAX) ? 20000000 : (expected_timeout / 2); + uint64_t full_time = (expected_timeout == UINT64_MAX) ? 600000000 : (half_time + 100000); + int ret = 0; + + initial_cid.id[4] = test_id; + + /* Create the test context */ + if (ret == 0) { + ret = tls_api_init_ctx_ex(&test_ctx, PICOQUIC_INTERNAL_TEST_VERSION_1, + PICOQUIC_TEST_SNI, PICOQUIC_TEST_ALPN, &simulated_time, NULL, NULL, 0, 1, 0, &initial_cid); + } + /* Set the binlog */ + if (ret == 0) { + picoquic_set_binlog(test_ctx->qclient, "."); + picoquic_set_binlog(test_ctx->qserver, "."); + /* Set the timeout */ + picoquic_set_default_idle_timeout(test_ctx->qclient, client_timeout); + picoquic_set_default_idle_timeout(test_ctx->qserver, server_timeout); + /* Directly set the timeout in the client parameters, + because the connection context is already created */ + test_ctx->cnx_client->local_parameters.max_idle_timeout = client_timeout; + } + + /* Do the connection */ + if (ret == 0) { + test_ctx->cnx_client->max_early_data_size = 0; + + if ((ret = picoquic_start_client_cnx(test_ctx->cnx_client)) == 0) { + ret = tls_api_connection_loop(test_ctx, &loss_mask, 0, &simulated_time); + } + } + + /* Verify the timer negotiation */ + if (ret == 0) { + if (test_ctx->cnx_client->local_parameters.max_idle_timeout != client_timeout) { + DBG_PRINTF("Idle timeout test %d. Client parameter set to %" PRIu64 " instead of %" PRIu64 "\n", + test_id, test_ctx->cnx_client->local_parameters.max_idle_timeout, client_timeout); + ret = -1; + } + if (test_ctx->cnx_server->local_parameters.max_idle_timeout != server_timeout) { + DBG_PRINTF("Idle timeout test %d. Server parameter set to %" PRIu64 " instead of %" PRIu64 "\n", + test_id, test_ctx->cnx_server->local_parameters.max_idle_timeout, server_timeout); + ret = -1; + } + if (test_ctx->cnx_client->idle_timeout != expected_timeout) { + DBG_PRINTF("Idle timeout test %d. Client negotiated %" PRIu64 " instead of %" PRIu64 "\n", + test_id, test_ctx->cnx_client->idle_timeout, expected_timeout); + ret = -1; + } + if (test_ctx->cnx_server->idle_timeout != expected_timeout) { + DBG_PRINTF("Idle timeout test %d. Server negotiated %" PRIu64 " instead of %" PRIu64 "\n", + test_id, test_ctx->cnx_server->idle_timeout, expected_timeout); + ret = -1; + } + } + + if (ret == 0) { + /* Wait for half time. Expectation: connections are still up */ + ret = tls_api_wait_for_timeout(test_ctx, &simulated_time, half_time); + if (ret != 0 || !((TEST_CLIENT_READY && TEST_SERVER_READY))) { + DBG_PRINTF("Idle timeout test %d. Broke early, time = %" PRIu64 "\n", test_id, simulated_time); + ret = -1; + } + } + + if (ret == 0) { + /* Wait for full time. Expectation: connections are down, unless timeout == 0 */ + ret = tls_api_wait_for_timeout(test_ctx, &simulated_time, full_time); + + if (ret == 0){ + if (TEST_CLIENT_READY && TEST_SERVER_READY) { + if (expected_timeout != UINT64_MAX) { + DBG_PRINTF("Idle timeout test %d. Waited too long, time = %" PRIu64 "\n", test_id, simulated_time); + ret = -1; + } + } + else { + if (expected_timeout == UINT64_MAX) { + DBG_PRINTF("Idle timeout test %d. Broke early, time = %" PRIu64 "\n", test_id, simulated_time); + ret = -1; + } + } + } + } + + if (test_ctx != NULL) { + tls_api_delete_ctx(test_ctx); + test_ctx = NULL; + } + + return ret; +} + +int idle_timeout_test() +{ + int ret = 0; + + if ((ret = idle_timeout_test_one(1, 30000, 30000, 30000000)) == 0 && + (ret = idle_timeout_test_one(2, 60000, 20000, 20000000)) == 0 && + (ret = idle_timeout_test_one(3, 20000, 60000, 20000000)) == 0 && + (ret = idle_timeout_test_one(4, 5000, 300000, 5000000)) == 0 && + (ret = idle_timeout_test_one(5, 300000, 5000, 5000000)) == 0 && + (ret = idle_timeout_test_one(6, 0, 5000, 5000000)) == 0 && + (ret = idle_timeout_test_one(7, 0, 60000, 60000000)) == 0 && + (ret = idle_timeout_test_one(8, 5000, 0, 5000000)) == 0 && + (ret = idle_timeout_test_one(9, 60000, 0, 60000000)) == 0 && + (ret = idle_timeout_test_one(10, 0, 0, UINT64_MAX)) == 0) { + DBG_PRINTF("%s", "All idle timeout tests pass.\n"); + } + return ret; +} + +/* Testing that connection attempt against a non responding server + * finishes after the timeout value. + */ + +int idle_server_test_one(uint8_t test_id, uint64_t client_timeout, uint64_t handshake_timeout, uint64_t expected_timeout) +{ + picoquic_test_tls_api_ctx_t* test_ctx = NULL; + uint64_t simulated_time = 0; + uint64_t target_timeout; + uint64_t loss_mask = 0; + picoquic_connection_id_t initial_cid = { { 0x41, 0x9e, 0xc0, 0x99, 0, 0, 0, 0}, 8 }; + uint8_t send_buffer[PICOQUIC_MAX_PACKET_SIZE]; + int ret = 0; + + initial_cid.id[4] = test_id; + + /* derive target timeout form spec */ + target_timeout = handshake_timeout; + if (handshake_timeout == 0) { + target_timeout = client_timeout * 1000; + if (client_timeout == 0) { + target_timeout = PICOQUIC_MICROSEC_HANDSHAKE_MAX; + } + } + + /* Create the test context */ + if (ret == 0) { + ret = tls_api_init_ctx_ex(&test_ctx, PICOQUIC_INTERNAL_TEST_VERSION_1, + PICOQUIC_TEST_SNI, PICOQUIC_TEST_ALPN, &simulated_time, NULL, NULL, 0, 1, 0, &initial_cid); + } + + /* Set the binlog */ + if (ret == 0) { + picoquic_set_binlog(test_ctx->qclient, "."); + /* Set the timeout */ + picoquic_set_default_idle_timeout(test_ctx->qclient, client_timeout); + if (handshake_timeout > 0) { + picoquic_set_default_handshake_timeout(test_ctx->qclient, handshake_timeout); + } + /* Directly set the timeout in the client parameters, + because the connection context is already created */ + test_ctx->cnx_client->local_parameters.max_idle_timeout = client_timeout; + /* Start the client */ + ret = picoquic_start_client_cnx(test_ctx->cnx_client); + } + + /* Run a simulation loop -- the server never responds. */ + if (ret == 0) { + int nb_trials = 0; + while (ret == 0 && simulated_time < expected_timeout) { + size_t send_length = 0; + struct sockaddr_storage addr_to; + struct sockaddr_storage addr_from; + + ret = picoquic_prepare_packet_ex(test_ctx->cnx_client, simulated_time, + send_buffer, sizeof(send_buffer), &send_length, + &addr_to, &addr_from, 0, NULL); + if (ret != 0) { + break; + } + else if (test_ctx->cnx_client->cnx_state == picoquic_state_disconnected) { + break; + } + else if (simulated_time > test_ctx->cnx_client->next_wake_time) { + DBG_PRINTF("Idle server test %d. Bug, simulation is walking back in time.", test_id); + ret = -1; + } + else if (nb_trials >= 512) { + DBG_PRINTF("Idle server test %d. Bug, simulation exceeds %d steps.", test_id, nb_trials); + ret = -1; + } + else { + nb_trials++; + simulated_time = test_ctx->cnx_client->next_wake_time; + } + } + } + + if ((ret == 0 && test_ctx->cnx_client->cnx_state == picoquic_state_disconnected) || + ret == PICOQUIC_ERROR_DISCONNECTED) { + if (simulated_time < target_timeout) { + DBG_PRINTF("Idle server test %d. Client gave up too soon, time = %" PRIu64 "\n", test_id, simulated_time); + ret = -1; + } + else { + ret = 0; + } + } + else if (ret == 0) { + DBG_PRINTF("Idle server test %d. Client did not disconnect, time = %" PRIu64 "\n", test_id, simulated_time); + ret = -1; + } + else { + DBG_PRINTF("Idle server test %d. ret=0x%x, time = %" PRIu64 "\n", ret, test_id, simulated_time); + } + + if (test_ctx != NULL) { + tls_api_delete_ctx(test_ctx); + test_ctx = NULL; + } + + return ret; +} + +int idle_server_test() +{ + int ret = 0; + + if ((ret = idle_server_test_one(1, 30000, 0, 30100000)) == 0 && + (ret = idle_server_test_one(2, 60000, 0, 60100000)) == 0 && + (ret = idle_server_test_one(3, 5000, 0, 5100000)) == 0 && + (ret = idle_server_test_one(4, 0, 0, 30100000)) == 0 && + (ret = idle_server_test_one(5, 0, 10000, 10100000)) == 0 && + (ret = idle_server_test_one(6, 20000, 60000, 60100000)) == 0 && + (ret = idle_server_test_one(7, 60000, 5000, 5100000)) == 0){ + DBG_PRINTF("%s", "All idle timeout tests pass.\n"); + } return ret; } \ No newline at end of file diff --git a/picoquictest/h3zerotest.c b/picoquictest/h3zerotest.c index cc26d92fd..3b4f817cf 100644 --- a/picoquictest/h3zerotest.c +++ b/picoquictest/h3zerotest.c @@ -2805,6 +2805,7 @@ int http_stress_test_one(int do_corrupt, int do_drop, int initial_random) uint64_t server_time = 0; uint64_t random_context = picohttp_random_stress_context; size_t nb_stress_clients = picohttp_nb_stress_clients; + int nb_loops = 0; ret = picoquic_store_text_addr(&server_address, "1::1", 443); @@ -2874,6 +2875,13 @@ int http_stress_test_one(int do_corrupt, int do_drop, int initial_random) picoquic_quic_t* qready = NULL; struct sockaddr* ready_from = NULL; + nb_loops++; + if (nb_loops > 10000000) { + DBG_PRINTF("Loop detected after %d iterations", nb_loops); + ret = -1; + break; + } + if (is_lan_ready) { next_time = picoquictest_sim_link_next_arrival(lan, next_time); } diff --git a/picoquictest/mediatest.c b/picoquictest/mediatest.c index e80a26c98..ae1b48d94 100644 --- a/picoquictest/mediatest.c +++ b/picoquictest/mediatest.c @@ -934,7 +934,7 @@ void mediatest_init_transport_parameters(picoquic_tp_t* tp, int client_mode) tp->initial_max_data = 0x100000; tp->initial_max_stream_id_bidir = 512; tp->initial_max_stream_id_unidir = 512; - tp->idle_timeout = 30000; + tp->max_idle_timeout = 30000; tp->max_packet_size = PICOQUIC_MAX_PACKET_SIZE; tp->ack_delay_exponent = 3; tp->active_connection_id_limit = 4; diff --git a/picoquictest/picoquictest.h b/picoquictest/picoquictest.h index bfec46446..8a03ce731 100644 --- a/picoquictest/picoquictest.h +++ b/picoquictest/picoquictest.h @@ -222,6 +222,8 @@ int ecf1_final_loss_test(); int ec5c_silly_cid_test(); int ec9a_preemptive_amok_test(); int error_reason_test(); +int idle_server_test(); +int idle_timeout_test(); int ready_to_send_test(); int ready_to_skip_test(); int ready_to_zero_test(); diff --git a/picoquictest/tls_api_test.c b/picoquictest/tls_api_test.c index 07481ac30..0e8f692f7 100644 --- a/picoquictest/tls_api_test.c +++ b/picoquictest/tls_api_test.c @@ -909,9 +909,9 @@ static int verify_transport_extension(picoquic_cnx_t* cnx_client, picoquic_cnx_t int ret = 0; /* verify that local parameters have a sensible value */ - if (cnx_client->local_parameters.idle_timeout == 0 || cnx_client->local_parameters.initial_max_data == 0 || cnx_client->local_parameters.initial_max_stream_data_bidi_local == 0 || cnx_client->local_parameters.max_packet_size == 0) { + if (cnx_client->local_parameters.max_idle_timeout == 0 || cnx_client->local_parameters.initial_max_data == 0 || cnx_client->local_parameters.initial_max_stream_data_bidi_local == 0 || cnx_client->local_parameters.max_packet_size == 0) { ret = -1; - } else if (cnx_server->local_parameters.idle_timeout == 0 || cnx_server->local_parameters.initial_max_data == 0 || cnx_server->local_parameters.initial_max_stream_data_bidi_remote == 0 || cnx_server->local_parameters.max_packet_size == 0) { + } else if (cnx_server->local_parameters.max_idle_timeout == 0 || cnx_server->local_parameters.initial_max_data == 0 || cnx_server->local_parameters.initial_max_stream_data_bidi_remote == 0 || cnx_server->local_parameters.max_packet_size == 0) { ret = -1; } /* Verify that the negotiation completed */ diff --git a/picoquictest/transport_param_test.c b/picoquictest/transport_param_test.c index 7ec4d3b3d..5cf70c4d3 100644 --- a/picoquictest/transport_param_test.c +++ b/picoquictest/transport_param_test.c @@ -427,9 +427,9 @@ static int transport_param_compare(picoquic_tp_t* param, picoquic_tp_t* ref) { param->initial_max_stream_id_unidir, ref->initial_max_stream_id_unidir); ret = -1; } - else if (param->idle_timeout != ref->idle_timeout) { + else if (param->max_idle_timeout != ref->max_idle_timeout) { DBG_PRINTF("idle_timeout: got %d, expected %d\n", - param->idle_timeout, ref->idle_timeout); + param->max_idle_timeout, ref->max_idle_timeout); ret = -1; } else if (param->prefered_address.is_defined != ref->prefered_address.is_defined) { diff --git a/picoquictest/warptest.c b/picoquictest/warptest.c index 0a791147b..5499fe5d6 100644 --- a/picoquictest/warptest.c +++ b/picoquictest/warptest.c @@ -1266,7 +1266,7 @@ void warptest_init_transport_parameters(picoquic_tp_t* tp, int client_mode, warp else { tp->initial_max_stream_id_unidir = spec->max_streams_client; } - tp->idle_timeout = 30000; + tp->max_idle_timeout = 30000; tp->max_packet_size = PICOQUIC_MAX_PACKET_SIZE; tp->ack_delay_exponent = 3; tp->active_connection_id_limit = 4;