// Copyright (c) 2012 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 "chrome/browser/net/network_stats.h"

#include "base/bind.h"
#include "base/logging.h"
#include "base/message_loop.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/histogram.h"
#include "base/rand_util.h"
#include "base/stringprintf.h"
#include "base/threading/platform_thread.h"
#include "base/time.h"
#include "base/tuple.h"
#include "chrome/common/chrome_version_info.h"
#include "content/public/browser/browser_thread.h"
#include "googleurl/src/gurl.h"
#include "net/base/net_errors.h"
#include "net/base/net_util.h"
#include "net/base/network_change_notifier.h"
#include "net/base/test_completion_callback.h"
#include "net/proxy/proxy_service.h"
#include "net/udp/udp_client_socket.h"
#include "net/udp/udp_server_socket.h"

using content::BrowserThread;

namespace chrome_browser_net {

// This specifies the number of bytes to be sent to the UDP echo servers as part
// of small packet size test.
static const uint32 kSmallTestBytesToSend = 100;

// This specifies the number of bytes to be sent to the UDP echo servers as part
// of medium packet size test.
static const uint32 kMediumTestBytesToSend = 500;

// This specifies the number of bytes to be sent to the UDP echo servers as part
// of large packet size test.
static const uint32 kLargeTestBytesToSend = 1200;

// This specifies starting position of the <version> and length of the
// <version> in "echo request" and "echo response".
static const uint32 kVersionNumber = 1;
static const uint32 kVersionStart = 0;
static const uint32 kVersionLength = 2;
static const uint32 kVersionEnd = kVersionStart + kVersionLength;

// This specifies the starting position of the <checksum> and length of the
// <checksum> in "echo request" and "echo response". Maximum value for the
// <checksum> is less than (2 ** 31 - 1).
static const uint32 kChecksumStart = kVersionEnd;
static const uint32 kChecksumLength = 10;
static const uint32 kChecksumEnd = kChecksumStart + kChecksumLength;

// This specifies the starting position of the <payload_size> and length of the
// <payload_size> in "echo request" and "echo response". Maximum number of bytes
// that can be sent in the <payload> is 9,999,999.
static const uint32 kPayloadSizeStart = kChecksumEnd;
static const uint32 kPayloadSizeLength = 7;
static const uint32 kPayloadSizeEnd = kPayloadSizeStart + kPayloadSizeLength;

// This specifies the starting position of the <key> and length of the <key> in
// "echo response".
static const uint32 kKeyStart = kPayloadSizeEnd;
static const uint32 kKeyLength = 6;
static const uint32 kKeyEnd = kKeyStart + kKeyLength;
static const int32 kKeyMinValue = 0;
static const int32 kKeyMaxValue = 999999;

// This specifies the starting position of the <payload> in "echo request".
static const uint32 kPayloadStart = kPayloadSizeEnd;

// This specifies the length of the packet_number in the payload.
static const uint32 kPacketNumberLength = 10;

// This specifies the starting position of the <encoded_payload> in
// "echo response".
static const uint32 kEncodedPayloadStart = kKeyEnd;

// HistogramPortSelector and kPorts should be kept in sync. Port 999999 is
// used by the unit tests.
static const int32 kPorts[] = {6121, 999999};

// Number of packets that are recorded in a packet-correlation histogram, which
// shows exactly what sequence of packets were responded to.  We use this to
// deduce specific packet loss correlation.
static const uint32 kCorrelatedLossPacketCount = 6;

// Maximum number of packets that can be sent to the server.
static const uint32 kMaximumSequentialPackets = 21;

// This specifies the maximum message (payload) size.
static const uint32 kMaxMessage = kMaximumSequentialPackets * 2048;

// NetworkStats methods and members.
NetworkStats::NetworkStats()
    : read_buffer_(NULL),
      write_buffer_(NULL),
      load_size_(0),
      bytes_to_read_(0),
      bytes_to_send_(0),
      has_proxy_server_(false),
      packets_to_send_(0),
      packets_sent_(0),
      packets_received_(0),
      packets_received_mask_(0),
      packet_number_(0),
      base_packet_number_(0),
      sending_complete_(false),
      current_test_(START_PACKET_TEST),
      next_test_(TEST_TYPE_MAX),
      ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) {
}

NetworkStats::~NetworkStats() {
  socket_.reset();
}

