Index: net/quic/crypto/strike_register_test.cc |
diff --git a/net/quic/crypto/strike_register_test.cc b/net/quic/crypto/strike_register_test.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..4dd479ea0eb0e365c752c65e92ecc43f1bc02a72 |
--- /dev/null |
+++ b/net/quic/crypto/strike_register_test.cc |
@@ -0,0 +1,271 @@ |
+// 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}; |
+ |
+void |
+NonceSetTimeAndOrbit(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); |
+} |
+ |
+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); |
+ uint8 nonce[32]; |
+ NonceSetTimeAndOrbit(nonce, 999, kOrbit); |
+ ASSERT_FALSE(set.Insert(nonce, 1000)); |
+ NonceSetTimeAndOrbit(nonce, 1000, kOrbit); |
+ ASSERT_FALSE(set.Insert(nonce, 1000)); |
+} |
+ |
+TEST(StrikeRegisterTest, WindowFuture) { |
+ // The set must reject values outside the window. |
+ StrikeRegister set(10 /* max size */, 1000 /* current time */, |
+ 100 /* window secs */, kOrbit); |
+ uint8 nonce[32]; |
+ NonceSetTimeAndOrbit(nonce, 1101, kOrbit); |
+ ASSERT_FALSE(set.Insert(nonce, 1000)); |
+ NonceSetTimeAndOrbit(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); |
+ uint8 nonce[32]; |
+ static const uint8 kBadOrbit[8] = {0, 0, 0, 0, 1, 1, 1, 1}; |
+ NonceSetTimeAndOrbit(nonce, 1101, kBadOrbit); |
+ ASSERT_FALSE(set.Insert(nonce, 1100)); |
+} |
+ |
+TEST(StrikeRegisterTest, OneValue) { |
+ StrikeRegister set(10 /* max size */, 1000 /* current time */, |
+ 100 /* window secs */, kOrbit); |
+ uint8 nonce[32]; |
+ NonceSetTimeAndOrbit(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); |
+ uint8 nonce[32]; |
+ memset(nonce, 0, sizeof(nonce)); |
+ NonceSetTimeAndOrbit(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); |
+ uint8 nonce[6][32]; |
+ for (unsigned i = 0; i < 5; i++) { |
+ NonceSetTimeAndOrbit(nonce[i], 1101, kOrbit); |
+ memset(nonce[i] + 12, 0, 20); |
+ 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. |
+ NonceSetTimeAndOrbit(nonce[5], 1102, kOrbit); |
+ memset(nonce[5] + 12, 0, 20); |
+ ASSERT_TRUE(set.Insert(nonce[5], 1100)); |
+ |
+ // This should be behind the horizon now: |
+ NonceSetTimeAndOrbit(nonce[5], 1101, kOrbit); |
+ memset(nonce[5] + 12, 0, 20); |
+ 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); |
+ |
+ uint8 nonce[32]; |
+ NonceSetTimeAndOrbit(nonce, 1101, kOrbit); |
+ for (unsigned i = 0; i < 100000; i++) { |
+ NonceSetTimeAndOrbit(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), |
+ horizon_(current_time + window_secs) { |
+ memcpy(orbit_, orbit, sizeof(orbit_)); |
+ } |
+ |
+ bool Insert(const uint8 nonce_bytes[32], const uint32 current_time) { |
+ // Same overflow and underflow check as the real StrikeRegister. |
+ if (current_time < window_secs_ || |
+ current_time + window_secs_ < current_time) { |
+ nonces_.clear(); |
+ horizon_ = current_time; |
+ return false; |
+ } |
+ |
+ // Check to see if the orbit is correct. |
+ if (memcmp(nonce_bytes + 4, orbit_, sizeof(orbit_))) { |
+ return false; |
+ } |
+ const uint32 nonce_time = 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 (nonce_time < (current_time - window_secs_) || |
+ nonce_time > (current_time + window_secs_)) { |
+ return false; |
+ } |
+ |
+ const string nonce(reinterpret_cast<const char*>(nonce_bytes), 32); |
+ |
+ 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]); |
+ } |
+ |
+ 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_; |
+ 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)); |
+ 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)); |
+ 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]; |
+ NonceSetTimeAndOrbit(nonce, time, kOrbit); |
+ memset(nonce + 12, 0, 20); |
+ |
+ // 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 |