root/net/quic/crypto/strike_register_test.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. SetNonce
  2. TEST
  3. TEST
  4. TEST
  5. TEST
  6. TEST
  7. TEST
  8. TEST
  9. TEST
  10. horizon_
  11. Insert
  12. TimeFromBytes
  13. ExternalTimeToInternal
  14. DropOldestEntry
  15. TEST

// Copyright (c) 2013 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/quic/crypto/strike_register.h"

#include <set>
#include <string>

#include "base/basictypes.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {

using net::StrikeRegister;
using std::set;
using std::string;

const uint8 kOrbit[8] = { 1, 2, 3, 4, 5, 6, 7, 8 };

// StrikeRegisterTests don't look at the random bytes so this function can
// simply set the random bytes to 0.
void SetNonce(uint8 nonce[32], unsigned time, const uint8 orbit[8]) {
  nonce[0] = time >> 24;
  nonce[1] = time >> 16;
  nonce[2] = time >> 8;
  nonce[3] = time;
  memcpy(nonce + 4, orbit, 8);
  memset(nonce + 12, 0, 20);
}

TEST(StrikeRegisterTest, SimpleHorizon) {
  // The set must reject values created on or before its own creation time.
  StrikeRegister set(10 /* max size */, 1000 /* current time */,
                     100 /* window secs */, kOrbit,
                     StrikeRegister::DENY_REQUESTS_AT_STARTUP);
  uint8 nonce[32];
  SetNonce(nonce, 999, kOrbit);
  ASSERT_FALSE(set.Insert(nonce, 1000));
  SetNonce(nonce, 1000, kOrbit);
  ASSERT_FALSE(set.Insert(nonce, 1000));
}

TEST(StrikeRegisterTest, NoStartupMode) {
  // Check that a strike register works immediately if NO_STARTUP_PERIOD_NEEDED
  // is specified.
  StrikeRegister set(10 /* max size */, 0 /* current time */,
                     100 /* window secs */, kOrbit,
                     StrikeRegister::NO_STARTUP_PERIOD_NEEDED);
  uint8 nonce[32];
  SetNonce(nonce, 0, kOrbit);
  ASSERT_TRUE(set.Insert(nonce, 0));
  ASSERT_FALSE(set.Insert(nonce, 0));
}

TEST(StrikeRegisterTest, WindowFuture) {
  // The set must reject values outside the window.
  StrikeRegister set(10 /* max size */, 1000 /* current time */,
                     100 /* window secs */, kOrbit,
                     StrikeRegister::DENY_REQUESTS_AT_STARTUP);
  uint8 nonce[32];
  SetNonce(nonce, 1101, kOrbit);
  ASSERT_FALSE(set.Insert(nonce, 1000));
  SetNonce(nonce, 999, kOrbit);
  ASSERT_FALSE(set.Insert(nonce, 1100));
}

TEST(StrikeRegisterTest, BadOrbit) {
  // The set must reject values with the wrong orbit
  StrikeRegister set(10 /* max size */, 1000 /* current time */,
                     100 /* window secs */, kOrbit,
                     StrikeRegister::DENY_REQUESTS_AT_STARTUP);
  uint8 nonce[32];
  static const uint8 kBadOrbit[8] = { 0, 0, 0, 0, 1, 1, 1, 1 };
  SetNonce(nonce, 1101, kBadOrbit);
  ASSERT_FALSE(set.Insert(nonce, 1100));
}

TEST(StrikeRegisterTest, OneValue) {
  StrikeRegister set(10 /* max size */, 1000 /* current time */,
                     100 /* window secs */, kOrbit,
                     StrikeRegister::DENY_REQUESTS_AT_STARTUP);
  uint8 nonce[32];
  SetNonce(nonce, 1101, kOrbit);
  ASSERT_TRUE(set.Insert(nonce, 1100));
}