bool NetworkStats::Start(net::HostResolver* host_resolver,
                         const net::HostPortPair& server_host_port_pair,
                         HistogramPortSelector histogram_port,
                         bool has_proxy_server,
                         uint32 bytes_to_send,
                         uint32 packets_to_send,
                         const net::CompletionCallback& finished_callback) {
  DCHECK(host_resolver);
  DCHECK(bytes_to_send);   // We should have data to send.
  DCHECK_LE(packets_to_send, kMaximumSequentialPackets);

  Initialize(bytes_to_send,
             histogram_port,
             has_proxy_server,
             packets_to_send,
             finished_callback);

  net::HostResolver::RequestInfo request(server_host_port_pair);
  int rv = host_resolver->Resolve(
      request, &addresses_,
      base::Bind(&NetworkStats::OnResolveComplete,
                 base::Unretained(this)),
      NULL, net::BoundNetLog());
  if (rv == net::ERR_IO_PENDING)
    return true;
  return DoConnect(rv);
}

void NetworkStats::RestartPacketTest() {
  ResetData();
  current_test_ = next_test_;
  next_test_ = TEST_TYPE_MAX;
  if (!bytes_to_read_) {
    read_buffer_ = NULL;
    ReadData();
  }
  SendPacket();
}

void NetworkStats::Initialize(
    uint32 bytes_to_send,
    HistogramPortSelector histogram_port,
    bool has_proxy_server,
    uint32 packets_to_send,
    const net::CompletionCallback& finished_callback) {
  DCHECK(bytes_to_send);    // We should have data to send.
  DCHECK(packets_to_send);  // We should send at least 1 packet.
  DCHECK_LE(bytes_to_send, kLargeTestBytesToSend);
  DCHECK_LE(packets_to_send,  8 * sizeof(packets_received_mask_));

  load_size_ = bytes_to_send;
  packets_to_send_ = packets_to_send;
  histogram_port_ = histogram_port;
  has_proxy_server_ = has_proxy_server;
  finished_callback_ = finished_callback;
  ResetData();
  packet_number_ = base::RandInt(1 << 28, INT_MAX);
}

void NetworkStats::ResetData() {
  write_buffer_ = NULL;
  bytes_to_send_ = 0;
  packet_status_.clear();
  packet_status_.resize(packets_to_send_);
  packets_sent_ = 0;
  packets_received_ = 0;
  packets_received_mask_ = 0;
  sending_complete_ = false;
}

void NetworkStats::OnResolveComplete(int result) {
  DoConnect(result);
}

bool NetworkStats::DoConnect(int result) {
  if (result != net::OK) {
    Finish(RESOLVE_FAILED, result);
    return false;
  }

  net::UDPClientSocket* udp_socket =
      new net::UDPClientSocket(net::DatagramSocket::DEFAULT_BIND,
                               net::RandIntCallback(),
                               NULL,
                               net::NetLog::Source());
  if (!udp_socket) {
    Finish(SOCKET_CREATE_FAILED, net::ERR_INVALID_ARGUMENT);
    return false;
  }
  set_socket(udp_socket);

  if (addresses().empty()) {
    Finish(RESOLVE_FAILED, net::ERR_INVALID_ARGUMENT);
    return false;
  }

  const net::IPEndPoint& endpoint = addresses().front();
  int rv = udp_socket->Connect(endpoint);
  if (rv < 0) {
    Finish(CONNECT_FAILED, rv);
    return false;
  }

  const int kSocketBufferSize = 2 * packets_to_send_ * 2048;
  udp_socket->SetSendBufferSize(kSocketBufferSize);
  udp_socket->SetReceiveBufferSize(kSocketBufferSize);
  return ConnectComplete(rv);
}

bool NetworkStats::ConnectComplete(int result) {
  if (result < 0) {
    Finish(CONNECT_FAILED, result);
    return false;
  }

  ReadData();
  SendPacket();
  return true;
}

void NetworkStats::SendPacket() {
  while (true) {
    if (bytes_to_send_ == 0u) {
      if (packets_sent_ >= packets_to_send_) {
        // Timeout if we don't get response back from echo servers in 30 secs.
        sending_complete_ = true;
        const int kReadDataTimeoutMs = 30000;
        StartReadDataTimer(kReadDataTimeoutMs);
        break;
      }

      ++packet_number_;
      if (packets_sent_ == 0)
        base_packet_number_ = packet_number_;
      bytes_to_send_ = SendingPacketSize();
      SendNextPacketAfterDelay();
      break;
    }

    int rv = SendData();
    if (rv < 0) {
      if (rv != net::ERR_IO_PENDING)
        Finish(WRITE_FAILED, rv);
      break;
    }
    DCHECK_EQ(bytes_to_send_, 0u);
  };
}

