// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stdlib.h>

#include "net/curvecp/rtt_and_send_rate_calculator.h"

namespace net {

RttAndSendRateCalculator::RttAndSendRateCalculator()
    : rtt_latest_(0),
      rtt_average_(0),
      rtt_deviation_(0),
      rtt_lowwater_(0),
      rtt_highwater_(0),
      rtt_timeout_(base::Time::kMicrosecondsPerSecond),
      send_rate_(1000000),
      rtt_seenrecenthigh_(0),
      rtt_seenrecentlow_(0),
      rtt_seenolderhigh_(0),
      rtt_seenolderlow_(0),
      rtt_phase_(false) {
}

void RttAndSendRateCalculator::OnMessage(int32 rtt_us) {
  UpdateRTT(rtt_us);
  AdjustSendRate();
}

void RttAndSendRateCalculator::OnTimeout() {
  base::TimeDelta time_since_last_loss = last_sample_time_ - last_loss_time_;
  if (time_since_last_loss.InMilliseconds() < 4 * rtt_timeout_)
    return;
  send_rate_ *= 2;
  last_loss_time_ = last_sample_time_;
  last_edge_time_ = last_sample_time_;
}

// Updates RTT
void RttAndSendRateCalculator::UpdateRTT(int32 rtt_us) {
  rtt_latest_ = rtt_us;
  last_sample_time_ = base::TimeTicks::Now();

  // Initialize for the first sample.
  if (!rtt_average_) {
    rtt_average_ = rtt_latest_;
    rtt_deviation_ = rtt_latest_;
    rtt_highwater_ = rtt_latest_;
    rtt_lowwater_ = rtt_latest_;
  }

  // Jacobson's retransmission timeout calculation.
  int32 rtt_delta = rtt_latest_ - rtt_average_;
  rtt_average_ += rtt_delta / 8;
  if (rtt_delta < 0)
    rtt_delta = -rtt_delta;
  rtt_delta -= rtt_deviation_;
  rtt_deviation_ += rtt_delta / 4;
  rtt_timeout_ = rtt_average_ + (4 * rtt_deviation_);

  // Adjust for delayed acks with anti-spiking.
  // TODO(mbelshe): this seems high.
  rtt_timeout_ += 8 * send_rate_;

  // Recognize the top and bottom of the congestion cycle.
  rtt_delta = rtt_latest_ - rtt_highwater_;
  rtt_highwater_ += rtt_delta / 1024;

  rtt_delta = rtt_latest_ - rtt_lowwater_;
  if (rtt_delta > 0)
    rtt_lowwater_ += rtt_delta / 8192;
  else
    rtt_lowwater_ += rtt_delta / 256;
}

void RttAndSendRateCalculator::AdjustSendRate() {
  last_sample_time_ = base::TimeTicks::Now();

  base::TimeDelta time = last_sample_time_ - last_send_adjust_time_;

  // We only adjust the send rate approximately every 16 samples.
  if (time.InMicroseconds() < 16 * send_rate_)
    return;

  if (rtt_average_ >
      rtt_highwater_ + (5 * base::Time::kMicrosecondsPerMillisecond))
    rtt_seenrecenthigh_ = true;
  else if (rtt_average_ < rtt_lowwater_)
    rtt_seenrecentlow_ = true;

  last_send_adjust_time_ = last_sample_time_;

  // If too much time has elapsed, re-initialize the send_rate.
  if (time.InMicroseconds() >  10 * base::Time::kMicrosecondsPerSecond) {
    send_rate_ =  base::Time::kMicrosecondsPerSecond;  // restart.
    send_rate_ += randommod(send_rate_ / 8);
  }

  // TODO(mbelshe):  Why is 128us a lower bound?
  if (send_rate_ >= 128) {
    // Additive increase:  adjust 1/N by a constant c.
    // rtt-fair additive increase: adjust 1/N by a constant c every nanosec.
    // approximation: adjust 1/N by cN every N nanoseconds.

    // TODO(mbelshe): he used c == 2^-51. for nanosecs.
    //                I use c ==  2^31, for microsecs.

    // i.e. N <- 1/(1/N + cN) = N/(1 + cN^2) every N nanosec.
    if (false && send_rate_ < 16384) {
      // N/(1+cN^2) approx N - cN^3
      // TODO(mbelshe):  note that he is using (cN)^3 here, not what he wrote.
      int32 msec = send_rate_ / 128;
      send_rate_ -= msec * msec * msec;
    } else {
      double d = send_rate_;
      send_rate_ = d / (1 + d * d / 2147483648.0);  // 2^31
    }
  }

  if (rtt_phase_ == false) {
    if (rtt_seenolderhigh_) {
      rtt_phase_ = true;
      last_edge_time_ = last_sample_time_;
      send_rate_ += randommod(send_rate_ / 4);
    }
  } else {
    if (rtt_seenolderlow_) {
      rtt_phase_ = false;
    }
  }

  rtt_seenolderhigh_ = rtt_seenrecenthigh_;
  rtt_seenolderlow_ = rtt_seenrecentlow_;
  rtt_seenrecenthigh_ = false;
  rtt_seenrecentlow_ = false;

  AttemptToDoubleSendRate();
}

void RttAndSendRateCalculator::AttemptToDoubleSendRate() {
  base::TimeDelta time_since_edge = last_sample_time_ - last_edge_time_;
  base::TimeDelta time_since_doubling =
      last_sample_time_ - last_doubling_time_;


  int32 threshold = 0;
  if (time_since_edge.InMicroseconds() <
      base::Time::kMicrosecondsPerSecond * 60) {
    threshold = (4 * send_rate_) +
                (64 * rtt_timeout_) +
                (5 * base::Time::kMicrosecondsPerSecond);
  } else {
    threshold = (4 * send_rate_) +
                (2 * rtt_timeout_);
  }
  if (time_since_doubling.InMicroseconds() < threshold)
    return;

  if (send_rate_ < 64)
    return;

  send_rate_ /= 2;
  last_doubling_time_ = last_sample_time_;
}

// returns a random number from 0 to val-1.
int32 RttAndSendRateCalculator::randommod(int32 val) {
  return rand() % val;
}

}  // namespace net