TEST(StrikeRegisterTest, RejectDuplicate) {
  // The set must reject values with the wrong orbit
  StrikeRegister set(10 /* max size */, 1000 /* current time */,
                     100 /* window secs */, kOrbit,
                     StrikeRegister::DENY_REQUESTS_AT_STARTUP);
  uint8 nonce[32];
  SetNonce(nonce, 1101, kOrbit);
  ASSERT_TRUE(set.Insert(nonce, 1100));
  ASSERT_FALSE(set.Insert(nonce, 1100));
}

TEST(StrikeRegisterTest, HorizonUpdating) {
  StrikeRegister set(5 /* max size */, 1000 /* current time */,
                     100 /* window secs */, kOrbit,
                     StrikeRegister::DENY_REQUESTS_AT_STARTUP);
  uint8 nonce[6][32];
  for (unsigned i = 0; i < 5; i++) {
    SetNonce(nonce[i], 1101, kOrbit);
    nonce[i][31] = i;
    ASSERT_TRUE(set.Insert(nonce[i], 1100));
  }

  // This should push the oldest value out and force the horizon to be updated.
  SetNonce(nonce[5], 1102, kOrbit);
  ASSERT_TRUE(set.Insert(nonce[5], 1100));

  // This should be behind the horizon now:
  SetNonce(nonce[5], 1101, kOrbit);
  nonce[5][31] = 10;
  ASSERT_FALSE(set.Insert(nonce[5], 1100));
}

TEST(StrikeRegisterTest, InsertMany) {
  StrikeRegister set(5000 /* max size */, 1000 /* current time */,
                     500 /* window secs */, kOrbit,
                     StrikeRegister::DENY_REQUESTS_AT_STARTUP);

  uint8 nonce[32];
  SetNonce(nonce, 1101, kOrbit);
  for (unsigned i = 0; i < 100000; i++) {
    SetNonce(nonce, 1101 + i/500, kOrbit);
    memcpy(nonce + 12, &i, sizeof(i));
    set.Insert(nonce, 1100);
  }
}


// For the following test we create a slow, but simple, version of a
// StrikeRegister. The behaviour of this object is much easier to understand
// than the fully fledged version. We then create a test to show, empirically,
// that the two objects have identical behaviour.

// A SlowStrikeRegister has the same public interface as a StrikeRegister, but
// is much slower. Hopefully it is also more obviously correct and we can
// empirically test that their behaviours are identical.
class SlowStrikeRegister {
 public:
  SlowStrikeRegister(unsigned max_entries, uint32 current_time,
                     uint32 window_secs, const uint8 orbit[8])
      : max_entries_(max_entries),
        window_secs_(window_secs),
        creation_time_(current_time),
        horizon_(ExternalTimeToInternal(current_time + window_secs)) {
    memcpy(orbit_, orbit, sizeof(orbit_));
  }

  bool Insert(const uint8 nonce_bytes[32], const uint32 current_time_external) {
    const uint32 current_time = ExternalTimeToInternal(current_time_external);

    // Check to see if the orbit is correct.
    if (memcmp(nonce_bytes + 4, orbit_, sizeof(orbit_))) {
      return false;
    }
    const uint32 nonce_time =
        ExternalTimeToInternal(TimeFromBytes(nonce_bytes));
    // We have dropped one or more nonces with a time value of |horizon_|, so
    // we have to reject anything with a timestamp less than or equal to that.
    if (nonce_time <= horizon_) {
      return false;
    }

    // Check that the timestamp is in the current window.
    if ((current_time > window_secs_ &&
         nonce_time < (current_time - window_secs_)) ||
        nonce_time > (current_time + window_secs_)) {
      return false;
    }

    string nonce;
    nonce.reserve(32);
    nonce +=
        string(reinterpret_cast<const char*>(&nonce_time), sizeof(nonce_time));
    nonce +=
        string(reinterpret_cast<const char*>(nonce_bytes) + sizeof(nonce_time),
               32 - sizeof(nonce_time));

    set<string>::const_iterator it = nonces_.find(nonce);
    if (it != nonces_.end()) {
      return false;
    }

    if (nonces_.size() == max_entries_) {
      DropOldestEntry();
    }

    nonces_.insert(nonce);
    return true;
  }

