// Copyright 2014 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 "net/spdy/hpack_huffman_aggregator.h"

#include "base/metrics/bucket_ranges.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/histogram.h"
#include "base/metrics/sample_vector.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "net/base/load_flags.h"
#include "net/http/http_request_headers.h"
#include "net/http/http_request_info.h"
#include "net/http/http_response_headers.h"
#include "net/spdy/hpack_encoder.h"
#include "net/spdy/spdy_http_utils.h"

namespace net {

namespace {

const char kHistogramName[] = "Net.SpdyHpackEncodedCharacterFrequency";

const size_t kTotalCountsPublishThreshold = 50000;

// Each encoder uses the default dynamic table size of 4096 total bytes.
const size_t kMaxEncoders = 20;

}  // namespace

HpackHuffmanAggregator::HpackHuffmanAggregator()
  : counts_(256, 0),
    total_counts_(0),
    max_encoders_(kMaxEncoders) {
}

HpackHuffmanAggregator::~HpackHuffmanAggregator() {
  STLDeleteContainerPairSecondPointers(encoders_.begin(), encoders_.end());
  encoders_.clear();
}

void HpackHuffmanAggregator::AggregateTransactionCharacterCounts(
    const HttpRequestInfo& request,
    const HttpRequestHeaders& request_headers,
    const ProxyServer& proxy,
    const HttpResponseHeaders& response_headers) {
  if (IsCrossOrigin(request)) {
    return;
  }
  HostPortPair endpoint = HostPortPair(request.url.HostNoBrackets(),
                                       request.url.EffectiveIntPort());
  HpackEncoder* encoder = ObtainEncoder(
      SpdySessionKey(endpoint, proxy, request.privacy_mode));

  // Convert and encode the request and response header sets.
  {
    SpdyHeaderBlock headers;
    CreateSpdyHeadersFromHttpRequest(
        request, request_headers, &headers, SPDY4, false);

    std::string tmp_out;
    encoder->EncodeHeaderSet(headers, &tmp_out);
  }
  {
    SpdyHeaderBlock headers;
    CreateSpdyHeadersFromHttpResponse(response_headers, &headers);

    std::string tmp_out;
    encoder->EncodeHeaderSet(headers, &tmp_out);
  }
  if (total_counts_ >= kTotalCountsPublishThreshold) {
    PublishCounts();
  }
}

// static
bool HpackHuffmanAggregator::UseAggregator() {
  const std::string group_name =
      base::FieldTrialList::FindFullName("HpackHuffmanAggregator");
  if (group_name == "Enabled") {
    return true;
  }
  return false;
}

// static
void HpackHuffmanAggregator::CreateSpdyHeadersFromHttpResponse(
    const HttpResponseHeaders& headers,
    SpdyHeaderBlock* headers_out) {
  // Lower-case header names, and coalesce multiple values delimited by \0.
  // Also add the fixed status header.
  std::string name, value;
  void* it = NULL;
  while (headers.EnumerateHeaderLines(&it, &name, &value)) {
    StringToLowerASCII(&name);
    if (headers_out->find(name) == headers_out->end()) {
      (*headers_out)[name] = value;
    } else {
      (*headers_out)[name] += std::string(1, '\0') + value;
    }
  }
  (*headers_out)[":status"] = base::IntToString(headers.response_code());
}

// static
bool HpackHuffmanAggregator::IsCrossOrigin(const HttpRequestInfo& request) {
  // Require that the request is top-level, or that it shares
  // an origin with its referer.
  HostPortPair endpoint = HostPortPair(request.url.HostNoBrackets(),
                                       request.url.EffectiveIntPort());
  if ((request.load_flags & LOAD_MAIN_FRAME) == 0) {
    std::string referer_str;
    if (!request.extra_headers.GetHeader(HttpRequestHeaders::kReferer,
                                         &referer_str)) {
      // Require a referer.
      return true;
    }
    GURL referer(referer_str);
    HostPortPair referer_endpoint = HostPortPair(referer.HostNoBrackets(),
                                                 referer.EffectiveIntPort());
    if (!endpoint.Equals(referer_endpoint)) {
      // Cross-origin request.
      return true;
    }
  }
  return false;
}

HpackEncoder* HpackHuffmanAggregator::ObtainEncoder(const SpdySessionKey& key) {
  for (OriginEncoders::iterator it = encoders_.begin();
       it != encoders_.end(); ++it) {
    if (key.Equals(it->first)) {
      // Move to head of list and return.
      OriginEncoder origin_encoder = *it;
      encoders_.erase(it);
      encoders_.push_front(origin_encoder);
      return origin_encoder.second;
    }
  }
  // Not found. Create a new encoder, evicting one if needed.
  encoders_.push_front(std::make_pair(
      key, new HpackEncoder(ObtainHpackHuffmanTable())));
  if (encoders_.size() > max_encoders_) {
    delete encoders_.back().second;
    encoders_.pop_back();
  }
  encoders_.front().second->SetCharCountsStorage(&counts_, &total_counts_);
  return encoders_.front().second;
}

void HpackHuffmanAggregator::PublishCounts() {
  // base::Histogram requires that values be 1-indexed.
  const size_t kRangeMin = 1;
  const size_t kRangeMax = counts_.size() + 1;
  const size_t kBucketCount = kRangeMax + 1;

  base::BucketRanges ranges(kBucketCount + 1);
  for (size_t i = 0; i != ranges.size(); ++i) {
    ranges.set_range(i, i);
  }
  ranges.ResetChecksum();

  // Copy |counts_| into a SampleVector.
  base::SampleVector samples(&ranges);
  for (size_t i = 0; i != counts_.size(); ++i) {
    samples.Accumulate(i + 1, counts_[i]);
  }

  STATIC_HISTOGRAM_POINTER_BLOCK(
      kHistogramName,
      AddSamples(samples),
      base::LinearHistogram::FactoryGet(
          kHistogramName, kRangeMin, kRangeMax, kBucketCount,
          base::HistogramBase::kUmaTargetedHistogramFlag));

  // Clear counts.
  counts_.assign(counts_.size(), 0);
  total_counts_ = 0;
}

}  // namespace net