diff --git a/CMakeLists.txt b/CMakeLists.txt index aa3471012..1e6a31125 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ else() endif() project(picoquic - VERSION 1.1.25.1 + VERSION 1.1.25.2 DESCRIPTION "picoquic library" LANGUAGES C CXX) @@ -132,10 +132,13 @@ set(LOGLIB_LIBRARY_FILES loglib/csv.c loglib/logconvert.c loglib/logreader.c + loglib/memory_log.c loglib/qlog.c loglib/svg.c) -set(PICOQUIC_LOGLIB_HEADERS loglib/autoqlog.h) +set(PICOQUIC_LOGLIB_HEADERS + loglib/autoqlog.h + loglib/auto_memlog.h) set(PICOQUIC_TEST_LIBRARY_FILES picoquictest/ack_of_ack_test.c diff --git a/baton_app/baton_app.c b/baton_app/baton_app.c index 4b3b3da04..2c8281772 100644 --- a/baton_app/baton_app.c +++ b/baton_app/baton_app.c @@ -34,23 +34,31 @@ #include #include #include +#include +#include #include "wt_baton.h" #include "pico_webtransport.h" -int wt_baton_client(char const* server_name, int server_port, char const* path); + +#ifdef _WINDOWS +#include +#endif + +int wt_baton_client(char const* server_name, int server_port, char const* path, picoquic_quic_config_t * config); int baton_client_loop_cb(picoquic_quic_t* quic, picoquic_packet_loop_cb_enum cb_mode, void* callback_ctx, void* callback_arg); static void usage(char const * sample_name) { fprintf(stderr, "Usage:\n"); - fprintf(stderr, " %s server_name port path\n", sample_name); + fprintf(stderr, " %s [options] server_name port path\n", sample_name); fprintf(stderr, "The path argument may include parameters:\n"); fprintf(stderr, " - version: baton protocol version,\n"); fprintf(stderr, " - baton: initial version value,\n"); fprintf(stderr, " - count: number of rounds,\n"); fprintf(stderr, " - inject: inject error for testing\n"); fprintf(stderr, "For example, set a path like /baton?count=17 to have 17 rounds of baton exchange."); + picoquic_config_usage(); exit(1); } @@ -68,20 +76,35 @@ int get_port(char const* sample_name, char const* port_arg) int main(int argc, char** argv) { int ret = 0; + picoquic_quic_config_t config; + char option_string[512]; #ifdef _WINDOWS WSADATA wsaData = { 0 }; (void)WSA_START(MAKEWORD(2, 2), &wsaData); #endif - if (argc != 4) { + picoquic_config_init(&config); + ret = picoquic_config_option_letters(option_string, sizeof(option_string), NULL); + if (ret == 0) { + int opt; + while ((opt = getopt(argc, argv, option_string)) != -1) { + if (picoquic_config_command_line(opt, &optind, argc, (char const**)argv, optarg, &config) != 0) { + usage(argv[0]); + ret = -1; + break; + } + } + } + + if (optind + 3 != argc){ usage(argv[0]); } else { - char const* server_name = argv[1]; - int server_port = get_port(argv[0], argv[2]); - char const * path = argv[3]; + char const* server_name = argv[optind++]; + int server_port = get_port(argv[0], argv[optind++]); + char const * path = argv[optind]; - ret = wt_baton_client(server_name, server_port, path); + ret = wt_baton_client(server_name, server_port, path, &config); if (ret != 0) { fprintf(stderr, "Baton dropped, ret=%d\n", ret); @@ -91,8 +114,6 @@ int main(int argc, char** argv) } /* Client: -* - Create the QUIC context. -* - Open the sockets * - Find the server's address * - Create a client context and a client connection. * - On a forever loop: @@ -108,22 +129,20 @@ int main(int argc, char** argv) #define PICOQUIC_BATON_CLIENT_TOKEN_STORE "baton_token_store.bin"; #define PICOQUIC_BATON_CLIENT_QLOG_DIR "."; -int wt_baton_client(char const * server_name, int server_port, char const * path) +int wt_baton_client(char const* server_name, int server_port, char const* path, picoquic_quic_config_t* config) { int ret = 0; struct sockaddr_storage server_address; char const* sni = "test"; picoquic_quic_t* quic = NULL; - char const* ticket_store_filename = PICOQUIC_BATON_CLIENT_TICKET_STORE; - char const* token_store_filename = PICOQUIC_BATON_CLIENT_TOKEN_STORE; - char const* qlog_dir = PICOQUIC_BATON_CLIENT_QLOG_DIR; picoquic_cnx_t* cnx = NULL; uint64_t current_time = picoquic_current_time(); wt_baton_ctx_t baton_ctx = { 0 }; h3zero_callback_ctx_t* h3_ctx = NULL; + h3zero_stream_ctx_t* control_stream_ctx = NULL; - /* Get the server's address */ if (ret == 0) { + /* Get the server's address */ int is_name = 0; ret = picoquic_get_server_address(server_name, server_port, &server_address, &is_name); @@ -135,107 +154,128 @@ int wt_baton_client(char const * server_name, int server_port, char const * path } } - /* Create a QUIC context - */ if (ret == 0) { - quic = picoquic_create( - 8, - NULL, /* Cert */ - NULL, /* Key file */ - NULL, /* trust file */ - "h3", - NULL, /* default_callback_fn */ - NULL, /* default_callback_ctx */ - NULL, - NULL, - NULL, /* Reset seed is only for servers */ - current_time, - NULL, /* Not using simulated time */ - ticket_store_filename, - NULL, /* Only server need the ticket_encryption_key */ - 0 /* Only server need the ticket_encryption_key length */ ); - + /* Prepare the QUIC context in which the web transport connection + * will be created. This will set the required transport parameters, + * and apply the option specified in the command line. + */ + quic = picoquic_create_and_configure(config, NULL, NULL, current_time, NULL); if (quic == NULL) { - fprintf(stderr, "Could not create quic context\n"); + fprintf(stderr, "Cannot create the Quic context\n"); ret = -1; } - else { - if (picoquic_load_retry_tokens(quic, token_store_filename) != 0) { - fprintf(stderr, "No token file present. Will create one as <%s>.\n", token_store_filename); - } + } - picoquic_set_default_congestion_algorithm(quic, picoquic_bbr_algorithm); + if (ret == 0) { + /* The default QUIC context creation does not initiate logging, because + * logging is an optional component. Some applications do not use logging + * at all, in an effort to reduce the code size. In our case, we enable + * the components by including the autoqlog.h and performance_log.h headers. + * We also enable the export of keys in a key log file, to enable QUIC + * parsing in wireshark + */ + picoquic_set_key_log_file_from_env(quic); + + if (config->qlog_dir != NULL) + { + picoquic_set_qlog(quic, config->qlog_dir); + } + + if (config->performance_log != NULL) + { + int ret = picoquic_perflog_setup(quic, config->performance_log); - picoquic_set_key_log_file_from_env(quic); - picoquic_set_qlog(quic, qlog_dir); - picoquic_set_log_level(quic, 1); + if (ret != 0) { + DBG_PRINTF("Cannot open performance log: %s, ret = 0x%x", config->performance_log, ret); + picoquic_free(quic); + quic = NULL; + } } } - /* Initialize the callback context and create the connection context. - * We use minimal options on the client side, keeping the transport - * parameter values set by default for picoquic. This could be fixed later. - */ + if (ret == 0) { + /* Prepare a QUIC connection and allocate the parameters required for + * the web transport setup: + * - cnx is the "raw" QUIc connection. + * - h3_ctx holds the parameter required for managing the HTTP3 protocol. + * - control_stream_ctx holds the parameter for the "control stream" of + * the web transport connection. + */ + ret = picowt_prepare_client_cnx(quic, (struct sockaddr*)&server_address, + &cnx, &h3_ctx, &control_stream_ctx, current_time, sni); + } if (ret == 0) { - /* use the generic H3 callback */ - /* Set the client callback context */ - h3_ctx = h3zero_callback_create_context(NULL); - if (h3_ctx == NULL) { - ret = 1; - } + /* At this stage, we have allocated the QUIC connection, the + * HTTP3 context, and the control stream. This, and the parameters + * encoded in the path, is enough to build the context of the + * application. + * The example here builds a baton application context. Other + * applications will replace that by their own values. + */ + ret = wt_baton_prepare_context(cnx, &baton_ctx, h3_ctx, control_stream_ctx, + sni, path); } if (ret == 0) { - ret = 0; + /* Once the application context has been initialized, we pass it to the + * "connect" request, with a pointer to the application specific callback. + * Of course, other application would follow the same logic and implement their + * own callback. + */ + ret = picowt_connect(cnx, h3_ctx, control_stream_ctx, baton_ctx.authority, baton_ctx.server_path, + wt_baton_callback, &baton_ctx); + + if (ret != 0) { + fprintf(stderr, "Could not program the web transport connection\n"); + } } + if (ret == 0) { - printf("Starting connection to %s, port %d\n", server_name, server_port); + /* + * Until the call to `picoquic_start_client_cnx`, the Quic connection + * is "intert". The previous calls to `wt_prepare_client_cnx` have + * set the context and prepared a variety of stream data, but we need to + * pull the trigger and start the client so the connection actions are properly + * executed inside `picoquic_packet_loop`. + */ + ret = picoquic_start_client_cnx(cnx); - /* Create a client connection */ - cnx = picoquic_create_cnx(quic, picoquic_null_connection_id, picoquic_null_connection_id, - (struct sockaddr*) & server_address, current_time, 0, sni, "h3", 1); - if (cnx == NULL) { - fprintf(stderr, "Could not create connection context\n"); - ret = -1; + if (ret != 0) { + fprintf(stderr, "Could not start the connection\n"); } - else { - picowt_set_transport_parameters(cnx); - picoquic_set_callback(cnx, h3zero_callback, h3_ctx); - /* Initialize the callback context. First, create a bidir stream */ - wt_baton_ctx_init(&baton_ctx, h3_ctx, NULL, NULL); - baton_ctx.cnx = cnx; - baton_ctx.is_client = 1; - baton_ctx.authority = server_name; - baton_ctx.server_path = path; - - /* Create a stream context for the connect call. */ - ret = wt_baton_connect(cnx, &baton_ctx, h3_ctx); - - /* Client connection parameters could be set here, before starting the connection. */ - if (ret == 0) { - ret = picoquic_start_client_cnx(cnx); - } - if (ret < 0) { - fprintf(stderr, "Could not activate connection\n"); - } else { - /* Printing out the initial CID, which is used to identify log files */ - picoquic_connection_id_t icid = picoquic_get_initial_cnxid(cnx); - printf("Initial connection ID: "); - for (uint8_t i = 0; i < icid.id_len; i++) { - printf("%02x", icid.id[i]); - } - printf("\n"); - /* Perform the initialization, settings and QPACK streams - */ - ret = h3zero_protocol_init(cnx); - } + } + + if (ret == 0) { + /* Not strictly necessary, but helpful: the log files will be + * identified by the initial CID. Printing that value now will + * allow us to identify these log files in the logging + * directory. + */ + picoquic_connection_id_t icid = picoquic_get_initial_cnxid(cnx); + printf("Initial connection ID: "); + for (uint8_t i = 0; i < icid.id_len; i++) { + printf("%02x", icid.id[i]); } + printf("\n"); } - /* Wait for packets */ - ret = picoquic_packet_loop(quic, 0, server_address.ss_family, 0, 0, 0, baton_client_loop_cb, &baton_ctx); + if (ret == 0) { + /* Time to start the "packet loop", which will manage the UDP sockets + * and send and receive messages. The application will be called + * back when messages arrrive, etc., through the application + * callback, "wt_baton_callback" in our example. The application + * may receive socket level events through another callback, + * `baton_client_loop_cb` in our examples. We mainly use that + * to exit the packet loop when the application is done. + */ + ret = picoquic_packet_loop(quic, 0, server_address.ss_family, 0, 0, 0, baton_client_loop_cb, &baton_ctx); + } - /* Done. At this stage, we print out statistics, etc. */ + /* Done. At this stage, we print out statistics, etc. + * In the example, these are statistics specific to the + * "baton" application. Other applications will replace this + * code and use their own logic. + */ printf("Final baton state: %d\n", baton_ctx.baton_state); printf("Nb turns: %d\n", baton_ctx.nb_turns); /* print statistics per lane */ @@ -260,28 +300,53 @@ int wt_baton_client(char const * server_name, int server_port, char const * path baton_ctx.capsule.error_msg_len)); } - if (h3_ctx != NULL) - { - h3zero_callback_delete_context(cnx, h3_ctx); - } - /* Save tickets and tokens, and free the QUIC context */ + /* Save the session resume tickets and the address verification tokens into + * their respective files. Tickets and tokens will be read into the QUIC + * context when the program runs again, provided of course that successive + * runs use the same tickets and tokens file names. Tickets and tokens are + * server specific, and for tokens IP address specific. + * + * If a ticket is available, the next connection will try to use `session + * resume' and `0 RTT`. If a token is available, the IP address of the + * client will be immediately validate, without requiring any + * validation roundtrip. + */ if (quic != NULL) { - if (picoquic_save_session_tickets(quic, ticket_store_filename) != 0) { - fprintf(stderr, "Could not store the saved session tickets.\n"); + if (config->ticket_file_name != NULL && + picoquic_save_session_tickets(quic, config->ticket_file_name) != 0) { + fprintf(stderr, "Could not save session tickets to <%s>.\n", config->ticket_file_name); } - if (picoquic_save_retry_tokens(quic, token_store_filename) != 0) { - fprintf(stderr, "Could not save tokens to <%s>.\n", token_store_filename); + if (config->token_file_name != NULL && + picoquic_save_retry_tokens(quic, config->token_file_name) != 0) { + fprintf(stderr, "Could not save tokens to <%s>.\n", config->token_file_name); } + } + + /* Freeing the memory that was allocated: first the HTTP three context, and + * then the QUIC context. Deleting the HTTP3 context also deletes HTTP3 + * objects such as stream contexts. Deleting the Quic context also deletes + * the quic connections started in that context. + * + * Freeing the stream contexts includes freeing the "control stream" context. + * When that happens, the application receives a callback of type + * "picohttp_callback_deregister". The baton application frees all allocated + * data during that callback -- except for the "baton_ctx", which + * in our case is entrely allocated on the stack. Other applications + * may want to do some memory clean up here. + */ + + if (h3_ctx != NULL) { + h3zero_callback_delete_context(cnx, h3_ctx); + } + + if (quic != NULL) { picoquic_free(quic); } return ret; } -/* Client socket loop - */ - /* Sample client, loop call back management. * The function "picoquic_packet_loop" will call back the application when it is ready to * receive or send packets, after receiving a packet, and after sending a packet. @@ -304,11 +369,11 @@ int baton_client_loop_cb(picoquic_quic_t* quic, picoquic_packet_loop_cb_enum cb_ fprintf(stdout, "Waiting for packets.\n"); break; case picoquic_packet_loop_after_receive: + case picoquic_packet_loop_after_send: if (picoquic_get_cnx_state(cb_ctx->cnx) == picoquic_state_disconnected) { ret = PICOQUIC_NO_ERROR_TERMINATE_PACKET_LOOP; } break; - case picoquic_packet_loop_after_send: if (picoquic_get_cnx_state(cb_ctx->cnx) == picoquic_state_disconnected) { ret = PICOQUIC_NO_ERROR_TERMINATE_PACKET_LOOP; } diff --git a/baton_app/baton_app.vcxproj b/baton_app/baton_app.vcxproj index 28ff0d853..9cd06af3f 100644 --- a/baton_app/baton_app.vcxproj +++ b/baton_app/baton_app.vcxproj @@ -92,7 +92,7 @@ true WIN32;_DEBUG;_CONSOLE;_WINDOWS;%(PreprocessorDefinitions) true - $(OPENSSLDIR)\include;..\..\picotls\include;$(SolutionDir)\picoquic;$(SolutionDir)\picohttp;$(SolutionDir)\loglib;%(AdditionalIncludeDirectories) + $(OPENSSLDIR)\include;..\..\picotls\include;$(SolutionDir)picoquic;$(SolutionDir)picohttp;$(SolutionDir)loglib;$(SolutionDir)picoquicfirst;%(AdditionalIncludeDirectories) Console @@ -109,7 +109,7 @@ true WIN32;NDEBUG;_CONSOLE;_WINDOWS;%(PreprocessorDefinitions) true - $(OPENSSLDIR)\include;..\..\picotls\include;$(SolutionDir)\picoquic;$(SolutionDir)\picohttp;$(SolutionDir)\loglib;%(AdditionalIncludeDirectories) + $(OPENSSLDIR)\include;..\..\picotls\include;$(SolutionDir)picoquic;$(SolutionDir)picohttp;$(SolutionDir)loglib;$(SolutionDir)picoquicfirst;%(AdditionalIncludeDirectories) Console @@ -126,7 +126,7 @@ true _DEBUG;_CONSOLE;_WINDOWS;_WINDOWS64;%(PreprocessorDefinitions) true - $(OPENSSL64DIR)\include;..\..\picotls\include;$(SolutionDir)\picoquic;$(SolutionDir)\picohttp;$(SolutionDir)\loglib;%(AdditionalIncludeDirectories) + $(OPENSSL64DIR)\include;..\..\picotls\include;$(SolutionDir)picoquic;$(SolutionDir)picohttp;$(SolutionDir)loglib;$(SolutionDir)picoquicfirst;%(AdditionalIncludeDirectories) Console @@ -143,7 +143,7 @@ true NDEBUG;_CONSOLE;_WINDOWS;_WINDOWS64;%(PreprocessorDefinitions) true - $(OPENSSL64DIR)\include;..\..\picotls\include;$(SolutionDir)\picoquic;$(SolutionDir)\picohttp;$(SolutionDir)\loglib;%(AdditionalIncludeDirectories) + $(OPENSSL64DIR)\include;..\..\picotls\include;$(SolutionDir)picoquic;$(SolutionDir)picohttp;$(SolutionDir)loglib;$(SolutionDir)picoquicfirst;%(AdditionalIncludeDirectories) Console diff --git a/loglib/auto_memlog.h b/loglib/auto_memlog.h new file mode 100644 index 000000000..6fef7b04f --- /dev/null +++ b/loglib/auto_memlog.h @@ -0,0 +1,34 @@ +/* +* Author: Christian Huitema +* Copyright (c) 2020, Private Octopus, Inc. +* All rights reserved. +* +* Permission to use, copy, modify, and distribute this software for any +* purpose with or without fee is hereby granted, provided that the above +* copyright notice and this permission notice appear in all copies. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL Private Octopus, Inc. BE LIABLE FOR ANY +* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef AUTO_MEMLOG_H +#define AUTO_MEMLOG_H + +#ifdef __cplusplus +extern "C" { +#endif + /* Initialize the memory log for a specific connection */ + int memlog_init(picoquic_cnx_t* cnx, size_t nb_lines, const char* memlog_file); +#ifdef __cplusplus +} +#endif + +#endif /* AUTO_MEMLOG_H */ \ No newline at end of file diff --git a/loglib/loglib.vcxproj b/loglib/loglib.vcxproj index 08672ac41..da3ea3591 100644 --- a/loglib/loglib.vcxproj +++ b/loglib/loglib.vcxproj @@ -151,6 +151,7 @@ + diff --git a/loglib/loglib.vcxproj.filters b/loglib/loglib.vcxproj.filters index 8e151a5d1..739e6d23b 100644 --- a/loglib/loglib.vcxproj.filters +++ b/loglib/loglib.vcxproj.filters @@ -27,5 +27,6 @@ Source + \ No newline at end of file diff --git a/loglib/memory_log.c b/loglib/memory_log.c new file mode 100644 index 000000000..cbd71e7f7 --- /dev/null +++ b/loglib/memory_log.c @@ -0,0 +1,289 @@ +/* +* Author: Christian Huitema +* Copyright (c) 2024, Private Octopus, Inc. +* All rights reserved. +* +* Permission to use, copy, modify, and distribute this software for any +* purpose with or without fee is hereby granted, provided that the above +* copyright notice and this permission notice appear in all copies. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL Private Octopus, Inc. BE LIABLE FOR ANY +* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/* +* Memory log: keep trace in memory for the specified connection. The program +* operates by: +* +* 1) Allocating memory at the beginning of the connection. The request +* specified how many lines shall be logged. +* 2) Log a line each time "pdu log" is called, until the table is full. +* 3) Writes the lines to the specified CSV file when the table is full, +* or when the connecton is being closed. +* +* The creation creates two entries in the connection context: +* - log memory callback function. +* - log memory address. +* There are two callbacks: log PDU, and close. +*/ +#include +#include +#include +#include +#include +#include "picoquic.h" +#include "picoquic_internal.h" +#include "picoquic_utils.h" + +typedef struct st_picoquic_memory_line_t { + uint64_t current_time; + uint64_t send_sequence; + uint64_t highest_acknowledged; + uint64_t highest_acknowledged_time; + uint64_t latest_time_acknowledged; + uint64_t cwin; + uint64_t one_way_delay_sample; + uint64_t rtt_sample; + uint64_t smoothed_rtt; + uint64_t rtt_min; + uint64_t bandwidth_estimate; + uint64_t receive_rate_estimate; + uint64_t send_mtu; + uint64_t packet_time_microsec; + uint64_t nb_retransmission_total; + uint64_t nb_spurious; + unsigned int cwin_blocked : 1; + unsigned int flow_blocked : 1; + unsigned int stream_blocked : 1; + unsigned int last_bw_estimate_path_limited : 1; + uint64_t cc_state; + uint64_t cc_param; + uint64_t peak_bandwidth_estimate; + uint64_t bytes_in_transit; +} picoquic_memory_line_t; + +typedef struct st_picoquic_memory_log_t { + FILE* F; + size_t nb_lines; + size_t nb_alloc; + + picoquic_memory_line_t* lines; +} picoquic_memory_log_t; + +int memlog_fill_line(picoquic_cnx_t* cnx, picoquic_path_t* path, picoquic_memory_line_t* memline, picoquic_memory_line_t* previous_line, uint64_t current_time) +{ + int ret = 0; + picoquic_packet_context_t* pkt_ctx = &cnx->pkt_ctx[picoquic_packet_context_application]; + + if (cnx->is_multipath_enabled) { + pkt_ctx = &path->pkt_ctx; + } + + memline->current_time = current_time - cnx->start_time; + memline->send_sequence = pkt_ctx->send_sequence; + + if (pkt_ctx->highest_acknowledged != UINT64_MAX) { + memline->highest_acknowledged = pkt_ctx->highest_acknowledged; + memline->highest_acknowledged_time = pkt_ctx->highest_acknowledged_time - cnx->start_time; + memline->latest_time_acknowledged = pkt_ctx->latest_time_acknowledged - cnx->start_time; + } + else { + memline->highest_acknowledged = UINT64_MAX; + memline->highest_acknowledged_time = 0; + memline->latest_time_acknowledged = 0; + } + + if (previous_line != NULL && + previous_line->send_sequence == memline->send_sequence && + previous_line->highest_acknowledged == memline->highest_acknowledged) { + /* Would be a duplicate line. */ + ret = -1; + } + else { + memline->cwin = path->cwin; + memline->one_way_delay_sample = path->one_way_delay_sample; + memline->rtt_sample = path->rtt_sample; + memline->smoothed_rtt = path->smoothed_rtt; + memline->rtt_min = path->rtt_min; + memline->bandwidth_estimate = path->bandwidth_estimate; + memline->receive_rate_estimate = path->receive_rate_estimate; + memline->send_mtu = path->send_mtu; + memline->packet_time_microsec = path->pacing.packet_time_microsec; + if (cnx->is_multipath_enabled) { + memline->nb_retransmission_total = path->nb_losses_found; + memline->nb_spurious = path->nb_spurious; + } + else { + memline->nb_retransmission_total = cnx->nb_retransmission_total; + memline->nb_spurious = cnx->nb_spurious; + } + memline->cwin_blocked = cnx->cwin_blocked; + memline->flow_blocked = cnx->flow_blocked; + memline->stream_blocked = cnx->stream_blocked; + + if (cnx->congestion_alg == NULL || + cnx->path[0]->congestion_alg_state == NULL) { + memline->cc_state = 0; + memline->cc_param = 0; + } + else { + cnx->congestion_alg->alg_observe(cnx->path[0], &memline->cc_state, &memline->cc_param); + } + + memline->peak_bandwidth_estimate = path->peak_bandwidth_estimate; + memline->bytes_in_transit = path->bytes_in_transit; + memline->last_bw_estimate_path_limited = path->last_bw_estimate_path_limited; + } + return ret; +} + +void memlog_print_header(FILE* F) +{ + fprintf(F, "current_time, "); + fprintf(F, "send_sequence, "); + + fprintf(F, "highest_ack, "); + fprintf(F, "high_ack_time, "); + fprintf(F, "latest_time_ack, "); + + fprintf(F, "cwin, "); + fprintf(F, "one_way_delay, "); + fprintf(F, "rtt_sample, "); + fprintf(F, "smoothed_rtt, "); + fprintf(F, "rtt_min, "); + fprintf(F, "bw_e, "); + fprintf(F, "recv_rate, "); + fprintf(F, "send_mtu, "); + fprintf(F, "packet_time, "); + + fprintf(F, "nb_retrans, "); + fprintf(F, "nb_spurious, "); + + fprintf(F, "cwin_blocked, "); + fprintf(F, "flow_blocked, "); + fprintf(F, "stream_blocked, "); + + fprintf(F, "cc_state, "); + fprintf(F, "cc_param, "); + + fprintf(F, "peak_bandwidth_estimate, "); + fprintf(F, "bytes_in_transit, "); + fprintf(F, "bwe_path_limited"); + fprintf(F, "\n"); +} + + +void memlog_print_line(FILE* F, picoquic_memory_line_t* memline) +{ + fprintf(F, "%" PRIu64 ",", memline->current_time); + fprintf(F, "%" PRIu64 ",", memline->send_sequence); + + fprintf(F, "%" PRIi64 ",", (int64_t)memline->highest_acknowledged); + fprintf(F, "%" PRIu64 ",", memline->highest_acknowledged_time); + fprintf(F, "%" PRIu64 ",", memline->latest_time_acknowledged); + + fprintf(F, "%" PRIu64 ",", memline->cwin); + fprintf(F, "%" PRIu64 ",", memline->one_way_delay_sample); + fprintf(F, "%" PRIu64 ",", memline->rtt_sample); + fprintf(F, "%" PRIu64 ",", memline->smoothed_rtt); + fprintf(F, "%" PRIu64 ",", memline->rtt_min); + fprintf(F, "%" PRIu64 ",", memline->bandwidth_estimate); + fprintf(F, "%" PRIu64 ",", memline->receive_rate_estimate); + fprintf(F, "%" PRIu64 ",", memline->send_mtu); + fprintf(F, "%" PRIu64 ",", memline->packet_time_microsec); + + fprintf(F, "%" PRIu64 ",", memline->nb_retransmission_total); + fprintf(F, "%" PRIu64 ",", memline->nb_spurious); + + fprintf(F, "%u,", memline->cwin_blocked); + fprintf(F, "%u,", memline->flow_blocked); + fprintf(F, "%u,", memline->stream_blocked); + + fprintf(F, "%" PRIu64 ",", memline->cc_state); + fprintf(F, "%" PRIu64 ",", memline->cc_param); + + fprintf(F, "%" PRIu64 ",", memline->peak_bandwidth_estimate); + fprintf(F, "%" PRIu64 ",", memline->bytes_in_transit); + fprintf(F, "%u,", memline->last_bw_estimate_path_limited); + fprintf(F, "\n"); +} + +void memlog_call_back(picoquic_cnx_t* cnx, picoquic_path_t* path, void* v_memlog, int op_code, uint64_t current_time) +{ + picoquic_memory_log_t* memlog = (picoquic_memory_log_t*)v_memlog; + if (memlog != NULL) { + if (op_code == 0) { + if (memlog->nb_lines < memlog->nb_alloc){ + if (memlog_fill_line(cnx, path, &memlog->lines[memlog->nb_lines], + (memlog->nb_lines == 0)?NULL: &memlog->lines[memlog->nb_lines - 1], + current_time) == 0) { + memlog->nb_lines++; + } + } + } + else + { +#ifdef PICOQUIC_MEMORY_LOG + cnx->memlog_call_back = NULL; + cnx->memlog_ctx = NULL; +#endif + /* This is the close callback */ + if (memlog->F != NULL) { + memlog_print_header(memlog->F); + for (size_t i = 0; i < memlog->nb_lines; i++) + { + memlog_print_line(memlog->F, &memlog->lines[i]); + } + memlog->F = picoquic_file_close(memlog->F); + } + free(memlog->lines); + free(memlog); + } + } +} + +int memlog_init(picoquic_cnx_t* cnx, size_t nb_lines, const char * memlog_file) +{ + int ret = -1; + picoquic_memory_log_t* memlog = (picoquic_memory_log_t*)malloc(sizeof(picoquic_memory_log_t)); + if (memlog != NULL) { + memset(memlog, 0, sizeof(picoquic_memory_log_t)); + + memlog->lines = (picoquic_memory_line_t*)malloc(nb_lines * sizeof(picoquic_memory_line_t)); + if (memlog->lines != NULL) { + memlog->nb_alloc = nb_lines; + + memlog->F = picoquic_file_open(memlog_file, "wt"); + + if (memlog->F == NULL) { + free(memlog->lines); + ret = -1; + } + else { + ret = 0; + } + } + else { + ret = -1; + } + + if (ret != 0) { + free(memlog); + } + else { +#ifdef PICOQUIC_MEMORY_LOG + cnx->memlog_call_back = memlog_call_back; + cnx->memlog_ctx = memlog; +#endif + } + } + return(ret); +} \ No newline at end of file diff --git a/picohttp/pico_webtransport.h b/picohttp/pico_webtransport.h index abc425fec..4353556aa 100644 --- a/picohttp/pico_webtransport.h +++ b/picohttp/pico_webtransport.h @@ -36,6 +36,25 @@ extern "C" { /* Create the control stream for the Web Transport session on the client. */ h3zero_stream_ctx_t* picowt_set_control_stream(picoquic_cnx_t* cnx, h3zero_callback_ctx_t* h3_ctx); + + + /* + * picowt_prepare_client_cnx: + * Prepare a QUIC connection and allocate the parameters required for + * the web transport setup: + * - p_cnx points to a quic connection context. If *p_cnx is null, a connection context + * will be created. + * - p_h3_ctx points to an HTTP3 connection context. If *p_h3_ctx is null, + * an HTTP3 context will be created. + * - p_control_stream_ctx should be NULL. On successfull return, it will + * point to the stream context for the "control stream" of + * the web transport connection. + */ + int picowt_prepare_client_cnx(picoquic_quic_t* quic, struct sockaddr* server_address, + picoquic_cnx_t** p_cnx, h3zero_callback_ctx_t** p_h3_ctx, + h3zero_stream_ctx_t** p_stream_ctx, + uint64_t current_time, const char* sni); + /* Web transport initiate, client side * cnx: an established QUIC connection, set to ALPN=H3. * stream_ctx: the stream context returned by picowt_set_control_stream diff --git a/picohttp/webtransport.c b/picohttp/webtransport.c index 38d634f9d..f04555b26 100644 --- a/picohttp/webtransport.c +++ b/picohttp/webtransport.c @@ -186,6 +186,55 @@ h3zero_stream_ctx_t* picowt_set_control_stream(picoquic_cnx_t* cnx, h3zero_callb return stream_ctx; } +int picowt_prepare_client_cnx(picoquic_quic_t* quic, struct sockaddr* server_address, + picoquic_cnx_t** p_cnx, h3zero_callback_ctx_t** p_h3_ctx, + h3zero_stream_ctx_t** p_stream_ctx, + uint64_t current_time, const char* sni) +{ + int ret = 0; + + + /* use the generic H3 callback */ + /* Set the client callback context */ + if (*p_h3_ctx == NULL) { + *p_h3_ctx = h3zero_callback_create_context(NULL); + } + if (*p_h3_ctx == NULL) { + ret = 1; + } + else + { + /* Create a client connection */ + if (*p_cnx == NULL) { + *p_cnx = picoquic_create_cnx(quic, picoquic_null_connection_id, picoquic_null_connection_id, + (struct sockaddr*)server_address, current_time, 0, sni, "h3", 1); + } + if (*p_cnx == NULL) { + fprintf(stderr, "Could not create connection context\n"); + ret = -1; + } + else { + picowt_set_transport_parameters(*p_cnx); + picoquic_set_callback(*p_cnx, h3zero_callback, *p_h3_ctx); + *p_stream_ctx = picowt_set_control_stream(*p_cnx, *p_h3_ctx); + + if (*p_stream_ctx == NULL) { + ret = -1; + } + else { + /* Perform the initialization, settings and QPACK streams + */ + ret = h3zero_protocol_init(*p_cnx); + } + } + } + return ret; +} + + +/* +* Connect +*/ int picowt_connect(picoquic_cnx_t* cnx, h3zero_callback_ctx_t* ctx, h3zero_stream_ctx_t* stream_ctx, const char * authority, const char* path, picohttp_post_data_cb_fn wt_callback, void* wt_ctx) diff --git a/picohttp/wt_baton.c b/picohttp/wt_baton.c index e64ccb478..82e2b069f 100644 --- a/picohttp/wt_baton.c +++ b/picohttp/wt_baton.c @@ -892,32 +892,36 @@ int wt_baton_process_remote_stream(picoquic_cnx_t* cnx, return ret; } -/* Queue the connection to a baton server - */ -int wt_baton_connect(picoquic_cnx_t * cnx, wt_baton_ctx_t* baton_ctx, h3zero_callback_ctx_t* h3_ctx) +/* +* wt_baton_prepare_context: +* Prepare the application context (baton_ctx), documenting the h3 context, +* and initializing the application. Should be called before calling +* picowt_connect. +*/ + +int wt_baton_prepare_context(picoquic_cnx_t* cnx, wt_baton_ctx_t* baton_ctx, + h3zero_callback_ctx_t* h3_ctx, h3zero_stream_ctx_t* control_stream_ctx, + const char* server_name, const char* path) { int ret = 0; - /* Create a stream context for the connect call. */ - h3zero_stream_ctx_t* stream_ctx = picowt_set_control_stream(cnx, h3_ctx); - if (stream_ctx == NULL) { - ret = -1; + wt_baton_ctx_init(baton_ctx, h3_ctx, NULL, NULL); + baton_ctx->cnx = cnx; + baton_ctx->is_client = 1; + baton_ctx->authority = server_name; + baton_ctx->server_path = path; + + baton_ctx->connection_ready = 1; + baton_ctx->is_client = 1; + + if (baton_ctx->server_path != NULL) { + ret = wt_baton_ctx_path_params(baton_ctx, (const uint8_t*)baton_ctx->server_path, + strlen(baton_ctx->server_path)); } - else { - baton_ctx->connection_ready = 1; - baton_ctx->is_client = 1; - if (baton_ctx->server_path != NULL) { - ret = wt_baton_ctx_path_params(baton_ctx, (const uint8_t*)baton_ctx->server_path, - strlen(baton_ctx->server_path)); - } - if (ret == 0) { - /* send the WT CONNECT */ - ret = picowt_connect(cnx, h3_ctx, stream_ctx, baton_ctx->authority, baton_ctx->server_path, wt_baton_callback, baton_ctx); - } - if (ret == 0) { - wt_baton_set_receive_ready(baton_ctx); - } + if (ret == 0) { + wt_baton_set_receive_ready(baton_ctx); } + return ret; } diff --git a/picohttp/wt_baton.h b/picohttp/wt_baton.h index 61db97bfb..501662db1 100644 --- a/picohttp/wt_baton.h +++ b/picohttp/wt_baton.h @@ -127,7 +127,9 @@ extern "C" { int nb_turns_required; } wt_baton_app_ctx_t; - int wt_baton_connect(picoquic_cnx_t* cnx, wt_baton_ctx_t* baton_ctx, h3zero_callback_ctx_t* h3_ctx); + int wt_baton_prepare_context(picoquic_cnx_t* cnx, wt_baton_ctx_t* baton_ctx, + h3zero_callback_ctx_t* h3_ctx, h3zero_stream_ctx_t* control_stream_ctx, + const char* server_name, const char* path); int wt_baton_ctx_path_params(wt_baton_ctx_t* baton_ctx, const uint8_t* path, size_t path_length); diff --git a/picoquic.sln b/picoquic.sln index fb8d479cd..3dbe2016f 100644 --- a/picoquic.sln +++ b/picoquic.sln @@ -1,51 +1,51 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29230.47 +# Visual Studio Version 17 +VisualStudioVersion = 17.11.35208.52 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "picoquic", "picoquic\picoquic.vcxproj", "{63E1E6B7-DB5F-4EDC-8AC8-7E9F5990D11F}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UnitTest1", "UnitTest1\UnitTest1.vcxproj", "{454244B2-B174-41B4-B7AC-41962FE95941}" ProjectSection(ProjectDependencies) = postProject - {C8F3740E-56FB-4BE7-9D8C-30A954846146} = {C8F3740E-56FB-4BE7-9D8C-30A954846146} {63E1E6B7-DB5F-4EDC-8AC8-7E9F5990D11F} = {63E1E6B7-DB5F-4EDC-8AC8-7E9F5990D11F} - {B04168BD-4D56-4DE9-B1E3-CF4C16FE21C7} = {B04168BD-4D56-4DE9-B1E3-CF4C16FE21C7} {998765EE-64DF-49C1-8471-A79E2DA7CD21} = {998765EE-64DF-49C1-8471-A79E2DA7CD21} + {B04168BD-4D56-4DE9-B1E3-CF4C16FE21C7} = {B04168BD-4D56-4DE9-B1E3-CF4C16FE21C7} + {C8F3740E-56FB-4BE7-9D8C-30A954846146} = {C8F3740E-56FB-4BE7-9D8C-30A954846146} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "picoquictest", "picoquictest\picoquictest.vcxproj", "{B04168BD-4D56-4DE9-B1E3-CF4C16FE21C7}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "picoquicdemo", "picoquicfirst\picoquicfirst.vcxproj", "{995745F2-E8BA-48C1-AF6D-BA554B869D47}" ProjectSection(ProjectDependencies) = postProject - {C8F3740E-56FB-4BE7-9D8C-30A954846146} = {C8F3740E-56FB-4BE7-9D8C-30A954846146} {63E1E6B7-DB5F-4EDC-8AC8-7E9F5990D11F} = {63E1E6B7-DB5F-4EDC-8AC8-7E9F5990D11F} - {B04168BD-4D56-4DE9-B1E3-CF4C16FE21C7} = {B04168BD-4D56-4DE9-B1E3-CF4C16FE21C7} {998765EE-64DF-49C1-8471-A79E2DA7CD21} = {998765EE-64DF-49C1-8471-A79E2DA7CD21} + {B04168BD-4D56-4DE9-B1E3-CF4C16FE21C7} = {B04168BD-4D56-4DE9-B1E3-CF4C16FE21C7} + {C8F3740E-56FB-4BE7-9D8C-30A954846146} = {C8F3740E-56FB-4BE7-9D8C-30A954846146} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "picoquic_t", "picoquic_t\picoquic_t.vcxproj", "{4898D6E0-6FC5-4375-99F5-4C69BB20CE94}" ProjectSection(ProjectDependencies) = postProject {63E1E6B7-DB5F-4EDC-8AC8-7E9F5990D11F} = {63E1E6B7-DB5F-4EDC-8AC8-7E9F5990D11F} - {B04168BD-4D56-4DE9-B1E3-CF4C16FE21C7} = {B04168BD-4D56-4DE9-B1E3-CF4C16FE21C7} {998765EE-64DF-49C1-8471-A79E2DA7CD21} = {998765EE-64DF-49C1-8471-A79E2DA7CD21} + {B04168BD-4D56-4DE9-B1E3-CF4C16FE21C7} = {B04168BD-4D56-4DE9-B1E3-CF4C16FE21C7} EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A71B5718-FDFC-4047-A76C-15EA7A78ED6B}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "quicwind", "quicwind\quicwind.vcxproj", "{7C931959-3DBA-4440-A215-B085036F3EE1}" ProjectSection(ProjectDependencies) = postProject - {C8F3740E-56FB-4BE7-9D8C-30A954846146} = {C8F3740E-56FB-4BE7-9D8C-30A954846146} {63E1E6B7-DB5F-4EDC-8AC8-7E9F5990D11F} = {63E1E6B7-DB5F-4EDC-8AC8-7E9F5990D11F} + {C8F3740E-56FB-4BE7-9D8C-30A954846146} = {C8F3740E-56FB-4BE7-9D8C-30A954846146} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "picohttp", "picohttp\picohttp.vcxproj", "{C8F3740E-56FB-4BE7-9D8C-30A954846146}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "picohttp_t", "picohttp_t\picohttp_t.vcxproj", "{C9138A46-C3D0-491B-BACF-C7C65592D1F7}" ProjectSection(ProjectDependencies) = postProject - {C8F3740E-56FB-4BE7-9D8C-30A954846146} = {C8F3740E-56FB-4BE7-9D8C-30A954846146} {63E1E6B7-DB5F-4EDC-8AC8-7E9F5990D11F} = {63E1E6B7-DB5F-4EDC-8AC8-7E9F5990D11F} - {B04168BD-4D56-4DE9-B1E3-CF4C16FE21C7} = {B04168BD-4D56-4DE9-B1E3-CF4C16FE21C7} {998765EE-64DF-49C1-8471-A79E2DA7CD21} = {998765EE-64DF-49C1-8471-A79E2DA7CD21} + {B04168BD-4D56-4DE9-B1E3-CF4C16FE21C7} = {B04168BD-4D56-4DE9-B1E3-CF4C16FE21C7} + {C8F3740E-56FB-4BE7-9D8C-30A954846146} = {C8F3740E-56FB-4BE7-9D8C-30A954846146} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "loglib", "loglib\loglib.vcxproj", "{998765EE-64DF-49C1-8471-A79E2DA7CD21}" @@ -58,33 +58,33 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "picolog", "picolog\picolog. EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sample", "sample\sample.vcxproj", "{F395E460-C042-4FA9-B00D-6EC14856182D}" ProjectSection(ProjectDependencies) = postProject - {B3DDD196-3D03-4396-97BD-E5DE733E9D24} = {B3DDD196-3D03-4396-97BD-E5DE733E9D24} {63E1E6B7-DB5F-4EDC-8AC8-7E9F5990D11F} = {63E1E6B7-DB5F-4EDC-8AC8-7E9F5990D11F} {998765EE-64DF-49C1-8471-A79E2DA7CD21} = {998765EE-64DF-49C1-8471-A79E2DA7CD21} + {B3DDD196-3D03-4396-97BD-E5DE733E9D24} = {B3DDD196-3D03-4396-97BD-E5DE733E9D24} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PerfAndStressTest", "PerfAndStressTest\PerfAndStressTest.vcxproj", "{436B580F-B0E8-4EB8-A987-08218D863316}" ProjectSection(ProjectDependencies) = postProject - {C8F3740E-56FB-4BE7-9D8C-30A954846146} = {C8F3740E-56FB-4BE7-9D8C-30A954846146} {63E1E6B7-DB5F-4EDC-8AC8-7E9F5990D11F} = {63E1E6B7-DB5F-4EDC-8AC8-7E9F5990D11F} - {B04168BD-4D56-4DE9-B1E3-CF4C16FE21C7} = {B04168BD-4D56-4DE9-B1E3-CF4C16FE21C7} {998765EE-64DF-49C1-8471-A79E2DA7CD21} = {998765EE-64DF-49C1-8471-A79E2DA7CD21} + {B04168BD-4D56-4DE9-B1E3-CF4C16FE21C7} = {B04168BD-4D56-4DE9-B1E3-CF4C16FE21C7} + {C8F3740E-56FB-4BE7-9D8C-30A954846146} = {C8F3740E-56FB-4BE7-9D8C-30A954846146} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "thread_tester", "thread_tester\thread_tester.vcxproj", "{57341DB3-B498-4E7B-9CB2-E897A562C15F}" ProjectSection(ProjectDependencies) = postProject - {B3DDD196-3D03-4396-97BD-E5DE733E9D24} = {B3DDD196-3D03-4396-97BD-E5DE733E9D24} {63E1E6B7-DB5F-4EDC-8AC8-7E9F5990D11F} = {63E1E6B7-DB5F-4EDC-8AC8-7E9F5990D11F} {998765EE-64DF-49C1-8471-A79E2DA7CD21} = {998765EE-64DF-49C1-8471-A79E2DA7CD21} + {B3DDD196-3D03-4396-97BD-E5DE733E9D24} = {B3DDD196-3D03-4396-97BD-E5DE733E9D24} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "baton_app", "baton_app\baton_app.vcxproj", "{C0F21D3F-ECC3-4AB5-A3E3-E2D48965EBA5}" ProjectSection(ProjectDependencies) = postProject - {C8F3740E-56FB-4BE7-9D8C-30A954846146} = {C8F3740E-56FB-4BE7-9D8C-30A954846146} - {B3DDD196-3D03-4396-97BD-E5DE733E9D24} = {B3DDD196-3D03-4396-97BD-E5DE733E9D24} {63E1E6B7-DB5F-4EDC-8AC8-7E9F5990D11F} = {63E1E6B7-DB5F-4EDC-8AC8-7E9F5990D11F} - {B04168BD-4D56-4DE9-B1E3-CF4C16FE21C7} = {B04168BD-4D56-4DE9-B1E3-CF4C16FE21C7} {998765EE-64DF-49C1-8471-A79E2DA7CD21} = {998765EE-64DF-49C1-8471-A79E2DA7CD21} + {B04168BD-4D56-4DE9-B1E3-CF4C16FE21C7} = {B04168BD-4D56-4DE9-B1E3-CF4C16FE21C7} + {B3DDD196-3D03-4396-97BD-E5DE733E9D24} = {B3DDD196-3D03-4396-97BD-E5DE733E9D24} + {C8F3740E-56FB-4BE7-9D8C-30A954846146} = {C8F3740E-56FB-4BE7-9D8C-30A954846146} EndProjectSection EndProject Global diff --git a/picoquic/bbr.c b/picoquic/bbr.c index bc8b9971f..ffb2a225a 100644 --- a/picoquic/bbr.c +++ b/picoquic/bbr.c @@ -25,6 +25,14 @@ #include "cc_common.h" #include "picoquic_utils.h" +#ifdef BBRExperiment +#define BBRExpGate(ctx, test, action) { if (ctx->exp_flags.test) action; } +#define BBRExpTest(ctx, test) ( (ctx)->exp_flags.test ) +#else +#define BBRExpGate(ctx, test, action) {} +#define BBRExpTest(ctx, test) (1) +#endif + #define RTTJitterBuffer On #define RTTJitterBufferStartup On #define RTTJitterBufferProbe On @@ -257,6 +265,10 @@ typedef struct st_picoquic_bbr_state_t { /* Experimental extensions, may or maynot be a good idea. */ uint64_t wifi_shadow_rtt; /* Shadow RTT used for wifi connections. */ double quantum_ratio; /* allow application to use a different default than 0.1% of bandwidth (or 1ms of traffic) */ +#ifdef BBRExperiment + /* Control flags for BBR improvements */ + bbr_exp exp_flags; +#endif } picoquic_bbr_state_t; @@ -439,6 +451,11 @@ static void BBROnInit(picoquic_bbr_state_t* bbr_state, picoquic_path_t* path_x, bbr_state->extra_acked_delivered = 0; /* Support for the wifi_shadow_rtt hack */ bbr_state->wifi_shadow_rtt = path_x->cnx->quic->wifi_shadow_rtt; + +#ifdef BBRExperiment + /* Support for BBR Experiment */ + bbr_state->exp_flags = path_x->cnx->quic->bbr_exp_flags; +#endif /* Support for experimenting with the send_quantum ratio */ bbr_state->quantum_ratio = path_x->cnx->quic->bbr_quantum_ratio; if (bbr_state->quantum_ratio == 0) { @@ -675,7 +692,7 @@ static void BBROnExitRecovery(picoquic_bbr_state_t* bbr_state, picoquic_path_t* bbr_state->recovery_packet_number = UINT64_MAX; bbr_state->packet_conservation = 0; - if (bbr_state->is_pto_recovery) { + if (bbr_state->is_pto_recovery && BBRExpTest(bbr_state, do_handle_suspension)) { /* TODO: * we should try to enter startup with a high enough BW. However, * simple attempts to restore the BW parameters have proven ineffective. @@ -1639,7 +1656,7 @@ static int BBRCheckTimeToProbeBW(picoquic_bbr_state_t* bbr_state, picoquic_path_ { if (BBRHasElapsedInPhase(bbr_state, bbr_state->bw_probe_wait, current_time) || BBRIsRenoCoexistenceProbeTime(bbr_state, path_x) || - BBRCheckAppLimitedEnded(bbr_state, rs)) { + (BBRExpTest(bbr_state, do_enter_probeBW_after_limited) && BBRCheckAppLimitedEnded(bbr_state, rs))) { BBRStartProbeBW_REFILL(bbr_state, path_x); return 1; } @@ -1654,7 +1671,7 @@ static void BBRStartProbeBW_DOWN(picoquic_bbr_state_t* bbr_state, picoquic_path_ bbr_state->cwnd_gain = BBRProbeBwDownCwndGain; /* maintain cwnd */ BBRResetCongestionSignals(bbr_state); bbr_state->bw_probe_up_cnt = UINT32_MAX; /* not growing inflight_hi */ - if (bbr_state->probe_probe_bw_quickly) { + if (bbr_state->probe_probe_bw_quickly && BBRExpTest(bbr_state, do_rapid_start)) { BBRPickProbeWaitEarly(bbr_state); } else { @@ -1763,6 +1780,7 @@ static void BBRUpdateProbeBWCyclePhase(picoquic_bbr_state_t* bbr_state, picoquic case picoquic_bbr_alg_probe_bw_up: if (BBRHasElapsedInPhase(bbr_state, bbr_state->min_rtt, current_time) && bbr_state->min_rtt > PICOQUIC_MINRTT_MARGIN && + BBRExpTest(bbr_state, do_exit_probeBW_up_on_delay) && (bbr_state->nb_rtt_excess > 0 || path_x->bytes_in_transit > BBRInflightWithBw(bbr_state, path_x, 1.25, bbr_state->max_bw))) { BBRStartProbeBW_DOWN(bbr_state, path_x, current_time); @@ -2288,6 +2306,7 @@ static void picoquic_bbr_notify( break; case picoquic_congestion_notification_lost_feedback: /* Feedback has been lost. It will be restored at the next notification. */ + BBRExpGate(bbr_state, do_control_lost, break); BBREnterLostFeedback(bbr_state, path_x); break; case picoquic_congestion_notification_rtt_measurement: diff --git a/picoquic/memory_log.c b/picoquic/memory_log.c new file mode 100644 index 000000000..e69de29bb diff --git a/picoquic/picoquic.h b/picoquic/picoquic.h index a85406d12..3d2ab90a9 100644 --- a/picoquic/picoquic.h +++ b/picoquic/picoquic.h @@ -40,7 +40,7 @@ extern "C" { #endif -#define PICOQUIC_VERSION "1.1.25.1" +#define PICOQUIC_VERSION "1.1.25.2" #define PICOQUIC_ERROR_CLASS 0x400 #define PICOQUIC_ERROR_DUPLICATE (PICOQUIC_ERROR_CLASS + 1) #define PICOQUIC_ERROR_AEAD_CHECK (PICOQUIC_ERROR_CLASS + 3) @@ -1528,6 +1528,24 @@ void picoquic_set_default_wifi_shadow_rtt(picoquic_quic_t* quic, uint64_t wifi_s */ void picoquic_set_default_bbr_quantum_ratio(picoquic_quic_t* quic, double quantum_ratio); +/* Temporary code, do define a set of BBR flags that +* turn on and off individual extensions. We want to use that +* to do "before/after" measurements. + */ +#define BBRExperiment on +#ifdef BBRExperiment +/* Control flags for BBR improvements */ +typedef struct st_bbr_exp { + unsigned int do_early_exit : 1; + unsigned int do_rapid_start : 1; + unsigned int do_handle_suspension : 1; + unsigned int do_control_lost : 1; + unsigned int do_exit_probeBW_up_on_delay : 1; + unsigned int do_enter_probeBW_after_limited : 1; +} bbr_exp; + +void picoquic_set_bbr_exp(picoquic_quic_t * quic, bbr_exp* exp); +#endif /* The experimental API 'picoquic_set_priority_limit_for_bypass' * instruct the stack to send the high priority streams or datagrams * immediately, even if congestion control would normally prevent it. diff --git a/picoquic/picoquic_internal.h b/picoquic/picoquic_internal.h index 7e467557d..55e6b8245 100644 --- a/picoquic/picoquic_internal.h +++ b/picoquic/picoquic_internal.h @@ -734,6 +734,11 @@ typedef struct st_picoquic_quic_t { struct st_picoquic_unified_logging_t* qlog_fns; picoquic_performance_log_fn perflog_fn; void* v_perflog_ctx; + +#ifdef BBRExperiment + bbr_exp bbr_exp_flags; +#endif + } picoquic_quic_t; picoquic_packet_context_enum picoquic_context_from_epoch(int epoch); @@ -1520,7 +1525,10 @@ typedef struct st_picoquic_cnx_t { uint16_t log_unique; FILE* f_binlog; char* binlog_file_name; - +#ifdef PICOQUIC_MEMORY_LOG + void (*memlog_call_back)(picoquic_cnx_t* cnx, picoquic_path_t* path, void* v_memlog, int op_code, uint64_t current_time); + void *memlog_ctx; +#endif } picoquic_cnx_t; typedef struct st_picoquic_packet_data_t { diff --git a/picoquic/quicctx.c b/picoquic/quicctx.c index 3f4123924..bc54331af 100644 --- a/picoquic/quicctx.c +++ b/picoquic/quicctx.c @@ -736,7 +736,17 @@ picoquic_quic_t* picoquic_create(uint32_t max_nb_connections, } } } - +#ifdef BBRExperiment + if (ret == 0) { + quic->bbr_exp_flags.do_early_exit = 1; + quic->bbr_exp_flags.do_rapid_start = 1; + quic->bbr_exp_flags.do_handle_suspension = 1; + quic->bbr_exp_flags.do_control_lost = 1; + quic->bbr_exp_flags.do_exit_probeBW_up_on_delay = 1; + quic->bbr_exp_flags.do_enter_probeBW_after_limited = 1; + } +#endif + if (ret != 0) { picoquic_free(quic); quic = NULL; @@ -4583,6 +4593,11 @@ void picoquic_delete_sooner_packets(picoquic_cnx_t* cnx) void picoquic_delete_cnx(picoquic_cnx_t* cnx) { if (cnx != NULL) { +#ifdef PICOQUIC_MEMORY_LOG + if (cnx->memlog_call_back != NULL) { + cnx->memlog_call_back(cnx, NULL, cnx->memlog_ctx, 1, 0); + } +#endif if (cnx->quic->perflog_fn != NULL) { (void)(cnx->quic->perflog_fn)(cnx->quic, cnx, 0); } @@ -4878,6 +4893,12 @@ void picoquic_set_default_bbr_quantum_ratio(picoquic_quic_t* quic, double quantu quic->bbr_quantum_ratio = quantum_ratio; } +#ifdef BBRExperiment +void picoquic_set_bbr_exp(picoquic_quic_t* quic, bbr_exp* exp) +{ + quic->bbr_exp_flags = *exp; +} +#endif void picoquic_set_priority_limit_for_bypass(picoquic_cnx_t* cnx, uint8_t priority_limit) { cnx->priority_limit_for_bypass = priority_limit; diff --git a/picoquic/unified_log.c b/picoquic/unified_log.c index 74cdc81d0..7d432e190 100644 --- a/picoquic/unified_log.c +++ b/picoquic/unified_log.c @@ -249,6 +249,11 @@ void picoquic_log_close_connection(picoquic_cnx_t* cnx) /* log congestion control parameters */ void picoquic_log_cc_dump(picoquic_cnx_t* cnx, uint64_t current_time) { +#ifdef PICOQUIC_MEMORY_LOG + if (cnx->memlog_call_back != NULL) { + cnx->memlog_call_back(cnx, cnx->path[0], cnx->memlog_ctx, 0, current_time); + } +#endif if (picoquic_cnx_is_still_logging(cnx)) { if (cnx->quic->F_log != NULL) { cnx->quic->text_log_fns->log_cc_dump(cnx, current_time); diff --git a/picoquicfirst/picoquicdemo.c b/picoquicfirst/picoquicdemo.c index 01fc6fa78..8556601a3 100644 --- a/picoquicfirst/picoquicdemo.c +++ b/picoquicfirst/picoquicdemo.c @@ -81,7 +81,9 @@ static const char* token_store_filename = "demo_token_store.bin"; #include "performance_log.h" #include "picoquic_config.h" #include "picoquic_lb.h" - +#ifdef PICOQUIC_MEMORY_LOG +#include "auto_memlog.h" +#endif /* * SIDUCK datagram demo call back. */ @@ -141,6 +143,14 @@ static int server_loop_cb(picoquic_quic_t* quic, picoquic_packet_loop_cb_enum cb if (ret == 0 && cb_ctx->just_once){ if (!cb_ctx->first_connection_seen && picoquic_get_first_cnx(quic) != NULL) { +#ifdef PICOQUIC_MEMORY_LOG + if (memlog_init(picoquic_get_first_cnx(quic), 1000000, "./memlog.csv") != 0) { + fprintf(stderr, "Could not initialize memlog as ./memlog.csv\n"); + } + else { + fprintf(stdout, "Initialized memlog as ./memlog.csv\n"); + } +#endif cb_ctx->first_connection_seen = 1; fprintf(stdout, "First connection noticed.\n"); } else if (cb_ctx->first_connection_seen && picoquic_get_first_cnx(quic) == NULL) { diff --git a/picoquictest/webtransport_test.c b/picoquictest/webtransport_test.c index c4fba02f2..e8ed5d9a5 100644 --- a/picoquictest/webtransport_test.c +++ b/picoquictest/webtransport_test.c @@ -117,35 +117,38 @@ static int picowt_baton_test_one( * We want to replace that by the demo client callback */ if (ret == 0) { - /* use the generic H3 callback */ - /* Set the client callback context */ - h3zero_cb = h3zero_callback_create_context(NULL); - if (h3zero_cb == NULL) { - ret = 1; - } - else { - h3zero_cb->no_print = 1; - picoquic_set_callback(test_ctx->cnx_client, h3zero_callback, h3zero_cb); - /* Initialize the callback context. First, create a bidir stream */ - wt_baton_ctx_init(&baton_ctx, h3zero_cb, NULL, NULL); - baton_ctx.is_client = 1; - baton_ctx.authority = PICOQUIC_TEST_SNI; - baton_ctx.server_path = baton_path; - /* Create a stream context for the connect call. */ - ret = wt_baton_connect(test_ctx->cnx_client, &baton_ctx, h3zero_cb); + /* Set the client callback context using as much as possible + * the generic picowt calls. */ + h3zero_stream_ctx_t* control_stream_ctx = NULL; + + ret = picowt_prepare_client_cnx(test_ctx->qclient, (struct sockaddr*)NULL, + &test_ctx->cnx_client, &h3zero_cb, &control_stream_ctx, simulated_time, PICOQUIC_TEST_SNI); + + if (ret == 0) { + ret = wt_baton_prepare_context(test_ctx->cnx_client, &baton_ctx, h3zero_cb, + control_stream_ctx, PICOQUIC_TEST_SNI, baton_path); } - /* Initialize the server -- should include the path setup for connect action */ - memset(&server_param, 0, sizeof(picohttp_server_parameters_t)); - server_param.web_folder = NULL; - server_param.path_table = path_item_list; - server_param.path_table_nb = 1; - picoquic_set_alpn_select_fn(test_ctx->qserver, picoquic_demo_server_callback_select_alpn); - picoquic_set_default_callback(test_ctx->qserver, h3zero_callback, &server_param); + if (ret == 0) { + ret = picowt_connect(test_ctx->cnx_client, h3zero_cb, control_stream_ctx, + baton_ctx.authority, baton_ctx.server_path, + wt_baton_callback, &baton_ctx); + } if (ret == 0) { ret = picoquic_start_client_cnx(test_ctx->cnx_client); } + + if (ret == 0) { + /* Initialize the server -- should include the path setup for connect action */ + memset(&server_param, 0, sizeof(picohttp_server_parameters_t)); + server_param.web_folder = NULL; + server_param.path_table = path_item_list; + server_param.path_table_nb = 1; + + picoquic_set_alpn_select_fn(test_ctx->qserver, picoquic_demo_server_callback_select_alpn); + picoquic_set_default_callback(test_ctx->qserver, h3zero_callback, &server_param); + } } /* Establish the connection from client to server. At this stage,