 private:
  // TimeFromBytes returns a big-endian uint32 from |d|.
  static uint32 TimeFromBytes(const uint8 d[4]) {
    return static_cast<uint32>(d[0]) << 24 |
           static_cast<uint32>(d[1]) << 16 |
           static_cast<uint32>(d[2]) << 8 |
           static_cast<uint32>(d[3]);
  }

  uint32 ExternalTimeToInternal(uint32 external_time) {
    static const uint32 kCreationTimeFromInternalEpoch = 63115200.0;
    uint32 internal_epoch = 0;
    if (creation_time_ > kCreationTimeFromInternalEpoch) {
      internal_epoch = creation_time_ - kCreationTimeFromInternalEpoch;
    }

    return external_time - internal_epoch;
  }

  void DropOldestEntry() {
    set<string>::iterator oldest = nonces_.begin(), it;
    uint32 oldest_time =
        TimeFromBytes(reinterpret_cast<const uint8*>(oldest->data()));

    for (it = oldest; it != nonces_.end(); it++) {
      uint32 t = TimeFromBytes(reinterpret_cast<const uint8*>(it->data()));
      if (t < oldest_time ||
          (t == oldest_time && memcmp(it->data(), oldest->data(), 32) < 0)) {
        oldest_time = t;
        oldest = it;
      }
    }

    nonces_.erase(oldest);
    horizon_ = oldest_time;
  }

  const unsigned max_entries_;
  const unsigned window_secs_;
  const uint32 creation_time_;
  uint8 orbit_[8];
  uint32 horizon_;

  set<string> nonces_;
};

TEST(StrikeRegisterStressTest, Stress) {
  // Fixed seed gives reproducibility for this test.
  srand(42);
  unsigned max_entries = 64;
  uint32 current_time = 10000, window = 200;
  scoped_ptr<StrikeRegister> s1(
      new StrikeRegister(max_entries, current_time, window, kOrbit,
                         StrikeRegister::DENY_REQUESTS_AT_STARTUP));
  scoped_ptr<SlowStrikeRegister> s2(
      new SlowStrikeRegister(max_entries, current_time, window, kOrbit));
  uint64 i;

  // When making changes it's worth removing the limit on this test and running
  // it for a while. For the initial development an opt binary was left running
  // for 10 minutes.
  const uint64 kMaxIterations = 10000;
  for (i = 0; i < kMaxIterations; i++) {
    if (rand() % 1000 == 0) {
      // 0.1% chance of resetting the sets.
      max_entries = rand() % 300 + 2;
      current_time = rand() % 10000;
      window = rand() % 500;
      s1.reset(new StrikeRegister(max_entries, current_time, window, kOrbit,
                                  StrikeRegister::DENY_REQUESTS_AT_STARTUP));
      s2.reset(
          new SlowStrikeRegister(max_entries, current_time, window, kOrbit));
    }

    int32 time_delta = rand() % (window * 4);
    time_delta -= window * 2;
    const uint32 time = current_time + time_delta;
    if (time_delta < 0 && time > current_time) {
      continue;  // overflow
    }

    uint8 nonce[32];
    SetNonce(nonce, time, kOrbit);

    // There are 2048 possible nonce values:
    const uint32 v = rand() % 2048;
    nonce[30] = v >> 8;
    nonce[31] = v;

    const bool r2 = s2->Insert(nonce, time);
    const bool r1 = s1->Insert(nonce, time);

    if (r1 != r2) {
      break;
    }

    if (i % 10 == 0) {
      s1->Validate();
    }
  }

  if (i != kMaxIterations) {
    FAIL() << "Failed after " << i << " iterations";
  }
}

}  // anonymous namespace

/* [<][>][^][v][top][bottom][index][help] */