// Copyright (c) 2009 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/base/host_cache.h"

#include "base/format_macros.h"
#include "base/stl_util-inl.h"
#include "base/string_util.h"
#include "net/base/net_errors.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace net {

namespace {
const int kMaxCacheEntries = 10;

const base::TimeDelta kSuccessEntryTTL = base::TimeDelta::FromSeconds(10);
const base::TimeDelta kFailureEntryTTL = base::TimeDelta::FromSeconds(0);

// Builds a key for |hostname|, defaulting the address family to unspecified.
HostCache::Key Key(const std::string& hostname) {
  return HostCache::Key(hostname, ADDRESS_FAMILY_UNSPECIFIED);
}

}  // namespace

TEST(HostCacheTest, Basic) {
  HostCache cache(kMaxCacheEntries, kSuccessEntryTTL, kFailureEntryTTL);

  // Start at t=0.
  base::TimeTicks now;

  const HostCache::Entry* entry1 = NULL;  // Entry for foobar.com.
  const HostCache::Entry* entry2 = NULL;  // Entry for foobar2.com.

  EXPECT_EQ(0U, cache.size());

  // Add an entry for "foobar.com" at t=0.
  EXPECT_TRUE(cache.Lookup(Key("foobar.com"), base::TimeTicks()) == NULL);
  cache.Set(Key("foobar.com"), OK, AddressList(), now);
  entry1 = cache.Lookup(Key("foobar.com"), base::TimeTicks());
  EXPECT_FALSE(entry1 == NULL);
  EXPECT_EQ(1U, cache.size());

  // Advance to t=5.
  now += base::TimeDelta::FromSeconds(5);

  // Add an entry for "foobar2.com" at t=5.
  EXPECT_TRUE(cache.Lookup(Key("foobar2.com"), base::TimeTicks()) == NULL);
  cache.Set(Key("foobar2.com"), OK, AddressList(), now);
  entry2 = cache.Lookup(Key("foobar2.com"), base::TimeTicks());
  EXPECT_FALSE(NULL == entry1);
  EXPECT_EQ(2U, cache.size());

  // Advance to t=9
  now += base::TimeDelta::FromSeconds(4);

  // Verify that the entries we added are still retrievable, and usable.
  EXPECT_EQ(entry1, cache.Lookup(Key("foobar.com"), now));
  EXPECT_EQ(entry2, cache.Lookup(Key("foobar2.com"), now));

  // Advance to t=10; entry1 is now expired.
  now += base::TimeDelta::FromSeconds(1);

  EXPECT_TRUE(cache.Lookup(Key("foobar.com"), now) == NULL);
  EXPECT_EQ(entry2, cache.Lookup(Key("foobar2.com"), now));

  // Update entry1, so it is no longer expired.
  cache.Set(Key("foobar.com"), OK, AddressList(), now);
  // Re-uses existing entry storage.
  EXPECT_EQ(entry1, cache.Lookup(Key("foobar.com"), now));
  EXPECT_EQ(2U, cache.size());

  // Both entries should still be retrievable and usable.
  EXPECT_EQ(entry1, cache.Lookup(Key("foobar.com"), now));
  EXPECT_EQ(entry2, cache.Lookup(Key("foobar2.com"), now));

  // Advance to t=20; both entries are now expired.
  now += base::TimeDelta::FromSeconds(10);

  EXPECT_TRUE(cache.Lookup(Key("foobar.com"), now) == NULL);
  EXPECT_TRUE(cache.Lookup(Key("foobar2.com"), now) == NULL);
}

// Try caching entries for a failed resolve attempt -- since we set
// the TTL of such entries to 0 it won't work.
TEST(HostCacheTest, NoCacheNegative) {
  HostCache cache(kMaxCacheEntries, kSuccessEntryTTL, kFailureEntryTTL);

  // Set t=0.
  base::TimeTicks now;

  EXPECT_TRUE(cache.Lookup(Key("foobar.com"), base::TimeTicks()) == NULL);
  cache.Set(Key("foobar.com"), ERR_NAME_NOT_RESOLVED, AddressList(), now);
  EXPECT_EQ(1U, cache.size());

  // We disallow use of negative entries.
  EXPECT_TRUE(cache.Lookup(Key("foobar.com"), now) == NULL);

  // Now overwrite with a valid entry, and then overwrite with negative entry
  // again -- the valid entry should be kicked out.
  cache.Set(Key("foobar.com"), OK, AddressList(), now);
  EXPECT_FALSE(cache.Lookup(Key("foobar.com"), now) == NULL);
  cache.Set(Key("foobar.com"), ERR_NAME_NOT_RESOLVED, AddressList(), now);
  EXPECT_TRUE(cache.Lookup(Key("foobar.com"), now) == NULL);
}