void NetworkStats::SendNextPacketAfterDelay() {
  if (current_test_ == PACED_PACKET_TEST) {
    MessageLoop::current()->PostDelayedTask(
        FROM_HERE,
        base::Bind(&NetworkStats::SendPacket, weak_factory_.GetWeakPtr()),
        average_time_);
    return;
  }

  MessageLoop::current()->PostTask(
      FROM_HERE,
      base::Bind(&NetworkStats::SendPacket, weak_factory_.GetWeakPtr()));
}

bool NetworkStats::ReadComplete(int result) {
  DCHECK(socket_.get());
  DCHECK_NE(net::ERR_IO_PENDING, result);
  if (result < 0) {
    Finish(READ_FAILED, result);
    return true;
  }

  if (result > 0) {
    std::string encoded_message;
    encoded_message.append(read_buffer_->data(), result);
    if (VerifyBytes(encoded_message) == SUCCESS) {
      base::TimeTicks now = base::TimeTicks::Now();
      if (packets_received_ == 0)
        packet_1st_byte_read_time_ = now;
      packet_last_byte_read_time_ = now;

      DCHECK_GE(bytes_to_read_, static_cast<uint32>(result));
      if (bytes_to_read_ >= static_cast<uint32>(result))
        bytes_to_read_ -= result;
      ++packets_received_;
    }
  }

  read_buffer_ = NULL;

  // No more data to read.
  if (!bytes_to_read_ || result == 0) {
    if (!sending_complete_)
      return false;

    Status status = VerifyPackets();
    if (status == SUCCESS)
      Finish(status, net::OK);
    else
      Finish(status, net::ERR_INVALID_RESPONSE);
    return true;
  }
  return false;
}

void NetworkStats::OnReadComplete(int result) {
  if (!ReadComplete(result)) {
    // Called ReadData() via PostDelayedTask() to avoid recursion. Added a delay
    // of 1ms so that the time-out will fire before we have time to really hog
    // the CPU too extensively (waiting for the time-out) in case of an infinite
    // loop.
    MessageLoop::current()->PostDelayedTask(
        FROM_HERE,
        base::Bind(&NetworkStats::ReadData, weak_factory_.GetWeakPtr()),
        base::TimeDelta::FromMilliseconds(1));
  }
}

void NetworkStats::OnWriteComplete(int result) {
  DCHECK(socket_.get());
  DCHECK_NE(net::ERR_IO_PENDING, result);
  if (result < 0) {
    Finish(WRITE_FAILED, result);
    return;
  }

  DidSendData(result);
  if (bytes_to_send_) {
    int rv = SendData();
    if (rv < 0) {
      if (rv != net::ERR_IO_PENDING)
        Finish(WRITE_FAILED, rv);
      return;
    }
    DCHECK_EQ(rv, net::OK);
    DCHECK_EQ(bytes_to_send_, 0u);
  }

  MessageLoop::current()->PostTask(
      FROM_HERE,
      base::Bind(&NetworkStats::SendPacket, weak_factory_.GetWeakPtr()));
}

void NetworkStats::ReadData() {
  int rv;
  do {
    if (!socket_.get())
      break;

    DCHECK(!read_buffer_.get());

    // We release the read_buffer_ in the destructor if there is an error.
    read_buffer_ = new net::IOBuffer(kMaxMessage);

    rv = socket_->Read(read_buffer_, kMaxMessage,
                       base::Bind(&NetworkStats::OnReadComplete,
                                  base::Unretained(this)));
    if (rv == net::ERR_IO_PENDING)
      break;

    // If we have read all the data then return.
    if (ReadComplete(rv))
      break;
  } while (rv > 0);
}

int NetworkStats::SendData() {
  DCHECK(bytes_to_send_);   // We should have data to send.
  do {
    if (!write_buffer_.get()) {
      // Send a new packet.
      scoped_refptr<net::IOBufferWithSize> buffer(
          new net::IOBufferWithSize(bytes_to_send_));
      GetEchoRequest(buffer);
      write_buffer_ = new net::DrainableIOBuffer(buffer, bytes_to_send_);

      // As soon as we write, a read could happen. Thus update all the book
      // keeping data.
      bytes_to_read_ += ReceivingPacketSize();
      ++packets_sent_;
      if (packets_sent_ >= packets_to_send_)
        sending_complete_ = true;

      uint32 packet_index = packet_number_ - base_packet_number_;
      DCHECK_GE(packet_index, 0u);
      DCHECK_LT(packet_index, packet_status_.size());
      packet_status_[packet_index].start_time_ = base::TimeTicks::Now();
    }

    if (!socket_.get())
      return net::ERR_UNEXPECTED;
    int rv = socket_->Write(write_buffer_,
                            write_buffer_->BytesRemaining(),
                            base::Bind(&NetworkStats::OnWriteComplete,
                                       base::Unretained(this)));
    if (rv < 0)
      return rv;
    DidSendData(rv);
  } while (bytes_to_send_);
  return net::OK;
}

