diff --git a/UnitTest1/unittest1.cpp b/UnitTest1/unittest1.cpp index 8f7ccb4d03c5b95ae5313df8573c0b6e9a2c0642..7683df877effa851f1f2f9df9fe143e82b58e498 100644 --- a/UnitTest1/unittest1.cpp +++ b/UnitTest1/unittest1.cpp @@ -619,6 +619,14 @@ namespace UnitTest1 Assert::AreEqual(ret, 0); } + TEST_METHOD(test_transmit_cnxid) + { + int ret = transmit_cnxid_test(); + + Assert::AreEqual(ret, 0); + } + + TEST_METHOD(stress) { int ret = stress_test(); diff --git a/picoquic/picoquic_internal.h b/picoquic/picoquic_internal.h index 845cbd22d1bee7f1d38586e494d27cf4f4f88b6e..85d0f05f6ef672b8c57cf3c2304fb6601ad2a5ae 100644 --- a/picoquic/picoquic_internal.h +++ b/picoquic/picoquic_internal.h @@ -39,6 +39,7 @@ extern "C" { #define PICOQUIC_PRACTICAL_MAX_MTU 1440 #define PICOQUIC_RETRY_SECRET_SIZE 64 #define PICOQUIC_DEFAULT_0RTT_WINDOW 4096 +#define PICOQUIC_NB_PATH_TARGET 4 #define PICOQUIC_NUMBER_OF_EPOCHS 4 #define PICOQUIC_NUMBER_OF_EPOCH_OFFSETS (PICOQUIC_NUMBER_OF_EPOCHS+1) diff --git a/picoquic/sender.c b/picoquic/sender.c index 241a1299d5684be89f0192700d5f405f6d2380ed..d60a6341c7a7effe380c4de890679f4dad4cafde 100644 --- a/picoquic/sender.c +++ b/picoquic/sender.c @@ -1187,6 +1187,13 @@ void picoquic_cnx_set_next_wake_time(picoquic_cnx_t* cnx, uint64_t current_time) else if (path_x->cwin > path_x->bytes_in_transit && picoquic_is_mtu_probe_needed(cnx, path_x)) { blocked = 0; } + else if ((cnx->cnx_state == picoquic_state_client_ready || + cnx->cnx_state == picoquic_state_server_ready) && + cnx->remote_parameters.migration_disabled == 0 && + cnx->local_parameters.migration_disabled == 0 && + cnx->nb_paths < PICOQUIC_NB_PATH_TARGET) { + blocked = 0; + } else { for (picoquic_packet_context_enum pc = 0; pc < picoquic_nb_packet_context; pc++) { picoquic_packet_t* p = cnx->pkt_ctx[pc].retransmit_oldest; @@ -2010,6 +2017,28 @@ int picoquic_prepare_packet_closing(picoquic_cnx_t* cnx, picoquic_path_t * path_ return ret; } +/* Create a new path, register it, and file the corresponding connection ID frame */ +int picoquic_prepare_new_path_and_id(picoquic_cnx_t* cnx, uint8_t* bytes, size_t bytes_max, int64_t current_time, size_t* consumed) +{ + int ret = 0; + int path_index; + + path_index = picoquic_create_path(cnx, current_time, NULL, NULL); + + picoquic_register_path(cnx, cnx->path[path_index]); + + ret = picoquic_prepare_connection_id_frame(cnx, cnx->path[path_index], bytes, bytes_max, consumed); + + if (ret == PICOQUIC_ERROR_FRAME_BUFFER_TOO_SMALL) { + /* Oops. Try again next time. */ + picoquic_delete_path(cnx, path_index); + *consumed = 0; + } + + return ret; +} + + /* Prepare the next packet to send when in one the ready states */ int picoquic_prepare_packet_ready(picoquic_cnx_t* cnx, picoquic_path_t * path_x, picoquic_packet_t* packet, uint64_t current_time, uint8_t* send_buffer, size_t send_buffer_max, size_t* send_length) @@ -2147,6 +2176,25 @@ int picoquic_prepare_packet_ready(picoquic_cnx_t* cnx, picoquic_path_t * path_x, break; } } + + /* If there are not enough paths, create and advertise */ + while (ret == 0 && cnx->remote_parameters.migration_disabled == 0 && + cnx->local_parameters.migration_disabled == 0 && + cnx->nb_paths < PICOQUIC_NB_PATH_TARGET) { + ret = picoquic_prepare_new_path_and_id(cnx, &bytes[length], + send_buffer_min_max - checksum_overhead - length, + current_time, &data_bytes); + if (ret == 0) { + length += (uint32_t)data_bytes; + } + else { + if (ret == PICOQUIC_ERROR_FRAME_BUFFER_TOO_SMALL) { + ret = 0; + } + break; + } + } + /* If necessary, encode the max data frame */ if (ret == 0 && 2 * cnx->data_received > cnx->maxdata_local) { ret = picoquic_prepare_max_data_frame(cnx, 2 * cnx->data_received, &bytes[length], diff --git a/picoquic_t/picoquic_t.c b/picoquic_t/picoquic_t.c index fe0343b46ffeff9446aa6f4fa2c69730cd83e898..e2e3cf66437662d5e73e23e54f39366dd63836dd 100644 --- a/picoquic_t/picoquic_t.c +++ b/picoquic_t/picoquic_t.c @@ -130,7 +130,8 @@ static const picoquic_test_def_t test_table[] = { { "zero_rtt_retry", zero_rtt_retry_test }, { "random_tester", random_tester_test}, { "stress", stress_test }, - { "fuzz", fuzz_test } + { "fuzz", fuzz_test }, + { "transmit_cnxid", transmit_cnxid_test } }; static size_t const nb_tests = sizeof(test_table) / sizeof(picoquic_test_def_t); diff --git a/picoquictest/picoquictest.h b/picoquictest/picoquictest.h index 6326f19a6adf96d4f408c607a6b5547571c76d3a..35ed2cc675a386d6f45a1a702cc766f581b0c457 100644 --- a/picoquictest/picoquictest.h +++ b/picoquictest/picoquictest.h @@ -123,6 +123,7 @@ int fuzz_test(); int random_tester_test(); int cnxid_stash_test(); int new_cnxid_test(); +int transmit_cnxid_test(); #ifdef __cplusplus } diff --git a/picoquictest/tls_api_test.c b/picoquictest/tls_api_test.c index 30b3d3e3d2d9a45141b2ca297480afa0a9f96510..d21e97df0de063e6281ef4047763fea8e0d07ef9 100644 --- a/picoquictest/tls_api_test.c +++ b/picoquictest/tls_api_test.c @@ -1259,7 +1259,7 @@ int tls_api_one_scenario_test(test_api_stream_desc_t* scenario, int tls_api_oneway_stream_test() { - return tls_api_one_scenario_test(test_scenario_oneway, sizeof(test_scenario_oneway), 0, 0, 0, 0, 65000, NULL); + return tls_api_one_scenario_test(test_scenario_oneway, sizeof(test_scenario_oneway), 0, 0, 0, 0, 70000, NULL); } int tls_api_q_and_r_stream_test() @@ -1269,7 +1269,7 @@ int tls_api_q_and_r_stream_test() int tls_api_q2_and_r2_stream_test() { - return tls_api_one_scenario_test(test_scenario_q2_and_r2, sizeof(test_scenario_q2_and_r2), 0, 0, 0, 0, 75000, NULL); + return tls_api_one_scenario_test(test_scenario_q2_and_r2, sizeof(test_scenario_q2_and_r2), 0, 0, 0, 0, 80000, NULL); } int tls_api_very_long_stream_test() @@ -3453,5 +3453,110 @@ int client_error_test() test_ctx = NULL; } + return ret; +} + +/* + * Set a connection, then verify that the "new connection id" frames have been exchanged properly. + * Use the "check stash" function to verify that new connection ID were properly + * stashed on each side. + * + * TODO: also test that no New Connection Id frames are sent if migration is disabled + */ + +int transmit_cnxid_test_stash(picoquic_cnx_t * cnx1, picoquic_cnx_t * cnx2, char const * cnx_text) +{ + int ret = 0; + picoquic_cnxid_stash_t * stash = cnx1->cnxid_stash_first; + int path_id = 1; + + while (stash != NULL && path_id < cnx2->nb_paths) { + if (picoquic_compare_connection_id(&stash->cnx_id, &cnx2->path[path_id]->local_cnxid) != 0) { + DBG_PRINTF("On %s, cnx ID of stash #%d does not match path[%d] of peer.\n", + cnx_text, path_id - 1, path_id); + ret = -1; + break; + } + stash = stash->next_in_stash; + path_id++; + } + + if (ret == 0 && path_id < cnx2->nb_paths) { + DBG_PRINTF("On %s, %d items in stash instead instead of %d.\n", cnx_text, path_id - 1, cnx2->nb_paths); + ret = -1; + } + + if (ret == 0 && stash != NULL) { + DBG_PRINTF("On %s, more than %d items in stash.\n", cnx_text, path_id - 1); + ret = -1; + } + + return ret; + +} + +int transmit_cnxid_test() +{ + uint64_t simulated_time = 0; + uint64_t next_time = 0; + uint64_t loss_mask = 0; + picoquic_test_tls_api_ctx_t* test_ctx = NULL; + int ret = tls_api_init_ctx(&test_ctx, PICOQUIC_INTERNAL_TEST_VERSION_1, + PICOQUIC_TEST_SNI, PICOQUIC_TEST_ALPN, &simulated_time, NULL, 0, 0, 0); + + if (ret == 0) { + ret = tls_api_connection_loop(test_ctx, &loss_mask, 0, &simulated_time); + } + + /* run a receive loop until no outstanding data */ + if (ret == 0) { + uint64_t time_out = simulated_time + 4000000; + int nb_rounds = 0; + int success = 0; + + while (ret == 0 && simulated_time < time_out && + nb_rounds < 2048 && test_ctx->cnx_client->cnx_state != picoquic_state_disconnected) { + int was_active = 0; + + ret = tls_api_one_sim_round(test_ctx, &simulated_time, &was_active); + nb_rounds++; + + if (test_ctx->cnx_client->nb_paths >= PICOQUIC_NB_PATH_TARGET && + test_ctx->cnx_server->nb_paths >= PICOQUIC_NB_PATH_TARGET && + picoquic_is_cnx_backlog_empty(test_ctx->cnx_client) && + picoquic_is_cnx_backlog_empty(test_ctx->cnx_server)) { + success = 1; + break; + } + } + + if (ret == 0 && success == 0) { + DBG_PRINTF("Exit synch loop after %d rounds, backlog or not enough paths (%d & %d).\n", + nb_rounds, test_ctx->cnx_client->nb_paths, test_ctx->cnx_server->nb_paths); + } + } + + if (ret == 0) { + if (test_ctx->cnx_client->nb_paths < PICOQUIC_NB_PATH_TARGET) { + DBG_PRINTF("Only %d paths created on client.\n", test_ctx->cnx_client->nb_paths); + ret = -1; + } else if (test_ctx->cnx_server->nb_paths < PICOQUIC_NB_PATH_TARGET) { + DBG_PRINTF("Only %d paths created on server.\n", test_ctx->cnx_server->nb_paths); + } + } + + if (ret == 0) { + ret = transmit_cnxid_test_stash(test_ctx->cnx_client, test_ctx->cnx_server, "client"); + } + + if (ret == 0) { + ret = transmit_cnxid_test_stash(test_ctx->cnx_server, test_ctx->cnx_client, "server"); + } + + if (test_ctx != NULL) { + tls_api_delete_ctx(test_ctx); + test_ctx = NULL; + } + return ret; } \ No newline at end of file