// Try caching entries for a failed resolves for 10 seconds.
TEST(HostCacheTest, CacheNegativeEntry) {
  HostCache cache(kMaxCacheEntries,
                  base::TimeDelta::FromSeconds(0), // success entry TTL.
                  base::TimeDelta::FromSeconds(10)); // failure entry TTL.

  // Start at t=0.
  base::TimeTicks now;

  const HostCache::Entry* entry1 = NULL;  // Entry for foobar.com.
  const HostCache::Entry* entry2 = NULL;  // Entry for foobar2.com.

  EXPECT_EQ(0U, cache.size());

  // Add an entry for "foobar.com" at t=0.
  EXPECT_TRUE(cache.Lookup(Key("foobar.com"), base::TimeTicks()) == NULL);
  cache.Set(Key("foobar.com"), ERR_NAME_NOT_RESOLVED, AddressList(), now);
  entry1 = cache.Lookup(Key("foobar.com"), base::TimeTicks());
  EXPECT_FALSE(entry1 == NULL);
  EXPECT_EQ(1U, cache.size());

  // Advance to t=5.
  now += base::TimeDelta::FromSeconds(5);

  // Add an entry for "foobar2.com" at t=5.
  EXPECT_TRUE(cache.Lookup(Key("foobar2.com"), base::TimeTicks()) == NULL);
  cache.Set(Key("foobar2.com"), ERR_NAME_NOT_RESOLVED, AddressList(), now);
  entry2 = cache.Lookup(Key("foobar2.com"), base::TimeTicks());
  EXPECT_FALSE(NULL == entry1);
  EXPECT_EQ(2U, cache.size());

  // Advance to t=9
  now += base::TimeDelta::FromSeconds(4);

  // Verify that the entries we added are still retrievable, and usable.
  EXPECT_EQ(entry1, cache.Lookup(Key("foobar.com"), now));
  EXPECT_EQ(entry2, cache.Lookup(Key("foobar2.com"), now));

  // Advance to t=10; entry1 is now expired.
  now += base::TimeDelta::FromSeconds(1);

  EXPECT_TRUE(cache.Lookup(Key("foobar.com"), now) == NULL);
  EXPECT_EQ(entry2, cache.Lookup(Key("foobar2.com"), now));

  // Update entry1, so it is no longer expired.
  cache.Set(Key("foobar.com"), ERR_NAME_NOT_RESOLVED, AddressList(), now);
  // Re-uses existing entry storage.
  EXPECT_EQ(entry1, cache.Lookup(Key("foobar.com"), now));
  EXPECT_EQ(2U, cache.size());

  // Both entries should still be retrievable and usable.
  EXPECT_EQ(entry1, cache.Lookup(Key("foobar.com"), now));
  EXPECT_EQ(entry2, cache.Lookup(Key("foobar2.com"), now));

  // Advance to t=20; both entries are now expired.
  now += base::TimeDelta::FromSeconds(10);

  EXPECT_TRUE(cache.Lookup(Key("foobar.com"), now) == NULL);
  EXPECT_TRUE(cache.Lookup(Key("foobar2.com"), now) == NULL);
}