uint32 NetworkStats::SendingPacketSize() const {
  return kVersionLength + kChecksumLength + kPayloadSizeLength + load_size_;
}

uint32 NetworkStats::ReceivingPacketSize() const {
  return kVersionLength + kChecksumLength + kPayloadSizeLength + kKeyLength +
      load_size_;
}

void NetworkStats::DidSendData(int bytes_sent) {
  write_buffer_->DidConsume(bytes_sent);
  if (!write_buffer_->BytesRemaining())
    write_buffer_ = NULL;
  bytes_to_send_ -= bytes_sent;
}

void NetworkStats::StartReadDataTimer(int milliseconds) {
  MessageLoop::current()->PostDelayedTask(
      FROM_HERE,
      base::Bind(&NetworkStats::OnReadDataTimeout,
                 weak_factory_.GetWeakPtr(),
                 base_packet_number_),
      base::TimeDelta::FromMilliseconds(milliseconds));
}

void NetworkStats::OnReadDataTimeout(uint32 test_base_packet_number) {
  if (test_base_packet_number != base_packet_number_)
    return;

  Status status = VerifyPackets();
  if (status == SUCCESS)
    Finish(status, net::OK);
  else
    Finish(READ_TIMED_OUT, net::ERR_INVALID_ARGUMENT);
}

uint32 NetworkStats::GetChecksum(const char* message, uint32 message_length) {
  // Calculate the <checksum> of the <message>.
  uint32 sum = 0;
  for (uint32 i = 0; i < message_length; ++i)
    sum += message[i];
  return sum;
}

void NetworkStats::Crypt(const char* key,
                         uint32 key_length,
                         const char* data,
                         uint32 data_length,
                         char* encoded_data) {
  // Decrypt the data by looping through the |data| and XOR each byte with the
  // |key| to get the decoded byte. Append the decoded byte to the
  // |encoded_data|.
  for (uint32 data_index = 0, key_index = 0;
       data_index < data_length;
       ++data_index) {
    char data_byte = data[data_index];
    char key_byte = key[key_index];
    char encoded_byte = data_byte ^ key_byte;
    encoded_data[data_index] = encoded_byte;
    key_index = (key_index + 1) % key_length;
  }
}

void NetworkStats::GetEchoRequest(net::IOBufferWithSize* io_buffer) {
  char* buffer = io_buffer->data();
  uint32 buffer_size = static_cast<uint32>(io_buffer->size());

  // Copy the <version> into the io_buffer starting from the kVersionStart
  // position.
  std::string version = base::StringPrintf("%02d", kVersionNumber);
  DCHECK(kVersionLength == version.length());
  DCHECK_GE(buffer_size, kVersionStart + kVersionLength);
  memcpy(buffer + kVersionStart, version.c_str(), kVersionLength);

  // Copy the packet_number into the payload.
  std::string packet_number = base::StringPrintf("%010d", packet_number_);
  DCHECK(kPacketNumberLength == packet_number.length());
  DCHECK_GE(buffer_size, kPayloadStart + kPacketNumberLength);
  memcpy(buffer + kPayloadStart, packet_number.c_str(), kPacketNumberLength);

  // Get the <payload> from the |stream_| and copy it into io_buffer after
  // packet_number.
  stream_.Reset();
  DCHECK_GE(buffer_size, kPayloadStart + load_size_);
  stream_.GetBytes(buffer + kPayloadStart + kPacketNumberLength,
                   load_size_ - kPacketNumberLength);

  // Calculate the <checksum> of the <payload>.
  uint32 sum = GetChecksum(buffer + kPayloadStart, load_size_);

  // Copy the <checksum> into the io_buffer starting from the kChecksumStart
  // position.
  std::string checksum = base::StringPrintf("%010d", sum);
  DCHECK(kChecksumLength == checksum.length());
  DCHECK_GE(buffer_size, kChecksumStart + kChecksumLength);
  memcpy(buffer + kChecksumStart, checksum.c_str(), kChecksumLength);

  // Copy the size of the <payload> into the io_buffer starting from the
  // kPayloadSizeStart position.
  std::string payload_size = base::StringPrintf("%07d", load_size_);
  DCHECK(kPayloadSizeLength == payload_size.length());
  DCHECK_GE(buffer_size, kPayloadSizeStart + kPayloadSizeLength);
  memcpy(buffer + kPayloadSizeStart, payload_size.c_str(), kPayloadSizeLength);
}

