/* Copyright (c) 2019-2021, The Tor Project, Inc. */
/* See LICENSE for licensing information */

/**
 * \file sendme_nola.c
 * \brief Code that implements the TOR_NOLA congestion control algorithm
 *        from Proposal #324.
 */

#define SENDME_NOLA_PRIVATE

#include "core/or/or.h"

#include "core/or/crypt_path.h"
#include "core/or/or_circuit_st.h"
#include "core/or/sendme.h"
#include "core/or/sendme_nola.h"
#include "core/or/circuituse.h"
#include "core/or/circuitlist.h"
#include "core/or/origin_circuit_st.h"
#include "core/or/channel.h"

/**
* Process a SENDME and update the congestion window according to the
* rules specified in TOR_NOLA of Proposal #324.
*
* TOR_NOLA updates the congestion window to match the current
* BDP estimate, every sendme. Because this can result in downward
* drift, a fixed overhead is added to the BDP estimate. This will
* cause some queuing, but ensures that the algorithm always uses
* the full BDP.
*
* To handle the case where the local orconn blocks, TOR_NOLA uses
* the 'piecewise' BDP estimate, which uses more a conservative BDP
* estimate method when blocking occurrs, but a more aggressive BDP
* estimate when there is no local blocking. This minimizes local
* client queues.
*/
int
sendme_nola_process_circuit_level(crypt_path_t *layer_hint,
                                  circuit_t *circ)
{
  congestion_control_t *cc;

  tor_assert(circ);

  // Get CC object
  if (layer_hint) {
    tor_assert(CIRCUIT_IS_ORIGIN(circ));
    cc = layer_hint->ccontrol;
  } else {
    tor_assert(!CIRCUIT_IS_ORIGIN(circ));
    cc = circ->ccontrol;
  }

  /* Leave slow start immediately to use max of SENDME and CWND */
  cc->in_slow_start = 0;

  /* Nola updates every single RTT */
  cc->next_cc_event = 0;

  /* Compute BDP and RTT */
  sendme_update_circuit_estimates(circ, cc, layer_hint);

  // We overshoot the BDP by the cwnd_inc param amount, because BDP
  // may otherwise drift down. This helps us probe for more capacity.
  cc->cwnd = cc->bdp[cc->bdp_alg] + cc->nola_params.bdp_overshoot;

  /* cwnd can never fall below 1 increment */
  cc->cwnd = MAX(cc->cwnd, cc->cwnd_min);

  if (CIRCUIT_IS_ORIGIN(circ)) {
    log_notice(LD_CIRC,
               "Circuit %d "
               "CWND: %"PRIu64", "
               "INFL: %"PRIu64", "
               "NCCE: %"PRIu64", "
               "SS: %d",
             TO_ORIGIN_CIRCUIT(circ)->global_identifier,
             cc->cwnd,
             cc->inflight,
             cc->next_cc_event,
             cc->in_slow_start
             );
  } else {
    log_notice(LD_CIRC,
               "Circuit %"PRIu64":%d "
               "CWND: %"PRIu64", "
               "INFL: %"PRIu64", "
               "NCCE: %"PRIu64", "
               "SS: %d",
               circ->n_chan->global_identifier, circ->n_circ_id,
             cc->cwnd,
             cc->inflight,
             cc->next_cc_event,
             cc->in_slow_start
             );
  }

  /* Update inflight with ack */
  cc->inflight = cc->inflight - cc->sendme_inc;

  return 0;
}