TEST(HostCacheTest, Compact) {
  // Initial entries limit is big enough to accomadate everything we add.
  HostCache cache(kMaxCacheEntries, kSuccessEntryTTL, kFailureEntryTTL);

  EXPECT_EQ(0U, cache.size());

  // t=10
  base::TimeTicks now = base::TimeTicks() + base::TimeDelta::FromSeconds(10);

  // Add five valid entries at t=10.
  for (int i = 0; i < 5; ++i) {
    std::string hostname = StringPrintf("valid%d", i);
    cache.Set(Key(hostname), OK, AddressList(), now);
  }
  EXPECT_EQ(5U, cache.size());

  // Add 3 expired entries at t=0.
  for (int i = 0; i < 3; ++i) {
    std::string hostname = StringPrintf("expired%d", i);
    base::TimeTicks t = now - base::TimeDelta::FromSeconds(10);
    cache.Set(Key(hostname), OK, AddressList(), t);
  }
  EXPECT_EQ(8U, cache.size());

  // Add 2 negative entries at t=10
  for (int i = 0; i < 2; ++i) {
    std::string hostname = StringPrintf("negative%d", i);
    cache.Set(Key(hostname), ERR_NAME_NOT_RESOLVED, AddressList(), now);
  }
  EXPECT_EQ(10U, cache.size());

  EXPECT_TRUE(ContainsKey(cache.entries_, Key("valid0")));
  EXPECT_TRUE(ContainsKey(cache.entries_, Key("valid1")));
  EXPECT_TRUE(ContainsKey(cache.entries_, Key("valid2")));
  EXPECT_TRUE(ContainsKey(cache.entries_, Key("valid3")));
  EXPECT_TRUE(ContainsKey(cache.entries_, Key("valid4")));
  EXPECT_TRUE(ContainsKey(cache.entries_, Key("expired0")));
  EXPECT_TRUE(ContainsKey(cache.entries_, Key("expired1")));
  EXPECT_TRUE(ContainsKey(cache.entries_, Key("expired2")));
  EXPECT_TRUE(ContainsKey(cache.entries_, Key("negative0")));
  EXPECT_TRUE(ContainsKey(cache.entries_, Key("negative1")));

  // Shrink the max constraints bound and compact. We expect the "negative"
  // and "expired" entries to have been dropped.
  cache.max_entries_ = 5;
  cache.Compact(now, NULL);
  EXPECT_EQ(5U, cache.entries_.size());

  EXPECT_TRUE(ContainsKey(cache.entries_, Key("valid0")));
  EXPECT_TRUE(ContainsKey(cache.entries_, Key("valid1")));
  EXPECT_TRUE(ContainsKey(cache.entries_, Key("valid2")));
  EXPECT_TRUE(ContainsKey(cache.entries_, Key("valid3")));
  EXPECT_TRUE(ContainsKey(cache.entries_, Key("valid4")));
  EXPECT_FALSE(ContainsKey(cache.entries_, Key("expired0")));
  EXPECT_FALSE(ContainsKey(cache.entries_, Key("expired1")));
  EXPECT_FALSE(ContainsKey(cache.entries_, Key("expired2")));
  EXPECT_FALSE(ContainsKey(cache.entries_, Key("negative0")));
  EXPECT_FALSE(ContainsKey(cache.entries_, Key("negative1")));

  // Shrink further -- this time the compact will start dropping valid entries
  // to make space.
  cache.max_entries_ = 3;
  cache.Compact(now, NULL);
  EXPECT_EQ(3U, cache.size());
}

// Add entries while the cache is at capacity, causing evictions.
TEST(HostCacheTest, SetWithCompact) {
  HostCache cache(3, kSuccessEntryTTL, kFailureEntryTTL);

  // t=10
  base::TimeTicks now = base::TimeTicks() + kSuccessEntryTTL;

  cache.Set(Key("host1"), OK, AddressList(), now);
  cache.Set(Key("host2"), OK, AddressList(), now);
  cache.Set(Key("expired"), OK, AddressList(), now - kSuccessEntryTTL);

  EXPECT_EQ(3U, cache.size());

  // Should all be retrievable except "expired".
  EXPECT_FALSE(NULL == cache.Lookup(Key("host1"), now));
  EXPECT_FALSE(NULL == cache.Lookup(Key("host2"), now));
  EXPECT_TRUE(NULL == cache.Lookup(Key("expired"), now));

  // Adding the fourth entry will cause "expired" to be evicted.
  cache.Set(Key("host3"), OK, AddressList(), now);
  EXPECT_EQ(3U, cache.size());
  EXPECT_TRUE(cache.Lookup(Key("expired"), now) == NULL);
  EXPECT_FALSE(cache.Lookup(Key("host1"), now) == NULL);
  EXPECT_FALSE(cache.Lookup(Key("host2"), now) == NULL);
  EXPECT_FALSE(cache.Lookup(Key("host3"), now) == NULL);

  // Add two more entries. Something should be evicted, however "host5"
  // should definitely be in there (since it was last inserted).
  cache.Set(Key("host4"), OK, AddressList(), now);
  EXPECT_EQ(3U, cache.size());
  cache.Set(Key("host5"), OK, AddressList(), now);
  EXPECT_EQ(3U, cache.size());
  EXPECT_FALSE(cache.Lookup(Key("host5"), now) == NULL);
}