NetworkStats::Status NetworkStats::VerifyPackets() {
  Status status = SUCCESS;
  uint32 successful_packets = 0;

  for (uint32 i = 0; i < packet_status_.size(); i++) {
    if (packets_received_mask_ & (1 << i))
      ++successful_packets;
  }

  if (packets_received_ > packets_to_send_)
    status = TOO_MANY_PACKETS;

  if (packets_to_send_ > successful_packets)
    status = SOME_PACKETS_NOT_VERIFIED;

  if (packets_to_send_ == kMaximumSequentialPackets &&
      successful_packets > 1) {
    base::TimeDelta total_time;
    if (packet_last_byte_read_time_ > packet_1st_byte_read_time_) {
      total_time =
          packet_last_byte_read_time_ - packet_1st_byte_read_time_;
    }
    average_time_ = total_time / (successful_packets - 1);
    std::string histogram_name = base::StringPrintf(
        "NetConnectivity3.%s.Sent%02d.%d.%dB.PacketDelay",
        TestName(),
        kMaximumSequentialPackets,
        kPorts[histogram_port_],
        load_size_);
    base::Histogram* histogram = base::Histogram::FactoryTimeGet(
        histogram_name, base::TimeDelta::FromMilliseconds(1),
        base::TimeDelta::FromSeconds(30), 50,
        base::Histogram::kUmaTargetedHistogramFlag);
    histogram->AddTime(total_time);

    if (current_test_ == START_PACKET_TEST) {
        int experiment_to_run = base::RandInt(1, 2);
        if (experiment_to_run == 1)
          next_test_ = NON_PACED_PACKET_TEST;
        else
          next_test_ = PACED_PACKET_TEST;
    }
  }

  return status;
}

NetworkStats::Status NetworkStats::VerifyBytes(const std::string& response) {
  // If the "echo response" doesn't have enough bytes, then return false.
  if (response.length() <= kVersionStart)
    return ZERO_LENGTH_ERROR;
  if (response.length() <= kChecksumStart)
    return NO_CHECKSUM_ERROR;
  if (response.length() <= kPayloadSizeStart)
    return NO_PAYLOAD_SIZE_ERROR;
  if (response.length() <= kKeyStart)
    return NO_KEY_ERROR;
  if (response.length() <= kEncodedPayloadStart)
    return NO_PAYLOAD_ERROR;

  // Extract the |key| from the "echo response".
  std::string key_string = response.substr(kKeyStart, kKeyLength);
  const char* key = key_string.c_str();
  int key_value = atoi(key);
  if (key_value < kKeyMinValue || key_value > kKeyMaxValue)
    return INVALID_KEY_ERROR;

  std::string encoded_payload = response.substr(kEncodedPayloadStart);
  const char* encoded_data = encoded_payload.c_str();
  uint32 message_length = encoded_payload.length();
  message_length = std::min(message_length, kMaxMessage);
  if (message_length < load_size_)
    return TOO_SHORT_PAYLOAD;
  if (message_length > load_size_)
    return TOO_LONG_PAYLOAD;

  // Decode/decrypt the |encoded_data| into |decoded_data|.
  char decoded_data[kMaxMessage + 1];
  DCHECK_LE(message_length, kMaxMessage);
  memset(decoded_data, 0, kMaxMessage + 1);
  Crypt(key, kKeyLength, encoded_data, message_length, decoded_data);

  // Calculate the <checksum> of the <decoded_data>.
  uint32 sum = GetChecksum(decoded_data, message_length);
  // Extract the |checksum| from the "echo response".
  std::string checksum_string =
      response.substr(kChecksumStart, kChecksumLength);
  const char* checksum = checksum_string.c_str();
  uint32 checksum_value = atoi(checksum);
  if (checksum_value != sum)
    return INVALID_CHECKSUM;

  // Verify the packet_number.
  char packet_number_data[kPacketNumberLength + 1];
  memset(packet_number_data, 0, kPacketNumberLength + 1);
  memcpy(packet_number_data, decoded_data, kPacketNumberLength);
  uint32 packet_number_received = atoi(packet_number_data);
  if (packet_number_received < base_packet_number_)
    return PREVIOUS_PACKET_NUMBER;
  uint32 packet_index = packet_number_received - base_packet_number_;
  if (packet_index >= packets_to_send_)
    return INVALID_PACKET_NUMBER;

  stream_.Reset();
  if (!stream_.VerifyBytes(&decoded_data[kPacketNumberLength],
                           message_length - kPacketNumberLength)) {
    return PATTERN_CHANGED;
  }

  if (packets_received_mask_ & (1 << packet_index))
    return DUPLICATE_PACKET;

  packets_received_mask_ |= 1 << packet_index;
  DCHECK_GE(packet_index, 0u);
  DCHECK_LT(packet_index, packet_status_.size());
  packet_status_[packet_index].end_time_ = base::TimeTicks::Now();
  return SUCCESS;
}

