diff --git a/CMakeLists.txt b/CMakeLists.txt index 5f4c3f513..1d7ba755b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -190,6 +190,7 @@ set(PICOQUIC_TEST_LIBRARY_FILES picoquictest/getter_test.c picoquictest/hashtest.c picoquictest/high_latency_test.c + picoquictest/hystart_test.c picoquictest/intformattest.c picoquictest/l4s_test.c picoquictest/mbedtls_test.c diff --git a/UnitTest1/unittest1.cpp b/UnitTest1/unittest1.cpp index d08a87974..3cf30d41e 100644 --- a/UnitTest1/unittest1.cpp +++ b/UnitTest1/unittest1.cpp @@ -2055,6 +2055,13 @@ namespace UnitTest1 Assert::AreEqual(ret, 0); } + TEST_METHOD(cc_compete_cubic2_hystart_pp) + { + int ret = cc_compete_cubic2_hystart_pp_test(); + + Assert::AreEqual(ret, 0); + } + TEST_METHOD(cc_compete_prague2) { int ret = cc_compete_prague2_test(); @@ -3438,6 +3445,12 @@ namespace UnitTest1 Assert::AreEqual(ret, 0); } + TEST_METHOD(hystart) { + int ret = hystart_test(); + + Assert::AreEqual(ret, 0); + } + TEST_METHOD(cplusplus) { int ret = cplusplustest(); diff --git a/picohttp_t/picohttp_t.c b/picohttp_t/picohttp_t.c index dff80fb83..de4fc5f6f 100644 --- a/picohttp_t/picohttp_t.c +++ b/picohttp_t/picohttp_t.c @@ -112,6 +112,7 @@ static const picoquic_test_def_t test_table[] = { { "quicperf_multi", quicperf_multi_test }, { "quicperf_overflow", quicperf_overflow_test }, { "cc_compete_cubic2", cc_compete_cubic2_test }, + { "cc_compete_cubic2_hystart_pp", cc_compete_cubic2_hystart_pp_test }, { "cc_compete_prague2", cc_compete_prague2_test }, { "cc_compete_d_cubic", cc_compete_d_cubic_test }, { "cc_ns_asym", cc_ns_asym_test }, diff --git a/picoquic/bbr.c b/picoquic/bbr.c index c51c5ee28..d7d64fa48 100644 --- a/picoquic/bbr.c +++ b/picoquic/bbr.c @@ -274,6 +274,9 @@ typedef struct st_picoquic_bbr_state_t { picoquic_min_max_rtt_t rtt_filter; uint64_t bdp_seed; unsigned int probe_bdp_seed; + /* HyStart++ */ + picoquic_hystart_alg_t hystart_alg; + picoquic_hystart_pp_state_t hystart_pp_state; /* Experimental extensions, may or maynot be a good idea. */ char const* option_string; @@ -545,6 +548,11 @@ static void BBRSetOptions(picoquic_bbr_state_t* bbr_state) break; } } + case 'Y': + /* Reading digits into an uint64_t */ + bbr_state->hystart_alg = atoi(x); + x++; + break; case ':': /* Ignore */ break; @@ -556,7 +564,7 @@ static void BBRSetOptions(picoquic_bbr_state_t* bbr_state) } /* Initialization of the BBR state */ -static void BBROnInit(picoquic_bbr_state_t* bbr_state, picoquic_path_t* path_x, uint64_t current_time, char const * option_string) +static void BBROnInit(picoquic_bbr_state_t* bbr_state, picoquic_cnx_t* cnx, picoquic_path_t* path_x, uint64_t current_time, char const * option_string) { /* TODO: init_windowed_max_filter(filter = BBR.MaxBwFilter, value = 0, time = 0) @@ -593,11 +601,17 @@ static void BBROnInit(picoquic_bbr_state_t* bbr_state, picoquic_path_t* path_x, BBRInitFullPipe(bbr_state); BBRInitPacingRate(bbr_state, path_x); BBREnterStartup(bbr_state, path_x); + + /* HyStart++ */ + memset(&bbr_state->hystart_pp_state, 0, sizeof(picoquic_hystart_pp_state_t)); + if (IS_HYSTART_PP(bbr_state->hystart_alg)) { + picoquic_hystart_pp_reset(&bbr_state->hystart_pp_state, cnx, path_x); + } } -static void picoquic_bbr_reset(picoquic_bbr_state_t* bbr_state, picoquic_path_t* path_x, uint64_t current_time) +static void picoquic_bbr_reset(picoquic_bbr_state_t* bbr_state, picoquic_cnx_t* cnx, picoquic_path_t* path_x, uint64_t current_time) { - BBROnInit(bbr_state, path_x, current_time, bbr_state->option_string); + BBROnInit(bbr_state, cnx, path_x, current_time, bbr_state->option_string); } static void picoquic_bbr_init(picoquic_cnx_t * cnx, picoquic_path_t* path_x, char const* option_string, uint64_t current_time) @@ -607,7 +621,7 @@ static void picoquic_bbr_init(picoquic_cnx_t * cnx, picoquic_path_t* path_x, cha path_x->congestion_alg_state = (void*)bbr_state; if (bbr_state != NULL) { - BBROnInit(bbr_state, path_x, current_time, option_string); + BBROnInit(bbr_state, cnx, path_x, current_time, option_string); } } @@ -2125,7 +2139,7 @@ static void BBRExitStartupLongRtt(picoquic_bbr_state_t* bbr_state, picoquic_path BBRCheckDrain(bbr_state, path_x, current_time); } -void BBRCheckStartupLongRtt(picoquic_bbr_state_t* bbr_state, picoquic_path_t* path_x, bbr_per_ack_state_t* rs, uint64_t current_time) +void BBRCheckStartupLongRtt(picoquic_bbr_state_t* bbr_state, picoquic_cnx_t* cnx, picoquic_path_t* path_x, bbr_per_ack_state_t* rs, uint64_t current_time) { if ((bbr_state->state == picoquic_bbr_alg_startup || bbr_state->state == picoquic_bbr_alg_startup_resume) && @@ -2136,9 +2150,11 @@ void BBRCheckStartupLongRtt(picoquic_bbr_state_t* bbr_state, picoquic_path_t* pa return; } - if (picoquic_cc_hystart_test(&bbr_state->rtt_filter, rs->rtt_sample, + if (bbr_state->hystart_alg == picoquic_hystart_alg_hystart_t && picoquic_cc_hystart_test(&bbr_state->rtt_filter, rs->rtt_sample, path_x->pacing.packet_time_microsec, current_time, 0)) { BBRExitStartupLongRtt(bbr_state, path_x, current_time); + } else if (bbr_state->hystart_alg == picoquic_hystart_alg_hystart_pp_t && picoquic_cc_hystart_pp_test(&bbr_state->hystart_pp_state, cnx, path_x, rs->rtt_sample)) { + BBRExitStartupLongRtt(bbr_state, path_x, current_time); } else if (rs->ecn_alpha > BBRExcessiveEcnCE) { BBRExitStartupLongRtt(bbr_state, path_x, current_time); @@ -2155,7 +2171,8 @@ void BBRCheckStartupLongRtt(picoquic_bbr_state_t* bbr_state, picoquic_path_t* pa void BBRUpdateStartupLongRtt(picoquic_bbr_state_t* bbr_state, picoquic_path_t* path_x, bbr_per_ack_state_t* rs, uint64_t current_time) { if (path_x->last_time_acked_data_frame_sent > path_x->last_sender_limited_time) { - path_x->cwin += picoquic_cc_slow_start_increase(path_x, rs->newly_acked); + path_x->cwin += picoquic_cc_slow_start_increase_ex(path_x, rs->newly_acked, + (IS_HYSTART_PP(bbr_state->hystart_alg)) ? IS_IN_CSS(bbr_state->hystart_pp_state) : 0); } uint64_t max_win = path_x->peak_bandwidth_estimate * bbr_state->min_rtt / 1000000; @@ -2308,12 +2325,12 @@ static void BBRAdvanceEcnFrac(picoquic_bbr_state_t* bbr_state, picoquic_path_t* /* BBRv3 per ACK steps * The function BBRUpdateOnACK is executed for each ACK notification on the API */ -static void BBRUpdateModelAndState(picoquic_bbr_state_t* bbr_state, picoquic_path_t* path_x, bbr_per_ack_state_t * rs, uint64_t current_time) +static void BBRUpdateModelAndState(picoquic_bbr_state_t* bbr_state, picoquic_cnx_t* cnx, picoquic_path_t* path_x, bbr_per_ack_state_t * rs, uint64_t current_time) { BBRUpdateLatestDeliverySignals(bbr_state, path_x, rs); BBRUpdateCongestionSignals(bbr_state, path_x, rs); BBRUpdateACKAggregation(bbr_state, path_x, rs, current_time); - BBRCheckStartupLongRtt(bbr_state, path_x, rs, current_time); + BBRCheckStartupLongRtt(bbr_state, cnx, path_x, rs, current_time); BBRCheckStartupResume(bbr_state, path_x, rs, current_time); BBRCheckStartupDone(bbr_state, path_x, rs, current_time); BBRCheckRecovery(bbr_state, path_x, rs, current_time); @@ -2333,9 +2350,9 @@ static void BBRUpdateControlParameters(picoquic_bbr_state_t* bbr_state, picoquic BBRSetCwnd(bbr_state, path_x, rs); } -void BBRUpdateOnACK(picoquic_bbr_state_t* bbr_state, picoquic_path_t* path_x, bbr_per_ack_state_t * rs, uint64_t current_time) +void BBRUpdateOnACK(picoquic_bbr_state_t* bbr_state, picoquic_cnx_t* cnx, picoquic_path_t* path_x, bbr_per_ack_state_t * rs, uint64_t current_time) { - BBRUpdateModelAndState(bbr_state, path_x, rs, current_time); + BBRUpdateModelAndState(bbr_state, cnx, path_x, rs, current_time); if (bbr_state->state == picoquic_bbr_alg_startup_long_rtt) { BBRUpdateStartupLongRtt(bbr_state, path_x, rs, current_time); } @@ -2388,6 +2405,7 @@ static void BBRSetRsFromAckState(picoquic_path_t* path_x, picoquic_per_ack_state static void picoquic_bbr_notify_ack( picoquic_bbr_state_t* bbr_state, + picoquic_cnx_t* cnx, picoquic_path_t* path_x, picoquic_per_ack_state_t* ack_state, uint64_t current_time) @@ -2395,7 +2413,7 @@ static void picoquic_bbr_notify_ack( bbr_per_ack_state_t rs = { 0 }; BBRSetRsFromAckState(path_x, ack_state, &rs); BBRComputeEcnFrac(bbr_state, path_x, &rs); - BBRUpdateOnACK(bbr_state, path_x, &rs, current_time); + BBRUpdateOnACK(bbr_state, cnx, path_x, &rs, current_time); } /* @@ -2440,7 +2458,7 @@ static void picoquic_bbr_notify( break; case picoquic_congestion_notification_acknowledgement: BBRExitLostFeedback(bbr_state, path_x); - picoquic_bbr_notify_ack(bbr_state, path_x, ack_state, current_time); + picoquic_bbr_notify_ack(bbr_state, cnx, path_x, ack_state, current_time); if (bbr_state->state == picoquic_bbr_alg_startup_long_rtt) { picoquic_update_pacing_data(cnx, path_x, 1); } @@ -2452,7 +2470,7 @@ static void picoquic_bbr_notify( case picoquic_congestion_notification_cwin_blocked: break; case picoquic_congestion_notification_reset: - picoquic_bbr_reset(bbr_state, path_x, current_time); + picoquic_bbr_reset(bbr_state, cnx, path_x, current_time); break; case picoquic_congestion_notification_seed_cwin: BBRSetBdpSeed(bbr_state, ack_state->nb_bytes_acknowledged); diff --git a/picoquic/bbr1.c b/picoquic/bbr1.c index b1e6b0f73..366729920 100644 --- a/picoquic/bbr1.c +++ b/picoquic/bbr1.c @@ -260,6 +260,9 @@ typedef struct st_picoquic_bbr1_state_t { uint64_t bytes_delivered; /* Number of bytes signalled in ACK notify, but not processed yet */ uint64_t send_quantum; picoquic_min_max_rtt_t rtt_filter; + /* HyStart++. */ + picoquic_hystart_alg_t hystart_alg; + picoquic_hystart_pp_state_t hystart_pp_state; uint64_t target_cwnd; double pacing_gain; double cwnd_gain; @@ -436,9 +439,12 @@ static void picoquic_bbr1_set_options(picoquic_bbr1_state_t* bbr1_state) break; } } - case ':': - /* Ignore */ + case 'Y': + /* Reading digits into an uint64_t */ + bbr1_state->hystart_alg = atoi(x); + x++; break; + case ':': /* Ignore. */ default: break; } @@ -446,9 +452,8 @@ static void picoquic_bbr1_set_options(picoquic_bbr1_state_t* bbr1_state) } } -static void picoquic_bbr1_reset(picoquic_bbr1_state_t* bbr1_state, picoquic_path_t* path_x, uint64_t current_time) +static void picoquic_bbr1_reset(picoquic_bbr1_state_t* bbr1_state, picoquic_cnx_t* cnx, picoquic_path_t* path_x, uint64_t current_time) { - memset(bbr1_state, 0, sizeof(picoquic_bbr1_state_t)); path_x->cwin = PICOQUIC_CWIN_INITIAL; bbr1_state->rt_prop = UINT64_MAX; picoquic_bbr1_set_options(bbr1_state); @@ -464,6 +469,12 @@ static void picoquic_bbr1_reset(picoquic_bbr1_state_t* bbr1_state, picoquic_path BBR1EnterStartup(bbr1_state); BBR1SetSendQuantum(bbr1_state, path_x); BBR1UpdateTargetCwnd(bbr1_state); + + /* HyStart++ */ + memset(&bbr1_state->hystart_pp_state, 0, sizeof(picoquic_hystart_pp_state_t)); + if (IS_HYSTART_PP(bbr1_state->hystart_alg)) { + picoquic_hystart_pp_reset(&bbr1_state->hystart_pp_state, cnx, path_x); + } } static void picoquic_bbr1_init(picoquic_cnx_t * cnx, picoquic_path_t* path_x, char const* option_string, uint64_t current_time) @@ -473,8 +484,9 @@ static void picoquic_bbr1_init(picoquic_cnx_t * cnx, picoquic_path_t* path_x, ch path_x->congestion_alg_state = (void*)bbr1_state; if (bbr1_state != NULL) { + memset(bbr1_state, 0, sizeof(picoquic_bbr1_state_t)); bbr1_state->option_string = option_string; - picoquic_bbr1_reset(bbr1_state, path_x, current_time); + picoquic_bbr1_reset(bbr1_state, cnx, path_x, current_time); } } @@ -1238,9 +1250,21 @@ static void picoquic_bbr1_notify( } if (bbr1_state->state == picoquic_bbr1_alg_startup_long_rtt) { - if (picoquic_cc_hystart_test(&bbr1_state->rtt_filter, (cnx->is_time_stamp_enabled) ? ack_state->one_way_delay : ack_state->rtt_measurement, - cnx->path[0]->pacing.packet_time_microsec, current_time, cnx->is_time_stamp_enabled)) { - BBR1ExitStartupLongRtt(bbr1_state, path_x, current_time); + switch (bbr1_state->hystart_alg) { + case picoquic_hystart_alg_hystart_t: + if (picoquic_cc_hystart_test(&bbr1_state->rtt_filter, (cnx->is_time_stamp_enabled) ? ack_state->one_way_delay : ack_state->rtt_measurement, + cnx->path[0]->pacing.packet_time_microsec, current_time, cnx->is_time_stamp_enabled)) { + BBR1ExitStartupLongRtt(bbr1_state, path_x, current_time); + } + break; + case picoquic_hystart_alg_hystart_pp_t: + if (picoquic_cc_hystart_pp_test(&bbr1_state->hystart_pp_state, cnx, path_x, ack_state->rtt_measurement)) { + BBR1ExitStartupLongRtt(bbr1_state, path_x, current_time); + } + break; + case picoquic_hystart_alg_disabled_t: + default: + break; } } @@ -1255,7 +1279,8 @@ static void picoquic_bbr1_notify( bbr1_state->rt_prop_stamp = current_time; } if (path_x->last_time_acked_data_frame_sent > path_x->last_sender_limited_time) { - path_x->cwin += picoquic_cc_slow_start_increase(path_x, bbr1_state->bytes_delivered); + path_x->cwin += picoquic_cc_slow_start_increase_ex(path_x, ack_state->nb_bytes_acknowledged, + (IS_HYSTART_PP(bbr1_state->hystart_alg)) ? IS_IN_CSS(bbr1_state->hystart_pp_state) : 0); } bbr1_state->bytes_delivered = 0; @@ -1288,7 +1313,7 @@ static void picoquic_bbr1_notify( case picoquic_congestion_notification_cwin_blocked: break; case picoquic_congestion_notification_reset: - picoquic_bbr1_reset(bbr1_state, path_x, current_time); + picoquic_bbr1_reset(bbr1_state, cnx, path_x, current_time); break; case picoquic_congestion_notification_seed_cwin: if (bbr1_state->state == picoquic_bbr1_alg_startup_long_rtt) { diff --git a/picoquic/cc_common.c b/picoquic/cc_common.c index fe7cf2342..490ec6a8c 100644 --- a/picoquic/cc_common.c +++ b/picoquic/cc_common.c @@ -205,6 +205,16 @@ uint64_t picoquic_cc_slow_start_increase(picoquic_path_t * path_x, uint64_t nb_d return nb_delivered; } +/** For each arriving ACK in slow start, where N is the number of previously unacknowledged bytes acknowledged in + * the arriving ACK: + * Update the cwnd: + * cwnd = cwnd + min(N, L * SMSS) + */ +/** For each arriving ACK in CSS, where N is the number of previously unacknowledged bytes acknowledged in the arriving + * ACK: + * Update the cwnd: + * cwnd = cwnd + (min(N, L * SMSS) / CSS_GROWTH_DIVISOR) + */ uint64_t picoquic_cc_slow_start_increase_ex(picoquic_path_t * path_x, uint64_t nb_delivered, int in_css) { if (in_css) { @@ -239,6 +249,136 @@ uint64_t picoquic_cc_slow_start_increase_ex2(picoquic_path_t* path_x, uint64_t n return picoquic_cc_slow_start_increase_ex(path_x, nb_delivered, in_css); } +/* + * HyStart++ + */ +/** lastRoundMinRTT and currentRoundMinRTT are initialized to infinity at the initialization time. currRTT is + * the RTT sampled from the latest incoming ACK and initialized to infinity. + * - lastRoundMinRTT = infinity + * - currentRoundMinRTT = infinity + * - currRTT = infinity + */ +void picoquic_hystart_pp_reset(picoquic_hystart_pp_state_t* hystart_pp_state, picoquic_cnx_t* cnx, picoquic_path_t* path_x) { + /* init round */ + hystart_pp_state->current_round.last_round_min_rtt = UINT64_MAX; + hystart_pp_state->current_round.current_round_min_rtt = UINT64_MAX; + //hystart_pp_state.curr_rtt = UINT64_MAX; + hystart_pp_state->current_round.rtt_sample_count = 0; + hystart_pp_state->current_round.window_end = 0; + + /* init state */ + //hystart_pp_state->rtt_thresh = UINT64_MAX; + hystart_pp_state->css_baseline_min_rtt = UINT64_MAX; + hystart_pp_state->css_round_count = 0; + + picoquic_hystart_pp_start_new_round(hystart_pp_state, cnx, path_x); +} + +/** At the start of each round during standard slow start [RFC5681] and CSS, initialize the variables used to + * compute the last round's and current round's minimum RTT: + * - lastRoundMinRTT = currentRoundMinRTT + * - currentRoundMinRTT = infinity + * - rttSampleCount = 0 + */ +/** HyStart++ measures rounds using sequence numbers, as follows: + * - Define windowEnd as a sequence number initialized to SND.NXT. + */ +void picoquic_hystart_pp_start_new_round(picoquic_hystart_pp_state_t* hystart_pp_state, picoquic_cnx_t* cnx, picoquic_path_t* path_x) { + hystart_pp_state->current_round.last_round_min_rtt = hystart_pp_state->current_round.current_round_min_rtt; + hystart_pp_state->current_round.current_round_min_rtt = UINT64_MAX; + hystart_pp_state->current_round.rtt_sample_count = 0; + + /* Set window end to next sent sequence number. */ + hystart_pp_state->current_round.window_end = picoquic_cc_get_sequence_number(cnx, path_x); +} + +/** For each arriving ACK in slow start, where N is the number of previously unacknowledged bytes acknowledged in + * the arriving ACK: + * Keep track of the minimum observed RTT: + * currentRoundMinRTT = min(currentRoundMinRTT, currRTT) + * rttSampleCount += 1 + */ +/** For each arriving ACK in CSS, where N is the number of previously unacknowledged bytes acknowledged in the arriving + * ACK: + * Keep track of the minimum observed RTT: + * currentRoundMinRTT = min(currentRoundMinRTT, currRTT) + * rttSampleCount += 1 + */ +void picoquic_hystart_pp_keep_track(picoquic_hystart_pp_state_t *hystart_pp_state, uint64_t rtt_measurement) { + hystart_pp_state->current_round.current_round_min_rtt = MIN(hystart_pp_state->current_round.current_round_min_rtt, rtt_measurement); + hystart_pp_state->current_round.rtt_sample_count++; +} + +/** For rounds where at least N_RTT_SAMPLE RTT samples have been obtained and currentRoundMinRTT and lastRoundMinRTT + * are valid, check to see if delay increase triggers slow start exit: + * if ((rttSampleCount >= N_RTT_SAMPLE) AND (currentRoundMinRTT != infinity) AND (lastRoundMinRTT != infinity)) + * RttThresh = max(MIN_RTT_THRESH, min(lastRoundMinRTT / MIN_RTT_DIVISOR, MAX_RTT_THRESH)) + * if (currentRoundMinRTT >= (lastRoundMinRTT + RttThresh)) + * cssBaselineMinRtt = currentRoundMinRTT + * exit slow start and enter CSS + */ +/** For CSS rounds where at least N_RTT_SAMPLE RTT samples have been obtained, check to see if the current round's + * minRTT drops below baseline (cssBaselineMinRtt) indicating that slow start exit was spurious: + * if (currentRoundMinRTT < cssBaselineMinRtt) + * cssBaselineMinRtt = infinity + * resume slow start including HyStart++ + */ +void picoquic_hystart_pp_test(picoquic_hystart_pp_state_t *hystart_pp_state) { + if (hystart_pp_state->css_baseline_min_rtt == UINT64_MAX) { + /* In slow start (SS) */ + if (hystart_pp_state->current_round.rtt_sample_count >= PICOQUIC_HYSTART_PP_N_RTT_SAMPLE && + hystart_pp_state->current_round.current_round_min_rtt != UINT64_MAX && + hystart_pp_state->current_round.last_round_min_rtt != UINT64_MAX) { + uint64_t rtt_thresh = MAX(PICOQUIC_HYSTART_PP_MIN_RTT_THRESH, MIN(hystart_pp_state->current_round.last_round_min_rtt / PICOQUIC_HYSTART_PP_MIN_RTT_DIVISOR, PICOQUIC_HYSTART_PP_MAX_RTT_THRESH)); + + if (hystart_pp_state->current_round.current_round_min_rtt >= (hystart_pp_state->current_round.last_round_min_rtt + rtt_thresh)) { + /* Exit slow start and enter CSS. */ + hystart_pp_state->css_baseline_min_rtt = hystart_pp_state->current_round.current_round_min_rtt; + } + } + } else { + /* In conservative slow start (CSS) */ + if (hystart_pp_state->current_round.rtt_sample_count >= PICOQUIC_HYSTART_PP_N_RTT_SAMPLE) { + if (hystart_pp_state->current_round.current_round_min_rtt < hystart_pp_state->css_baseline_min_rtt) { + /* Resume slow start including hystart++. */ + hystart_pp_state->css_baseline_min_rtt = UINT64_MAX; + } + } + } +} + +int picoquic_cc_hystart_pp_test(picoquic_hystart_pp_state_t* hystart_pp_state, picoquic_cnx_t* cnx, picoquic_path_t* path_x, uint64_t rtt_measurement) { + int ret = 0; + + /* Keep track of the minimum RTT seen so far. */ + picoquic_hystart_pp_keep_track(hystart_pp_state, rtt_measurement); + + /* Switch between SS and CSS. */ + picoquic_hystart_pp_test(hystart_pp_state); + + /* Check if we reached the end of the round. */ + /* HyStart++ measures rounds using sequence numbers, as follows: + * - When windowEnd is ACKed, the current round ends and windowEnd is set to SND.NXT. + */ + if (picoquic_cc_get_ack_number(cnx, path_x) != UINT64_MAX && picoquic_cc_get_ack_number(cnx, path_x) >= hystart_pp_state->current_round.window_end) { + /* Round has ended. */ + if (IS_IN_CSS((*hystart_pp_state))) { + /* In CSS increase CSS round counter. */ + hystart_pp_state->css_round_count++; + + /* Enter CA if css round counter > max css rounds. */ + if (hystart_pp_state->css_round_count >= PICOQUIC_HYSTART_PP_CSS_ROUNDS) { + ret = 1; + } + } + + /* Start new round. */ + picoquic_hystart_pp_start_new_round(hystart_pp_state, cnx, path_x); + } + + return ret; +} + uint64_t picoquic_cc_update_target_cwin_estimation(picoquic_path_t* path_x) { /* RTT measurements will happen after the bandwidth is estimated. */ uint64_t max_win = path_x->peak_bandwidth_estimate * path_x->smoothed_rtt / 1000000; diff --git a/picoquic/cc_common.h b/picoquic/cc_common.h index 4b8f19fa9..e9a1091c6 100644 --- a/picoquic/cc_common.h +++ b/picoquic/cc_common.h @@ -35,7 +35,6 @@ extern "C" { * HyStart++ */ -/* TODO HyStart++ isn't implemented yet! */ /* It is RECOMMENDED that a HyStart++ implementation use the following constants: */ /* MIN_RTT_THRESH = 4 msec * MAX_RTT_THRESH = 16 msec @@ -45,7 +44,7 @@ extern "C" { * CSS_ROUNDS = 5 * L = infinity if paced, L = 8 if non-paced */ -/* Take a look at the draft for more information. */ +/* Take a look at the RFC for more information. */ #define PICOQUIC_HYSTART_PP_MIN_RTT_THRESH 4000 /* msec */ #define PICOQUIC_HYSTART_PP_MAX_RTT_THRESH 16000 /* msec */ #define PICOQUIC_HYSTART_PP_MIN_RTT_DIVISOR 8 @@ -79,6 +78,21 @@ uint64_t picoquic_cc_get_ack_number(picoquic_cnx_t* cnx, picoquic_path_t * path_ uint64_t picoquic_cc_get_ack_sent_time(picoquic_cnx_t* cnx, picoquic_path_t* path_x); +/* + * Slow Start + * Returns number of bytes CWIN should be increased. + */ + +uint64_t picoquic_cc_slow_start_increase(picoquic_path_t* path_x, uint64_t nb_delivered); + +uint64_t picoquic_cc_slow_start_increase_ex(picoquic_path_t* path_x, uint64_t nb_delivered, int in_css); + +uint64_t picoquic_cc_slow_start_increase_ex2(picoquic_path_t* path_x, uint64_t nb_delivered, int in_css, uint64_t prague_alpha); + +/* + * HyStart + */ + void picoquic_cc_filter_rtt_min_max(picoquic_min_max_rtt_t* rtt_track, uint64_t rtt); int picoquic_cc_hystart_loss_test(picoquic_min_max_rtt_t* rtt_track, picoquic_congestion_notification_t event, uint64_t lost_packet_number, double error_rate_max); @@ -88,14 +102,37 @@ int picoquic_cc_hystart_loss_volume_test(picoquic_min_max_rtt_t* rtt_track, pico int picoquic_cc_hystart_test(picoquic_min_max_rtt_t* rtt_track, uint64_t rtt_measurement, uint64_t packet_time, uint64_t current_time, int is_one_way_delay_enabled); /* - * Slow Start - * Returns number of bytes CWIN should be increased. + * HyStart++ */ -uint64_t picoquic_cc_slow_start_increase(picoquic_path_t* path_x, uint64_t nb_delivered); -uint64_t picoquic_cc_slow_start_increase_ex(picoquic_path_t* path_x, uint64_t nb_delivered, int in_css); +#define IS_HYSTART_PP(hystart_alg) (hystart_alg == picoquic_hystart_alg_hystart_pp_t) +#define IS_IN_CSS(hystart_pp_state) (hystart_pp_state.css_baseline_min_rtt != UINT64_MAX) -uint64_t picoquic_cc_slow_start_increase_ex2(picoquic_path_t* path_x, uint64_t nb_delivered, int in_css, uint64_t prague_alpha); +typedef struct st_picoquic_hystart_pp_round_t { + uint64_t last_round_min_rtt; + uint64_t current_round_min_rtt; + //uint64_t curr_rtt; /* TODO check if needed */ + uint64_t rtt_sample_count; + uint64_t window_end; +} picoquic_hystart_pp_round_t; + +typedef struct st_picoquic_hystart_pp_state_t { + picoquic_hystart_pp_round_t current_round; + + uint64_t rtt_thresh; + uint64_t css_baseline_min_rtt; + uint64_t css_round_count; +} picoquic_hystart_pp_state_t; + +void picoquic_hystart_pp_reset(picoquic_hystart_pp_state_t* hystart_pp_state, picoquic_cnx_t* cnx, picoquic_path_t* path_x); + +void picoquic_hystart_pp_start_new_round(picoquic_hystart_pp_state_t* hystart_pp_state, picoquic_cnx_t* cnx, picoquic_path_t* path_x); + +void picoquic_hystart_pp_keep_track(picoquic_hystart_pp_state_t* hystart_pp_state, uint64_t rtt_measurement); + +void picoquic_hystart_pp_test(picoquic_hystart_pp_state_t* hystart_pp_state); + +int picoquic_cc_hystart_pp_test(picoquic_hystart_pp_state_t* hystart_pp_state, picoquic_cnx_t* cnx, picoquic_path_t* path_x, uint64_t rtt_measurement); /* * Returns CWIN based on bandwidth estimation if larger than current CWIN. Otherwise, returns current CWIN. @@ -105,7 +142,7 @@ uint64_t picoquic_cc_update_target_cwin_estimation(picoquic_path_t* path_x); /* * Returns CWIN for long RTT connections if larger than current CWIN. Otherwise, returns current CWIN. */ -uint64_t picoquic_cc_update_cwin_for_long_rtt(picoquic_path_t * path_x); +uint64_t picoquic_cc_update_cwin_for_long_rtt(picoquic_path_t* path_x); /* Many congestion control algorithms run a parallel version of new reno in order * to provide a lower bound estimate of either the congestion window or the diff --git a/picoquic/config.c b/picoquic/config.c index 35bc55b6f..641171cb7 100644 --- a/picoquic/config.c +++ b/picoquic/config.c @@ -471,8 +471,6 @@ static int config_set_option(option_table_line_t* option_desc, option_param_t* p } case picoquic_option_HELP: - ret = -1; - break; default: ret = -1; break; diff --git a/picoquic/cubic.c b/picoquic/cubic.c index d31aa0b7b..7efa6eb92 100644 --- a/picoquic/cubic.c +++ b/picoquic/cubic.c @@ -48,9 +48,37 @@ typedef struct st_picoquic_cubic_state_t { double W_reno; uint64_t ssthresh; picoquic_min_max_rtt_t rtt_filter; + picoquic_hystart_alg_t hystart_alg; + const char* option_string; + + /* HyStart++ */ + picoquic_hystart_pp_state_t hystart_pp_state; } picoquic_cubic_state_t; -static void cubic_reset(picoquic_cubic_state_t* cubic_state, picoquic_path_t* path_x, uint64_t current_time) { +static void cubic_set_options(picoquic_cubic_state_t* cubic_state) +{ + const char* x = cubic_state->option_string; + + if (x != NULL) { + char c; + while ((c = *x) != 0) { + x++; + switch (c) { + case 'Y': { + /* Reading digits into an uint64_t */ + cubic_state->hystart_alg = atoi(x); + x++; + break; + } + case ':': + default: + break; + } + } + } +} + +static void cubic_reset(picoquic_cubic_state_t* cubic_state, picoquic_cnx_t* cnx, picoquic_path_t* path_x, const char* option_string, uint64_t current_time) { memset(&cubic_state->rtt_filter, 0, sizeof(picoquic_min_max_rtt_t)); memset(cubic_state, 0, sizeof(picoquic_cubic_state_t)); path_x->cwin = PICOQUIC_CWIN_INITIAL; @@ -65,6 +93,14 @@ static void cubic_reset(picoquic_cubic_state_t* cubic_state, picoquic_path_t* pa cubic_state->previous_ssthresh = UINT64_MAX; cubic_state->W_reno = PICOQUIC_CWIN_INITIAL; cubic_state->recovery_sequence = 0; + cubic_state->option_string = option_string; + cubic_set_options(cubic_state); + + /* HyStart++ */ + memset(&cubic_state->hystart_pp_state, 0, sizeof(picoquic_hystart_pp_state_t)); + if (IS_HYSTART_PP(cubic_state->hystart_alg)) { + picoquic_hystart_pp_reset(&cubic_state->hystart_pp_state, cnx, path_x); + } } static void cubic_init(picoquic_cnx_t * cnx, picoquic_path_t* path_x, char const* option_string, uint64_t current_time) @@ -77,7 +113,7 @@ static void cubic_init(picoquic_cnx_t * cnx, picoquic_path_t* path_x, char const #endif path_x->congestion_alg_state = (void*)cubic_state; if (cubic_state != NULL) { - cubic_reset(cubic_state, path_x, current_time); + cubic_reset(cubic_state, cnx, path_x, option_string, current_time); } } @@ -155,8 +191,10 @@ static void cubic_enter_recovery(picoquic_cnx_t * cnx, cubic_state->previous_alg_state = cubic_state->alg_state; cubic_state->previous_ssthresh = cubic_state->ssthresh; cubic_state->previous_cwin = path_x->cwin; + /* Update similar to new reno, but different beta */ cubic_state->W_max = (double)path_x->cwin / (double)path_x->send_mtu; + /* Apply fast convergence */ if (cubic_state->W_max < cubic_state->W_last_max) { cubic_state->W_last_max = cubic_state->W_max; @@ -165,6 +203,7 @@ static void cubic_enter_recovery(picoquic_cnx_t * cnx, else { cubic_state->W_last_max = cubic_state->W_max; } + /* Compute the new ssthresh */ cubic_state->ssthresh = (uint64_t)(cubic_state->W_max * PICOQUIC_CUBIC_BETA_ECN * (double)path_x->send_mtu); if (cubic_state->ssthresh < PICOQUIC_CWIN_MINIMUM) { @@ -252,7 +291,7 @@ static void cubic_notify( if (cubic_state != NULL) { switch (notification) { - /* RTT measurements will happen before acknowledgement is signalled */ + /* RTT measurements will happen before acknowledgement is signaled */ case picoquic_congestion_notification_acknowledgement: switch (cubic_state->alg_state) { case picoquic_cubic_alg_slow_start: @@ -260,16 +299,17 @@ static void cubic_notify( path_x->cwin = picoquic_cc_update_target_cwin_estimation(path_x); if (path_x->last_time_acked_data_frame_sent > path_x->last_sender_limited_time) { - //if (path_x->bytes_in_transit > path_x->cwin) { - path_x->cwin += picoquic_cc_slow_start_increase_ex(path_x, ack_state->nb_bytes_acknowledged, 0); - - /* if cnx->cwin exceeds SSTHRESH, exit and go to CA */ - if (path_x->cwin >= cubic_state->ssthresh) { - cubic_state->W_reno = ((double)path_x->cwin) / 2.0; - path_x->is_ssthresh_initialized = 1; - cubic_enter_avoidance(cubic_state, current_time); - } - //} + /* cubic_state->hystart_pp_state.css_baseline_min_rtt == UINT64_MAX -> in SS + * cubic_state->hystart_pp_state.css_baseline_min_rtt < UINT64_MAX -> in CSS */ + path_x->cwin += picoquic_cc_slow_start_increase_ex(path_x, ack_state->nb_bytes_acknowledged, + (IS_HYSTART_PP(cubic_state->hystart_alg)) ? IS_IN_CSS(cubic_state->hystart_pp_state) : 0); + + /* if cnx->cwin exceeds SSTHRESH, exit and go to CA */ + if (path_x->cwin >= cubic_state->ssthresh) { + cubic_state->W_reno = ((double)path_x->cwin) / 2.0; + path_x->is_ssthresh_initialized = 1; + cubic_enter_avoidance(cubic_state, current_time); + } } break; /* TODO discuss @@ -348,46 +388,66 @@ static void cubic_notify( if (cubic_state->alg_state == picoquic_cubic_alg_slow_start && cubic_state->ssthresh == UINT64_MAX) { - /* HyStart. */ - /* Using RTT increases as signal to get out of initial slow start */ - if (picoquic_cc_hystart_test(&cubic_state->rtt_filter, (cnx->is_time_stamp_enabled) ? ack_state->one_way_delay : ack_state->rtt_measurement, - cnx->path[0]->pacing.packet_time_microsec, current_time, cnx->is_time_stamp_enabled)) { - /* RTT increased too much, get out of slow start! */ + switch (cubic_state->hystart_alg) { + case picoquic_hystart_alg_hystart_t: + /* HyStart. */ + /* Using RTT increases as signal to get out of initial slow start */ + if (picoquic_cc_hystart_test(&cubic_state->rtt_filter, (cnx->is_time_stamp_enabled) ? ack_state->one_way_delay : ack_state->rtt_measurement, + cnx->path[0]->pacing.packet_time_microsec, current_time, cnx->is_time_stamp_enabled)) { + /* RTT increased too much, get out of slow start! */ + + if (cubic_state->rtt_filter.rtt_filtered_min > PICOQUIC_TARGET_RENO_RTT){ + double correction; + if (cubic_state->rtt_filter.rtt_filtered_min > PICOQUIC_TARGET_SATELLITE_RTT) { + correction = (double)PICOQUIC_TARGET_SATELLITE_RTT / (double)cubic_state->rtt_filter.rtt_filtered_min; + } + else { + correction = (double)PICOQUIC_TARGET_RENO_RTT / (double)cubic_state->rtt_filter.rtt_filtered_min; + } + uint64_t base_window = (uint64_t)(correction * (double)path_x->cwin); + uint64_t delta_window = path_x->cwin - base_window; + path_x->cwin -= (delta_window / 2); + } + else { + /* In the general case, compensate for the growth of the window after the acknowledged packet was sent. */ + path_x->cwin /= 2; + } - if (cubic_state->rtt_filter.rtt_filtered_min > PICOQUIC_TARGET_RENO_RTT){ - double correction; - if (cubic_state->rtt_filter.rtt_filtered_min > PICOQUIC_TARGET_SATELLITE_RTT) { - correction = (double)PICOQUIC_TARGET_SATELLITE_RTT / (double)cubic_state->rtt_filter.rtt_filtered_min; + cubic_state->ssthresh = path_x->cwin; + cubic_state->W_max = (double)path_x->cwin / (double)path_x->send_mtu; + cubic_state->W_last_max = cubic_state->W_max; + cubic_state->W_reno = ((double)path_x->cwin); + path_x->is_ssthresh_initialized = 1; + /* enter recovery to ignore the losses expected if the window grew + * too large after the acknowleded packet was sent. */ + cubic_enter_recovery(cnx, path_x, notification, cubic_state, current_time); + /* apply a correction to enter the test phase immediately */ + uint64_t K_micro = (uint64_t)(cubic_state->K * 1000000.0); + if (K_micro > current_time) { + cubic_state->K = ((double)current_time) / 1000000.0; + cubic_state->start_of_epoch = 0; + } + else { + cubic_state->start_of_epoch = current_time - K_micro; + } } - else { - correction = (double)PICOQUIC_TARGET_RENO_RTT / (double)cubic_state->rtt_filter.rtt_filtered_min; + break; + case picoquic_hystart_alg_hystart_pp_t: + /* HyStart++. */ + if (picoquic_cc_hystart_pp_test(&cubic_state->hystart_pp_state, cnx, path_x, ack_state->rtt_measurement)) { + /* Enter CA. */ + cubic_state->ssthresh = path_x->cwin; + cubic_state->W_max = (double)path_x->cwin / (double)path_x->send_mtu; + cubic_state->W_last_max = cubic_state->W_max; + cubic_state->W_reno = ((double)path_x->cwin); + path_x->is_ssthresh_initialized = 1; + cubic_enter_avoidance(cubic_state, current_time); } - uint64_t base_window = (uint64_t)(correction * (double)path_x->cwin); - uint64_t delta_window = path_x->cwin - base_window; - path_x->cwin -= (delta_window / 2); - } - else { - /* In the general case, compensate for the growth of the window after the acknowledged packet was sent. */ - path_x->cwin /= 2; - } - - cubic_state->ssthresh = path_x->cwin; - cubic_state->W_max = (double)path_x->cwin / (double)path_x->send_mtu; - cubic_state->W_last_max = cubic_state->W_max; - cubic_state->W_reno = ((double)path_x->cwin); - path_x->is_ssthresh_initialized = 1; - /* enter recovery to ignore the losses expected if the window grew - * too large after the acknowleded packet was sent. */ - cubic_enter_recovery(cnx, path_x, notification, cubic_state, current_time); - /* apply a correction to enter the test phase immediately */ - uint64_t K_micro = (uint64_t)(cubic_state->K * 1000000.0); - if (K_micro > current_time) { - cubic_state->K = ((double)current_time) / 1000000.0; - cubic_state->start_of_epoch = 0; - } - else { - cubic_state->start_of_epoch = current_time - K_micro; - } + break; + case picoquic_hystart_alg_disabled_t: + /* do nothing. */ + default: + break; } } break; @@ -410,7 +470,7 @@ static void cubic_notify( * cover cubic_reset(). */ case picoquic_congestion_notification_reset: - cubic_reset(cubic_state, path_x, current_time); + cubic_reset(cubic_state, cnx, path_x, cubic_state->option_string, current_time); break; default: break; @@ -459,7 +519,7 @@ static void dcubic_exit_slow_start( /* * Define delay-based Cubic, dcubic, and alternative congestion control protocol similar to Cubic but * using delay measurements instead of reacting to packet losses. This is a quic hack, intended for - * trials of a lossy satellite networks. + * trials of lossy satellite networks. */ static void dcubic_notify( picoquic_cnx_t* cnx, picoquic_path_t* path_x, @@ -500,13 +560,33 @@ static void dcubic_notify( path_x->cwin = picoquic_cc_update_cwin_for_long_rtt(path_x); } - /* HyStart. */ - /* Using RTT increases as congestion signal. This is used - * for getting out of slow start, but also for ending a cycle - * during congestion avoidance */ - if (picoquic_cc_hystart_test(&cubic_state->rtt_filter, (cnx->is_time_stamp_enabled) ? ack_state->one_way_delay : ack_state->rtt_measurement, - cnx->path[0]->pacing.packet_time_microsec, current_time, cnx->is_time_stamp_enabled)) { - dcubic_exit_slow_start(cnx, path_x, notification, cubic_state, current_time); + switch (cubic_state->hystart_alg) { + case picoquic_hystart_alg_hystart_t: + /* HyStart. */ + /* Using RTT increases as congestion signal. This is used + * for getting out of slow start, but also for ending a cycle + * during congestion avoidance */ + if (picoquic_cc_hystart_test(&cubic_state->rtt_filter, (cnx->is_time_stamp_enabled) ? ack_state->one_way_delay : ack_state->rtt_measurement, + cnx->path[0]->pacing.packet_time_microsec, current_time, cnx->is_time_stamp_enabled)) { + dcubic_exit_slow_start(cnx, path_x, notification, cubic_state, current_time); + } + break; + case picoquic_hystart_alg_hystart_pp_t: + /* HyStart++. */ + if (picoquic_cc_hystart_pp_test(&cubic_state->hystart_pp_state, cnx, path_x, ack_state->rtt_measurement)) { + /* Enter CA. */ + cubic_state->ssthresh = path_x->cwin; + cubic_state->W_max = (double)path_x->cwin / (double)path_x->send_mtu; + cubic_state->W_last_max = cubic_state->W_max; + cubic_state->W_reno = ((double)path_x->cwin); + path_x->is_ssthresh_initialized = 1; + dcubic_exit_slow_start(cnx, path_x, notification, cubic_state, current_time); + } + break; + case picoquic_hystart_alg_disabled_t: + /* do nothing. */ + default: + break; } break; case picoquic_cubic_alg_recovery: @@ -531,8 +611,6 @@ static void dcubic_notify( } break; case picoquic_congestion_notification_spurious_repeat: - /* In contrast to Cubic, do nothing here */ - break; case picoquic_congestion_notification_ecn_ec: /* In contrast to Cubic, do nothing here */ break; diff --git a/picoquic/newreno.c b/picoquic/newreno.c index a1a0f5a1f..babedd46a 100644 --- a/picoquic/newreno.c +++ b/picoquic/newreno.c @@ -177,12 +177,49 @@ void picoquic_newreno_sim_notify( typedef struct st_picoquic_newreno_state_t { picoquic_newreno_sim_state_t nrss; picoquic_min_max_rtt_t rtt_filter; + picoquic_hystart_alg_t hystart_alg; + const char* option_string; + + /* HyStart++. */ + picoquic_hystart_pp_state_t hystart_pp_state; } picoquic_newreno_state_t; -static void picoquic_newreno_reset(picoquic_newreno_state_t* nr_state, picoquic_path_t* path_x) +static void newreno_set_options(picoquic_newreno_state_t* nr_state) +{ + const char* x = nr_state->option_string; + + if (x != NULL) { + char c; + while ((c = *x) != 0) { + x++; + switch (c) { + case 'Y': { + /* Reading digits into an uint64_t */ + nr_state->hystart_alg = atoi(x); + x++; + break; + } + case ':': + default: + break; + } + } + } +} + +static void picoquic_newreno_reset(picoquic_newreno_state_t* nr_state, picoquic_cnx_t* cnx, picoquic_path_t* path_x, char const *option_string) { memset(nr_state, 0, sizeof(picoquic_newreno_state_t)); picoquic_newreno_sim_reset(&nr_state->nrss); + nr_state->option_string = option_string; + newreno_set_options(nr_state); + + /* HyStart++. */ + memset(&nr_state->hystart_pp_state, 0, sizeof(nr_state->hystart_pp_state)); + if (IS_HYSTART_PP(nr_state->hystart_alg)) { + picoquic_hystart_pp_reset(&nr_state->hystart_pp_state, cnx, path_x); + } + path_x->cwin = nr_state->nrss.cwin; } @@ -197,7 +234,7 @@ static void picoquic_newreno_init(picoquic_cnx_t * cnx, picoquic_path_t* path_x, #endif if (nr_state != NULL) { - picoquic_newreno_reset(nr_state, path_x); + picoquic_newreno_reset(nr_state, cnx, path_x, option_string); path_x->congestion_alg_state = nr_state; } else { @@ -235,8 +272,19 @@ static void picoquic_newreno_notify( if (path_x->last_time_acked_data_frame_sent > path_x->last_sender_limited_time) { /* TODO app limited. */ - picoquic_newreno_sim_notify(&nr_state->nrss, cnx, path_x, notification, ack_state, current_time); - path_x->cwin = nr_state->nrss.cwin; + /* TODO CSS increase. */ + if (IS_HYSTART_PP(nr_state->hystart_alg)) { + path_x->cwin += picoquic_cc_slow_start_increase_ex(path_x, ack_state->nb_bytes_acknowledged, + (IS_HYSTART_PP(nr_state->hystart_alg)) ? IS_IN_CSS(nr_state->hystart_pp_state) : 0); + nr_state->nrss.cwin = path_x->cwin; + + if (nr_state->nrss.cwin >= nr_state->nrss.ssthresh) { + nr_state->nrss.alg_state = picoquic_newreno_alg_congestion_avoidance; + } + } else { + picoquic_newreno_sim_notify(&nr_state->nrss, cnx, path_x, notification, ack_state, current_time); + path_x->cwin = nr_state->nrss.cwin; + } } break; case picoquic_congestion_notification_seed_cwin: @@ -272,18 +320,36 @@ static void picoquic_newreno_notify( /* HyStart. */ /* Using RTT increases as signal to get out of initial slow start */ - if (picoquic_cc_hystart_test(&nr_state->rtt_filter, (cnx->is_time_stamp_enabled) ? ack_state->one_way_delay : ack_state->rtt_measurement, - cnx->path[0]->pacing.packet_time_microsec, current_time, cnx->is_time_stamp_enabled)) { - /* RTT increased too much, get out of slow start! */ - nr_state->nrss.ssthresh = nr_state->nrss.cwin; - nr_state->nrss.alg_state = picoquic_newreno_alg_congestion_avoidance; - path_x->cwin = nr_state->nrss.cwin; - path_x->is_ssthresh_initialized = 1; + switch (nr_state->hystart_alg) { + case picoquic_hystart_alg_hystart_t: + if (picoquic_cc_hystart_test(&nr_state->rtt_filter, (cnx->is_time_stamp_enabled) ? ack_state->one_way_delay : ack_state->rtt_measurement, + cnx->path[0]->pacing.packet_time_microsec, current_time, cnx->is_time_stamp_enabled)) { + /* RTT increased too much, get out of slow start! */ + nr_state->nrss.ssthresh = nr_state->nrss.cwin; + nr_state->nrss.alg_state = picoquic_newreno_alg_congestion_avoidance; + path_x->cwin = nr_state->nrss.cwin; + path_x->is_ssthresh_initialized = 1; + } + break; + case picoquic_hystart_alg_hystart_pp_t: + /* HyStart++. */ + if (picoquic_cc_hystart_pp_test(&nr_state->hystart_pp_state, cnx, path_x, ack_state->rtt_measurement)) { + /* Enter CA. */ + nr_state->nrss.ssthresh = nr_state->nrss.cwin; + nr_state->nrss.alg_state = picoquic_newreno_alg_congestion_avoidance; + path_x->cwin = nr_state->nrss.cwin; + path_x->is_ssthresh_initialized = 1; + } + break; + case picoquic_hystart_alg_disabled_t: + /* do nothing. */ + default: + break; } } break; case picoquic_congestion_notification_reset: - picoquic_newreno_reset(nr_state, path_x); + picoquic_newreno_reset(nr_state, cnx, path_x, nr_state->option_string); break; default: /* ignore */ diff --git a/picoquic/pacing.c b/picoquic/pacing.c index edcfe30ce..631c0144e 100644 --- a/picoquic/pacing.c +++ b/picoquic/pacing.c @@ -190,7 +190,7 @@ void picoquic_update_pacing_window(picoquic_pacing_t * pacing, int slow_start, u uint64_t rtt_nanosec = smoothed_rtt * 1000; if ((cwin < ((uint64_t)send_mtu) * 8) || rtt_nanosec <= 1000) { - /* Small windows, should only relie on ACK clocking */ + /* Small windows, should only rely on ACK clocking */ pacing->bucket_max = rtt_nanosec; pacing->packet_time_nanosec = 1; pacing->packet_time_microsec = 1; diff --git a/picoquic/picoquic.h b/picoquic/picoquic.h index 547d6a4c4..8ca991d37 100644 --- a/picoquic/picoquic.h +++ b/picoquic/picoquic.h @@ -206,6 +206,7 @@ typedef enum { picoquic_pmtud_delayed = 2, /* only do pmtud if lots of data has to be sent */ picoquic_pmtud_blocked = 3 /* never do pmtud */ } picoquic_pmtud_policy_enum; + /* * Quic spin bit variants */ @@ -1576,6 +1577,18 @@ typedef enum { picoquic_congestion_notification_lost_feedback /* notification of lost feedback */ } picoquic_congestion_notification_t; +/** HyStart algorithms: + * - HyStart (https://doi.org/10.1016/j.comnet.2011.01.014) + * - HyStart++ (RFC 9406) + * - disabled (Slow Start) + */ + +typedef enum { + picoquic_hystart_alg_hystart_t = 0, + picoquic_hystart_alg_hystart_pp_t, + picoquic_hystart_alg_disabled_t +} picoquic_hystart_alg_t; + typedef struct st_picoquic_per_ack_state_t { uint64_t rtt_measurement; /* RTT as measured when receiving the ACK */ uint64_t one_way_delay; /* One way delay when receiving the ACK, 0 if unknown */ diff --git a/picoquic/picoquic_internal.h b/picoquic/picoquic_internal.h index e0a23f16f..007a3b533 100644 --- a/picoquic/picoquic_internal.h +++ b/picoquic/picoquic_internal.h @@ -1025,7 +1025,7 @@ typedef struct st_picoquic_pacing_t { * On the client side, they are placed in "validated" or "backup" state by * local interactions. On the server side, they move from "backup" to * "validated" when the client starts using them. -* +* * The tuple context contains the data necessary for managing the challenge/response. */ typedef struct st_picoquic_tuple_t { @@ -1611,7 +1611,7 @@ uint8_t* picoquic_prepare_path_challenge_frames(picoquic_cnx_t* cnx, picoquic_pa picoquic_packet_context_enum pc, int is_nominal_ack_path, uint8_t* bytes_next, uint8_t* bytes_max, int* more_data, int* is_pure_ack, int* is_challenge_padding_needed, - uint64_t current_time, uint64_t* next_wake_time); + uint64_t current_time, uint64_t* next_wake_time); void picoquic_select_next_path_tuple(picoquic_cnx_t* cnx, uint64_t current_time, uint64_t* next_wake_time, picoquic_path_t** next_path, picoquic_tuple_t** next_tuple); int picoquic_renew_connection_id(picoquic_cnx_t* cnx, int path_id); @@ -1771,7 +1771,7 @@ size_t picoquic_get_checksum_length(picoquic_cnx_t* cnx, picoquic_epoch_enum is_ void picoquic_protect_packet_header(uint8_t* send_buffer, size_t pn_offset, uint8_t first_mask, void* pn_enc); -size_t picoquic_protect_packet(picoquic_cnx_t* cnx, picoquic_packet_type_enum ptype, uint8_t* bytes, uint64_t sequence_number, size_t length, size_t header_length, uint8_t* send_buffer, size_t send_buffer_max, void* aead_context, void* pn_enc, +size_t picoquic_protect_packet(picoquic_cnx_t* cnx, picoquic_packet_type_enum ptype, uint8_t* bytes, uint64_t sequence_number, size_t length, size_t header_length, uint8_t* send_buffer, size_t send_buffer_max, void* aead_context, void* pn_enc, picoquic_path_t* path_x, picoquic_tuple_t* tuple, uint64_t current_time); uint64_t picoquic_get_packet_number64(uint64_t highest, uint64_t mask, uint32_t pn); diff --git a/picoquic/picoquic_utils.h b/picoquic/picoquic_utils.h index bc311d684..a2bdc0845 100644 --- a/picoquic/picoquic_utils.h +++ b/picoquic/picoquic_utils.h @@ -118,6 +118,10 @@ int picoquic_get_input_path(char * target_file_path, size_t file_path_max, const #define MAX(a, b) ((a) > (b) ? (a) : (b)) #endif +#ifndef MIN +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + #ifdef _WINDOWS #define PICOQUIC_FILE_SEPARATOR "\\" #ifdef _WINDOWS64 diff --git a/picoquic/prague.c b/picoquic/prague.c index e08265b1f..fdd730247 100644 --- a/picoquic/prague.c +++ b/picoquic/prague.c @@ -111,13 +111,50 @@ typedef struct st_picoquic_prague_state_t { uint64_t l4s_epoch_ect1; uint64_t l4s_epoch_ce; picoquic_min_max_rtt_t rtt_filter; + picoquic_hystart_alg_t hystart_alg; + const char* option_string; + + /* HyStart++ */ + picoquic_hystart_pp_state_t hystart_pp_state; } picoquic_prague_state_t; -static void picoquic_prague_init_reno(picoquic_prague_state_t* pr_state, picoquic_path_t* path_x) +static void prague_set_options(picoquic_prague_state_t* pr_state) +{ + const char* x = pr_state->option_string; + + if (x != NULL) { + char c; + while ((c = *x) != 0) { + x++; + switch (c) { + case 'Y': { + /* Reading digits into an uint64_t */ + pr_state->hystart_alg = atoi(x); + x++; + break; + } + case ':': + default: + break; + } + } + } +} + +static void picoquic_prague_init_reno(picoquic_prague_state_t* pr_state, picoquic_cnx_t* cnx, picoquic_path_t* path_x, char const* option_string) { pr_state->alg_state = picoquic_prague_alg_slow_start; pr_state->ssthresh = UINT64_MAX; pr_state->alpha = 0; + pr_state->option_string = option_string; + prague_set_options(pr_state); + + /* HyStart++. */ + memset(&pr_state->hystart_pp_state, 0, sizeof(pr_state->hystart_pp_state)); + if (IS_HYSTART_PP(pr_state->hystart_alg)) { + picoquic_hystart_pp_reset(&pr_state->hystart_pp_state, cnx, path_x); + } + path_x->cwin = PICOQUIC_CWIN_INITIAL; } @@ -133,7 +170,7 @@ void picoquic_prague_init(picoquic_cnx_t * cnx, picoquic_path_t* path_x, char co if (pr_state != NULL) { memset(pr_state, 0, sizeof(picoquic_prague_state_t)); path_x->congestion_alg_state = (void*)pr_state; - picoquic_prague_init_reno(pr_state, path_x); + picoquic_prague_init_reno(pr_state, cnx, path_x, option_string); } else { path_x->congestion_alg_state = NULL; @@ -165,9 +202,9 @@ static void picoquic_prague_reset_l3s(picoquic_cnx_t* cnx, picoquic_prague_state } -static void picoquic_prague_reset(picoquic_cnx_t * cnx, picoquic_prague_state_t* pr_state, picoquic_path_t* path_x) +static void picoquic_prague_reset(picoquic_cnx_t * cnx, picoquic_prague_state_t* pr_state, picoquic_path_t* path_x, const char* option_string) { - picoquic_prague_init_reno(pr_state, path_x); + picoquic_prague_init_reno(pr_state, cnx, path_x, option_string); picoquic_prague_reset_l3s(cnx, pr_state, path_x); } @@ -305,7 +342,7 @@ void picoquic_prague_notify( case picoquic_prague_alg_slow_start: /* TODO l4s_prague test fails. Have to increase max_completion time about 100 ms */ if (path_x->last_time_acked_data_frame_sent > path_x->last_sender_limited_time) { - path_x->cwin += picoquic_cc_slow_start_increase_ex2(path_x, ack_state->nb_bytes_acknowledged, 0, pr_state->alpha); + path_x->cwin += picoquic_cc_slow_start_increase_ex2(path_x, ack_state->nb_bytes_acknowledged, (IS_HYSTART_PP(pr_state->hystart_alg)) ? IS_IN_CSS(pr_state->hystart_pp_state) : 0, pr_state->alpha); /* if cnx->cwin exceeds SSTHRESH, exit and go to CA */ if (path_x->cwin >= pr_state->ssthresh) { @@ -364,20 +401,37 @@ void picoquic_prague_notify( path_x->cwin = picoquic_cc_update_cwin_for_long_rtt(path_x); } - /* HyStart. */ - /* Using RTT increases as signal to get out of initial slow start */ - if (picoquic_cc_hystart_test(&pr_state->rtt_filter, (cnx->is_time_stamp_enabled) ? ack_state->one_way_delay : ack_state->rtt_measurement, - cnx->path[0]->pacing.packet_time_microsec, current_time, - cnx->is_time_stamp_enabled)) { - /* RTT increased too much, get out of slow start! */ - pr_state->ssthresh = path_x->cwin; - pr_state->alg_state = picoquic_prague_alg_congestion_avoidance; - path_x->is_ssthresh_initialized = 1; + switch (pr_state->hystart_alg) { + case picoquic_hystart_alg_hystart_t: + /* HyStart. */ + /* Using RTT increases as signal to get out of initial slow start */ + if (picoquic_cc_hystart_test(&pr_state->rtt_filter, (cnx->is_time_stamp_enabled) ? ack_state->one_way_delay : ack_state->rtt_measurement, + cnx->path[0]->pacing.packet_time_microsec, current_time, + cnx->is_time_stamp_enabled)) { + /* RTT increased too much, get out of slow start! */ + pr_state->ssthresh = path_x->cwin; + pr_state->alg_state = picoquic_prague_alg_congestion_avoidance; + path_x->is_ssthresh_initialized = 1; + } + break; + case picoquic_hystart_alg_hystart_pp_t: + /* HyStart++. */ + if (picoquic_cc_hystart_pp_test(&pr_state->hystart_pp_state, cnx, path_x, ack_state->rtt_measurement)) { + /* Enter CA. */ + pr_state->ssthresh = path_x->cwin; + pr_state->alg_state = picoquic_prague_alg_congestion_avoidance; + path_x->is_ssthresh_initialized = 1; + } + break; + case picoquic_hystart_alg_disabled_t: + /* do nothing. */ + default: + break; } } break; case picoquic_congestion_notification_reset: - picoquic_prague_reset(cnx, pr_state, path_x); + picoquic_prague_reset(cnx, pr_state, path_x, pr_state->option_string); break; default: /* ignore */ diff --git a/picoquic_t/picoquic_t.c b/picoquic_t/picoquic_t.c index a0f9fc682..425ac4d14 100644 --- a/picoquic_t/picoquic_t.c +++ b/picoquic_t/picoquic_t.c @@ -530,8 +530,8 @@ static const picoquic_test_def_t test_table[] = { { "config_option", config_option_test }, { "config_option_letters", config_option_letters_test }, { "config_quic", config_quic_test }, - { "config_usage", config_usage_test } - + { "config_usage", config_usage_test }, + {"hystart", hystart_test }, }; static size_t const nb_tests = sizeof(test_table) / sizeof(picoquic_test_def_t); diff --git a/picoquictest/cc_compete_test.c b/picoquictest/cc_compete_test.c index 9efd39682..822442dae 100644 --- a/picoquictest/cc_compete_test.c +++ b/picoquictest/cc_compete_test.c @@ -80,6 +80,26 @@ int cc_compete_cubic2_test() return picoquic_ns(&spec, NULL); } +int cc_compete_cubic2_hystart_pp_test() +{ + picoquic_ns_spec_t spec = { 0 }; + picoquic_connection_id_t icid = { { 0xcc, 0xc0, 0xcb, 0xcb, 0, 0, 0, 0}, 8 }; + spec.main_cc_algo = picoquic_cubic_algorithm; + spec.main_cc_options = "Y1"; + spec.main_start_time = 0; + spec.main_scenario_text = cc_compete_batch_scenario_4M; + spec.background_cc_algo = picoquic_cubic_algorithm; + spec.background_start_time = 0; + spec.background_scenario_text = cc_compete_batch_scenario_10M; + spec.nb_connections = 2; + spec.main_target_time = 8500000; + spec.queue_delay_max = 40000; + spec.icid = icid; + spec.qlog_dir = "."; + + return picoquic_ns(&spec, NULL); +} + int cc_compete_prague2_test() { picoquic_ns_spec_t spec = { 0 }; @@ -133,7 +153,7 @@ int cc_compete_d_cubic_test() /* Check that the picoquic_ns simulations can correctly test asymmetric paths. */ int cc_ns_asym_test() - { +{ picoquic_ns_spec_t spec = { 0 }; picoquic_connection_id_t icid = { { 0xcc, 0xa5, 0xcb, 0, 0, 0, 0, 0}, 8 }; spec.main_cc_algo = picoquic_cubic_algorithm; diff --git a/picoquictest/hystart_test.c b/picoquictest/hystart_test.c new file mode 100644 index 000000000..6f64f0d78 --- /dev/null +++ b/picoquictest/hystart_test.c @@ -0,0 +1,138 @@ +/* +* Author: Matthias Hofstaetter +* Copyright (c) 2025, Matthias Hofstaetter +* 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. +*/ + +#include "picoquic_internal.h" +#include "picoquic_utils.h" +#include "tls_api.h" +#include "picoquictest_internal.h" +#ifdef _WINDOWS +#include "wincompat.h" +#endif +#include +#include "picoquic_binlog.h" +#include "autoqlog.h" +#include "picoquictest.h" +#include "picoquic_bbr.h" +#include "picoquic_bbr1.h" +#include "picoquic_cubic.h" +#include "picoquic_fastcc.h" +#include "picoquic_newreno.h" +#include "picoquic_prague.h" + +static int hystart_test_one(picoquic_congestion_algorithm_t* ccalgo, picoquic_hystart_alg_t hystart_algo, size_t data_size, uint64_t max_completion_time, + uint64_t datarate, uint64_t latency, uint64_t jitter, uint64_t queue_delay_max) +{ + uint64_t simulated_time = 0; + uint64_t picoseq_per_byte = (1000000ull * 8) / datarate; + picoquic_connection_id_t initial_cid = { {0x08, 0x22, 0, 0, 0, 0, 0, 0}, 8 }; + picoquic_test_tls_api_ctx_t* test_ctx = NULL; + int ret = 0; + + initial_cid.id[2] = ccalgo->congestion_algorithm_number; + initial_cid.id[3] = hystart_algo; + initial_cid.id[4] = (datarate > 0xff) ? 0xff : (uint8_t)datarate; + initial_cid.id[5] = (latency > 2550000) ? 0xff : (uint8_t)(latency / 10000); + initial_cid.id[6] = (jitter > 255000) ? 0xff : (uint8_t)(jitter / 1000); + initial_cid.id[7] = (queue_delay_max > 255000) ? 0xff : (uint8_t)(queue_delay_max / 1000); + + ret = tls_api_one_scenario_init_ex(&test_ctx, &simulated_time, PICOQUIC_INTERNAL_TEST_VERSION_1, NULL, NULL, &initial_cid, 0); + + if (ret == 0 && test_ctx == NULL) { + ret = -1; + } + + if (ret == 0) { + /* Set CC algo. */ + const char* option_string = "Y0"; + switch (hystart_algo) { + case picoquic_hystart_alg_hystart_pp_t: + option_string = "Y1"; + break; + case picoquic_hystart_alg_disabled_t: + option_string = "Y2"; + break; + default: + break; + } + picoquic_set_default_congestion_algorithm_ex(test_ctx->qserver, ccalgo, option_string); + picoquic_set_congestion_algorithm_ex(test_ctx->cnx_client, ccalgo, option_string); + + /* Configure links. */ + test_ctx->c_to_s_link->jitter = jitter; + test_ctx->c_to_s_link->microsec_latency = latency; + test_ctx->c_to_s_link->picosec_per_byte = picoseq_per_byte; + test_ctx->s_to_c_link->jitter = jitter; + test_ctx->s_to_c_link->microsec_latency = latency; + test_ctx->s_to_c_link->picosec_per_byte = picoseq_per_byte; + test_ctx->stream0_flow_release = 1; + test_ctx->immediate_exit = 1; + + /* set the binary log on the client side */ + picoquic_set_qlog(test_ctx->qclient, "."); + test_ctx->qclient->use_long_log = 1; + /* Since the client connection was created before the binlog was set, force log of connection header */ + binlog_new_connection(test_ctx->cnx_client); + + ret = tls_api_one_scenario_body(test_ctx, &simulated_time, + NULL, 0, data_size, 0, 0, queue_delay_max, max_completion_time); + } + + /* Free the resource, which will close the log file. */ + if (test_ctx != NULL) { + tls_api_delete_ctx(test_ctx); + test_ctx = NULL; + } + + return ret; +} + +int hystart_test() { + picoquic_congestion_algorithm_t* ccalgos[] = { + picoquic_newreno_algorithm, + picoquic_cubic_algorithm, + picoquic_dcubic_algorithm, + //picoquic_fastcc_algorithm, + picoquic_bbr_algorithm, + picoquic_prague_algorithm, + picoquic_bbr1_algorithm + }; + uint64_t max_completion_times[] = { + 10000000, + 10500000, + 10500000, + //21000, + 10000000, + 10000000, + 10000000 + }; + int ret = 0; + + for (size_t i = 0; i < sizeof(ccalgos) / sizeof(picoquic_congestion_algorithm_t*) && !ret; i++) { + for (picoquic_hystart_alg_t hystart_alg = picoquic_hystart_alg_hystart_t; hystart_alg <= picoquic_hystart_alg_disabled_t && !ret; hystart_alg++) { + ret = hystart_test_one(ccalgos[i], hystart_alg, 50000000, max_completion_times[i], 50, 125000, 0, 125000 * 10); + if (ret != 0) { + DBG_PRINTF("HyStart test fails for <%s><%i>", ccalgos[i]->congestion_algorithm_id, hystart_alg); + } + } + } + + return ret; +} \ No newline at end of file diff --git a/picoquictest/picoquictest.h b/picoquictest/picoquictest.h index 71f93235b..98dede2b2 100644 --- a/picoquictest/picoquictest.h +++ b/picoquictest/picoquictest.h @@ -276,6 +276,7 @@ int crypto_hs_offset_test(); int cubic_test(); int cubic_jitter_test(); int cc_compete_cubic2_test(); +int cc_compete_cubic2_hystart_pp_test(); int cc_compete_prague2_test(); int cc_compete_d_cubic_test(); int cc_ns_asym_test(); @@ -601,6 +602,7 @@ int quicperf_media_test(); int quicperf_multi_test(); int quicperf_overflow_test(); int cplusplustest(); +int hystart_test(); #ifdef __cplusplus } diff --git a/picoquictest/picoquictest.vcxproj b/picoquictest/picoquictest.vcxproj index 2b783936e..523c25134 100644 --- a/picoquictest/picoquictest.vcxproj +++ b/picoquictest/picoquictest.vcxproj @@ -172,6 +172,7 @@ + diff --git a/picoquictest/picoquictest.vcxproj.filters b/picoquictest/picoquictest.vcxproj.filters index 000aacb5b..41cbd2ed5 100644 --- a/picoquictest/picoquictest.vcxproj.filters +++ b/picoquictest/picoquictest.vcxproj.filters @@ -201,6 +201,9 @@ Source Files + + Source Files + Source Files