// Tests that the same hostname can be duplicated in the cache, so long as
// the address family differs.
TEST(HostCacheTest, AddressFamilyIsPartOfKey) {
  HostCache cache(kMaxCacheEntries, kSuccessEntryTTL, kFailureEntryTTL);

  // t=0.
  base::TimeTicks now;

  HostCache::Key key1("foobar.com", ADDRESS_FAMILY_UNSPECIFIED);
  HostCache::Key key2("foobar.com", ADDRESS_FAMILY_IPV4);

  const HostCache::Entry* entry1 = NULL;  // Entry for key1
  const HostCache::Entry* entry2 = NULL;  // Entry for key2

  EXPECT_EQ(0U, cache.size());

  // Add an entry for ("foobar.com", UNSPECIFIED) at t=0.
  EXPECT_TRUE(cache.Lookup(key1, base::TimeTicks()) == NULL);
  cache.Set(key1, OK, AddressList(), now);
  entry1 = cache.Lookup(key1, base::TimeTicks());
  EXPECT_FALSE(entry1 == NULL);
  EXPECT_EQ(1U, cache.size());

  // Add an entry for ("foobar.com", IPV4_ONLY) at t=0.
  EXPECT_TRUE(cache.Lookup(key2, base::TimeTicks()) == NULL);
  cache.Set(key2, OK, AddressList(), now);
  entry2 = cache.Lookup(key2, base::TimeTicks());
  EXPECT_FALSE(entry2 == NULL);
  EXPECT_EQ(2U, cache.size());

  // Even though the hostnames were the same, we should have two unique
  // entries (because the address families differ).
  EXPECT_NE(entry1, entry2);
}

TEST(HostCacheTest, NoCache) {
  // Disable caching.
  HostCache cache(0, kSuccessEntryTTL, kFailureEntryTTL);
  EXPECT_TRUE(cache.caching_is_disabled());

  // Set t=0.
  base::TimeTicks now;

  // Lookup and Set should have no effect.
  EXPECT_TRUE(cache.Lookup(Key("foobar.com"), base::TimeTicks()) == NULL);
  cache.Set(Key("foobar.com"), OK, AddressList(), now);
  EXPECT_TRUE(cache.Lookup(Key("foobar.com"), base::TimeTicks()) == NULL);

  EXPECT_EQ(0U, cache.size());
}

TEST(HostCacheTest, Clear) {
  HostCache cache(kMaxCacheEntries, kSuccessEntryTTL, kFailureEntryTTL);

  // Set t=0.
  base::TimeTicks now;

  EXPECT_EQ(0u, cache.size());

  // Add three entries.
  cache.Set(Key("foobar1.com"), OK, AddressList(), now);
  cache.Set(Key("foobar2.com"), OK, AddressList(), now);
  cache.Set(Key("foobar3.com"), OK, AddressList(), now);

  EXPECT_EQ(3u, cache.size());

  cache.clear();

  EXPECT_EQ(0u, cache.size());
}

// Tests the less than and equal operators for HostCache::Key work.
TEST(HostCacheTest, KeyComparators) {
  struct {
    // Inputs.
    HostCache::Key key1;
    HostCache::Key key2;

    // Expectation.
    //   -1 means key1 is less than key2
    //    0 means key1 equals key2
    //    1 means key1 is greater than key2
    int expected_comparison;
  } tests[] = {
    {
      HostCache::Key("host1", ADDRESS_FAMILY_UNSPECIFIED),
      HostCache::Key("host1", ADDRESS_FAMILY_UNSPECIFIED),
      0
    },
    {
      HostCache::Key("host1", ADDRESS_FAMILY_IPV4),
      HostCache::Key("host1", ADDRESS_FAMILY_UNSPECIFIED),
      1
    },
    {
      HostCache::Key("host1", ADDRESS_FAMILY_UNSPECIFIED),
      HostCache::Key("host1", ADDRESS_FAMILY_IPV4),
      -1
    },
    {
      HostCache::Key("host1", ADDRESS_FAMILY_UNSPECIFIED),
      HostCache::Key("host2", ADDRESS_FAMILY_UNSPECIFIED),
      -1
    },
    {
      HostCache::Key("host1", ADDRESS_FAMILY_IPV4),
      HostCache::Key("host2", ADDRESS_FAMILY_UNSPECIFIED),
      1
    },
    {
      HostCache::Key("host1", ADDRESS_FAMILY_UNSPECIFIED),
      HostCache::Key("host2", ADDRESS_FAMILY_IPV4),
      -1
    },
  };

  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
    SCOPED_TRACE(StringPrintf("Test[%" PRIuS "]", i));

    const HostCache::Key& key1 = tests[i].key1;
    const HostCache::Key& key2 = tests[i].key2;

    switch (tests[i].expected_comparison) {
      case -1:
        EXPECT_TRUE(key1 < key2);
        EXPECT_FALSE(key2 < key1);
        EXPECT_FALSE(key2 == key1);
        break;
      case 0:
        EXPECT_FALSE(key1 < key2);
        EXPECT_FALSE(key2 < key1);
        EXPECT_TRUE(key2 == key1);
        break;
      case 1:
        EXPECT_FALSE(key1 < key2);
        EXPECT_TRUE(key2 < key1);
        EXPECT_FALSE(key2 == key1);
        break;
      default:
        FAIL() << "Invalid expectation. Can be only -1, 0, 1";
    }
  }
}

}  // namespace net