void NetworkStats::Finish(Status status, int result) {
  // Set the base_packet_number_ for the start of next test. Changing the
  // |base_packet_number_| indicates to OnReadDataTimeout that the Finish has
  // already been called for the test and that it doesn't need to call Finish
  // again.
  base_packet_number_ = packet_number_ + 1;
  RecordHistograms(PROTOCOL_UDP, status, result);

  if (next_test() == NON_PACED_PACKET_TEST ||
      next_test() == PACED_PACKET_TEST) {
    MessageLoop::current()->PostTask(
        FROM_HERE,
        base::Bind(&NetworkStats::RestartPacketTest,
                   weak_factory_.GetWeakPtr()));
    return;
  }

  DoFinishCallback(result);

  // Close the socket so that there are no more IO operations.
  net::UDPClientSocket* udp_socket =
      static_cast<net::UDPClientSocket*>(socket());
  if (udp_socket)
    udp_socket->Close();

  delete this;
}

void NetworkStats::DoFinishCallback(int result) {
  if (!finished_callback_.is_null()) {
    net::CompletionCallback callback = finished_callback_;
    finished_callback_.Reset();
    callback.Run(result);
  }
}

void NetworkStats::RecordHistograms(const ProtocolValue& protocol,
                                    const Status& status,
                                    int result) {
  if (packets_to_send_ != kMaximumSequentialPackets)
    return;

  std::string load_size_string = base::StringPrintf("%dB", load_size_);

  RecordPacketLossSeriesHistograms(protocol, load_size_string, status, result);

  for (uint32 i = 0; i < 3; i++)
    RecordRTTHistograms(protocol, load_size_string, i);

  RecordRTTHistograms(protocol, load_size_string, 9);
  RecordRTTHistograms(protocol, load_size_string, 19);

  RecordAcksReceivedHistograms(load_size_string);
}

void NetworkStats::RecordAcksReceivedHistograms(
    const std::string& load_size_string) {
  DCHECK_EQ(packets_to_send_, kMaximumSequentialPackets);

  const char* test_name = TestName();
  bool received_atleast_one_packet = packets_received_mask_ > 0;

  std::string histogram_name = base::StringPrintf(
      "NetConnectivity3.%s.Sent%02d.GotAnAck.%d.%s",
      test_name,
      kMaximumSequentialPackets,
      kPorts[histogram_port_],
      load_size_string.c_str());
  base::Histogram* got_an_ack_histogram = base::BooleanHistogram::FactoryGet(
      histogram_name, base::Histogram::kUmaTargetedHistogramFlag);
  got_an_ack_histogram->AddBoolean(received_atleast_one_packet);

  histogram_name = base::StringPrintf(
      "NetConnectivity3.%s.Sent%02d.PacketsSent.%d.%s",
      test_name,
      kMaximumSequentialPackets,
      kPorts[histogram_port_],
      load_size_string.c_str());
  base::Histogram* packets_sent_histogram =
      base::Histogram::FactoryGet(
          histogram_name,
          1, kMaximumSequentialPackets, kMaximumSequentialPackets + 1,
          base::Histogram::kUmaTargetedHistogramFlag);
  packets_sent_histogram->Add(packets_sent_);

  if (!received_atleast_one_packet || packets_sent_ != packets_to_send_)
    return;

  histogram_name = base::StringPrintf(
      "NetConnectivity3.%s.Sent%02d.AckReceivedForNthPacket.%02d.%s",
      test_name,
      kMaximumSequentialPackets,
      kPorts[histogram_port_],
      load_size_string.c_str());
  base::Histogram* ack_received_for_nth_packet_histogram =
      base::Histogram::FactoryGet(
          histogram_name,
          1, kMaximumSequentialPackets + 1, kMaximumSequentialPackets + 2,
          base::Histogram::kUmaTargetedHistogramFlag);

  int count = 0;
  for (size_t j = 0; j < packets_to_send_; j++) {
    int packet_number = j + 1;
    if (packets_received_mask_ & (1 << j)) {
      ack_received_for_nth_packet_histogram->Add(packet_number);
      count++;
    }
    if (packet_number < 2)
      continue;
    histogram_name = base::StringPrintf(
        "NetConnectivity3.%s.Sent%02d.AcksReceivedFromFirst%02dPackets.%d.%s",
        test_name,
        kMaximumSequentialPackets,
        packet_number,
        kPorts[histogram_port_],
        load_size_string.c_str());
    base::Histogram* acks_received_count_histogram =
        base::Histogram::FactoryGet(
            histogram_name, 1, packet_number, packet_number + 1,
            base::Histogram::kUmaTargetedHistogramFlag);
    acks_received_count_histogram->Add(count);
  }
}

void NetworkStats::RecordPacketLossSeriesHistograms(
    const ProtocolValue& protocol,
    const std::string& load_size_string,
    const Status& status,
    int result) {
  DCHECK_GT(packets_to_send_, kCorrelatedLossPacketCount);
  const char* test_name = TestName();

  // Build "NetConnectivity3.Send6.SeriesAcked.<port>.<load_size>" histogram
  // name. Total number of histograms are 5*2.
  std::string series_acked_histogram_name = base::StringPrintf(
      "NetConnectivity3.%s.Send6.SeriesAcked.%d.%s",
      test_name,
      kPorts[histogram_port_],
      load_size_string.c_str());

  uint32 correlated_packet_mask =
    ((1 << kCorrelatedLossPacketCount) - 1) & packets_received_mask_;

  // If we are running without a proxy, we'll generate 2 distinct histograms in
  // each case, one will have the ".NoProxy" suffix.
  size_t histogram_count = has_proxy_server_ ? 1 : 2;
  for (size_t i = 0; i < histogram_count; i++) {
    // For packet loss test, just record packet loss data.
    base::Histogram* series_acked_histogram = base::LinearHistogram::FactoryGet(
        series_acked_histogram_name,
        1,
        1 << kCorrelatedLossPacketCount,
        (1 << kCorrelatedLossPacketCount) + 1,
        base::Histogram::kUmaTargetedHistogramFlag);
    series_acked_histogram->Add(correlated_packet_mask);
    series_acked_histogram_name.append(".NoProxy");
  }
}

void NetworkStats::RecordRTTHistograms(const ProtocolValue& protocol,
                                       const std::string& load_size_string,
                                       uint32 index) {
  DCHECK_GE(index, 0u);
  DCHECK_LT(index, packet_status_.size());

  const char* test_name = TestName();
  std::string rtt_histogram_name = base::StringPrintf(
      "NetConnectivity3.%s.Sent%02d.Success.RTT.Packet%02d.%d.%s",
      test_name,
      packets_to_send_,
      index + 1,
      kPorts[histogram_port_],
      load_size_string.c_str());
  base::Histogram* rtt_histogram = base::Histogram::FactoryTimeGet(
      rtt_histogram_name,
      base::TimeDelta::FromMilliseconds(10),
      base::TimeDelta::FromSeconds(30), 50,
      base::Histogram::kUmaTargetedHistogramFlag);
  base::TimeDelta duration =
      packet_status_[index].end_time_ - packet_status_[index].start_time_;
  rtt_histogram->AddTime(duration);
}

const char* NetworkStats::TestName() {
  switch (current_test_) {
    case START_PACKET_TEST:
      return "StartPacket";
    case NON_PACED_PACKET_TEST:
      return "NonPacedPacket";
    case PACED_PACKET_TEST:
      return "PacedPacket";
    default:
      NOTREACHED();
      return "None";
  }
}

void NetworkStats::set_socket(net::Socket* socket) {
  DCHECK(socket);
  DCHECK(!socket_.get());
  socket_.reset(socket);
}

// ProxyDetector methods and members.
ProxyDetector::ProxyDetector(net::ProxyService* proxy_service,
                             const net::HostPortPair& server_address,
                             OnResolvedCallback callback)
    : proxy_service_(proxy_service),
      server_address_(server_address),
      callback_(callback),
      has_pending_proxy_resolution_(false) {
}

ProxyDetector::~ProxyDetector() {
  CHECK(!has_pending_proxy_resolution_);
}

void ProxyDetector::StartResolveProxy() {
  std::string url =
      base::StringPrintf("https://%s", server_address_.ToString().c_str());
  GURL gurl(url);

  has_pending_proxy_resolution_ = true;
  DCHECK(proxy_service_);
  int rv = proxy_service_->ResolveProxy(
      gurl,
      &proxy_info_,
      base::Bind(&ProxyDetector::OnResolveProxyComplete,
                 base::Unretained(this)),
      NULL,
      net::BoundNetLog());
  if (rv != net::ERR_IO_PENDING)
    OnResolveProxyComplete(rv);
}

void ProxyDetector::OnResolveProxyComplete(int result) {
  has_pending_proxy_resolution_ = false;
  bool has_proxy_server = (result == net::OK &&
                           proxy_info_.proxy_server().is_valid() &&
                           !proxy_info_.proxy_server().is_direct());

  OnResolvedCallback callback = callback_;
  BrowserThread::PostTask(
      BrowserThread::IO,
      FROM_HERE,
      base::Bind(callback, has_proxy_server));

  // TODO(rtenneti): Will we leak if ProxyResolve is cancelled (or proxy
  // resolution never completes).
  delete this;
}

// static
void CollectNetworkStats(const std::string& network_stats_server,
                         IOThread* io_thread) {
  if (network_stats_server.empty())
    return;

  // If we are not on IO Thread, then post a task to call CollectNetworkStats on
  // IO Thread.
  if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
    BrowserThread::PostTask(
        BrowserThread::IO,
        FROM_HERE,
        base::Bind(
            &CollectNetworkStats, network_stats_server, io_thread));
    return;
  }

  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));

  // Check that there is a network connection. We get called only if UMA upload
  // to the server has succeeded.
  DCHECK(!net::NetworkChangeNotifier::IsOffline());

  CR_DEFINE_STATIC_LOCAL(scoped_refptr<base::FieldTrial>, trial, ());
  static bool collect_stats = false;
  static NetworkStats::HistogramPortSelector histogram_port =
      NetworkStats::PORT_6121;

  if (!trial.get()) {
    // Set up a field trial to collect network stats for UDP.
    const base::FieldTrial::Probability kDivisor = 1000;

    // Enable the connectivity testing for 0.5% of the users in stable channel.
    base::FieldTrial::Probability probability_per_group = 5;

    chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel();
    if (channel == chrome::VersionInfo::CHANNEL_CANARY)
      probability_per_group = kDivisor;
    else if (channel == chrome::VersionInfo::CHANNEL_DEV)
      // Enable the connectivity testing for 10% of the users in dev channel.
      probability_per_group = 500;
    else if (channel == chrome::VersionInfo::CHANNEL_BETA)
      // Enable the connectivity testing for 5% of the users in beta channel.
      probability_per_group = 50;

    // After October 30, 2012 builds, it will always be in default group
    // (disable_network_stats).
    trial = base::FieldTrialList::FactoryGetFieldTrial(
        "NetworkConnectivity", kDivisor, "disable_network_stats",
        2012, 10, 30, NULL);

    // Add option to collect_stats for NetworkConnectivity.
    int collect_stats_group = trial->AppendGroup("collect_stats",
                                                 probability_per_group);
    if (trial->group() == collect_stats_group)
      collect_stats = true;
  }

  if (!collect_stats)
    return;

  // Run test kMaxNumberOfTests times.
  const size_t kMaxNumberOfTests = INT_MAX;
  static size_t number_of_tests_done = 0;
  if (number_of_tests_done > kMaxNumberOfTests)
    return;

  ++number_of_tests_done;

  net::HostResolver* host_resolver = io_thread->globals()->host_resolver.get();
  DCHECK(host_resolver);

  net::HostPortPair server_address(network_stats_server,
                                   kPorts[histogram_port]);

  net::ProxyService* proxy_service =
      io_thread->globals()->system_proxy_service.get();
  DCHECK(proxy_service);

  ProxyDetector::OnResolvedCallback callback =
      base::Bind(&StartNetworkStatsTest,
          host_resolver, server_address, histogram_port);

  ProxyDetector* proxy_client = new ProxyDetector(
      proxy_service, server_address, callback);
  proxy_client->StartResolveProxy();
}

// static
void StartNetworkStatsTest(net::HostResolver* host_resolver,
                           const net::HostPortPair& server_address,
                           NetworkStats::HistogramPortSelector histogram_port,
                           bool has_proxy_server) {
  int experiment_to_run = base::RandInt(1, 3);
  switch (experiment_to_run) {
    case 1:
      {
        NetworkStats* udp_stats_client = new NetworkStats();
        udp_stats_client->Start(
            host_resolver, server_address, histogram_port, has_proxy_server,
            kSmallTestBytesToSend, kMaximumSequentialPackets,
            net::CompletionCallback());
      }
      break;
    case 2:
      {
        NetworkStats* udp_stats_client = new NetworkStats();
        udp_stats_client->Start(
            host_resolver, server_address, histogram_port, has_proxy_server,
            kMediumTestBytesToSend, kMaximumSequentialPackets,
            net::CompletionCallback());
      }
      break;
    case 3:
      {
        NetworkStats* udp_stats_client = new NetworkStats();
        udp_stats_client->Start(
            host_resolver, server_address, histogram_port, has_proxy_server,
            kLargeTestBytesToSend, kMaximumSequentialPackets,
            net::CompletionCallback());
      }
      break;
  }
}

}  // namespace chrome_browser_net