OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "net/quic/crypto/strike_register.h" |
| 6 |
| 7 #include <set> |
| 8 #include <string> |
| 9 |
| 10 #include "base/basictypes.h" |
| 11 #include "base/rand_util.h" |
| 12 #include "testing/gtest/include/gtest/gtest.h" |
| 13 |
| 14 namespace { |
| 15 |
| 16 using net::StrikeRegister; |
| 17 using std::set; |
| 18 using std::string; |
| 19 |
| 20 const uint8 kOrbit[8] = {1, 2, 3, 4, 5, 6, 7, 8}; |
| 21 |
| 22 void |
| 23 NonceSetTimeAndOrbit(uint8 nonce[32], unsigned time, const uint8 orbit[8]) { |
| 24 nonce[0] = time >> 24; |
| 25 nonce[1] = time >> 16; |
| 26 nonce[2] = time >> 8; |
| 27 nonce[3] = time; |
| 28 memcpy(nonce + 4, orbit, 8); |
| 29 } |
| 30 |
| 31 TEST(StrikeRegisterTest, SimpleHorizon) { |
| 32 // The set must reject values created on or before its own creation time. |
| 33 StrikeRegister set(10 /* max size */, 1000 /* current time */, |
| 34 100 /* window secs */, kOrbit); |
| 35 uint8 nonce[32]; |
| 36 NonceSetTimeAndOrbit(nonce, 999, kOrbit); |
| 37 ASSERT_FALSE(set.Insert(nonce, 1000)); |
| 38 NonceSetTimeAndOrbit(nonce, 1000, kOrbit); |
| 39 ASSERT_FALSE(set.Insert(nonce, 1000)); |
| 40 } |
| 41 |
| 42 TEST(StrikeRegisterTest, WindowFuture) { |
| 43 // The set must reject values outside the window. |
| 44 StrikeRegister set(10 /* max size */, 1000 /* current time */, |
| 45 100 /* window secs */, kOrbit); |
| 46 uint8 nonce[32]; |
| 47 NonceSetTimeAndOrbit(nonce, 1101, kOrbit); |
| 48 ASSERT_FALSE(set.Insert(nonce, 1000)); |
| 49 NonceSetTimeAndOrbit(nonce, 999, kOrbit); |
| 50 ASSERT_FALSE(set.Insert(nonce, 1100)); |
| 51 } |
| 52 |
| 53 TEST(StrikeRegisterTest, BadOrbit) { |
| 54 // The set must reject values with the wrong orbit |
| 55 StrikeRegister set(10 /* max size */, 1000 /* current time */, |
| 56 100 /* window secs */, kOrbit); |
| 57 uint8 nonce[32]; |
| 58 static const uint8 kBadOrbit[8] = {0, 0, 0, 0, 1, 1, 1, 1}; |
| 59 NonceSetTimeAndOrbit(nonce, 1101, kBadOrbit); |
| 60 ASSERT_FALSE(set.Insert(nonce, 1100)); |
| 61 } |
| 62 |
| 63 TEST(StrikeRegisterTest, OneValue) { |
| 64 StrikeRegister set(10 /* max size */, 1000 /* current time */, |
| 65 100 /* window secs */, kOrbit); |
| 66 uint8 nonce[32]; |
| 67 NonceSetTimeAndOrbit(nonce, 1101, kOrbit); |
| 68 ASSERT_TRUE(set.Insert(nonce, 1100)); |
| 69 } |
| 70 |
| 71 TEST(StrikeRegisterTest, RejectDuplicate) { |
| 72 // The set must reject values with the wrong orbit |
| 73 StrikeRegister set(10 /* max size */, 1000 /* current time */, |
| 74 100 /* window secs */, kOrbit); |
| 75 uint8 nonce[32]; |
| 76 memset(nonce, 0, sizeof(nonce)); |
| 77 NonceSetTimeAndOrbit(nonce, 1101, kOrbit); |
| 78 ASSERT_TRUE(set.Insert(nonce, 1100)); |
| 79 ASSERT_FALSE(set.Insert(nonce, 1100)); |
| 80 } |
| 81 |
| 82 TEST(StrikeRegisterTest, HorizonUpdating) { |
| 83 StrikeRegister set(5 /* max size */, 1000 /* current time */, |
| 84 100 /* window secs */, kOrbit); |
| 85 uint8 nonce[6][32]; |
| 86 for (unsigned i = 0; i < 5; i++) { |
| 87 NonceSetTimeAndOrbit(nonce[i], 1101, kOrbit); |
| 88 memset(nonce[i] + 12, 0, 20); |
| 89 nonce[i][31] = i; |
| 90 ASSERT_TRUE(set.Insert(nonce[i], 1100)); |
| 91 } |
| 92 |
| 93 // This should push the oldest value out and force the horizon to be updated. |
| 94 NonceSetTimeAndOrbit(nonce[5], 1102, kOrbit); |
| 95 memset(nonce[5] + 12, 0, 20); |
| 96 ASSERT_TRUE(set.Insert(nonce[5], 1100)); |
| 97 |
| 98 // This should be behind the horizon now: |
| 99 NonceSetTimeAndOrbit(nonce[5], 1101, kOrbit); |
| 100 memset(nonce[5] + 12, 0, 20); |
| 101 nonce[5][31] = 10; |
| 102 ASSERT_FALSE(set.Insert(nonce[5], 1100)); |
| 103 } |
| 104 |
| 105 TEST(StrikeRegisterTest, InsertMany) { |
| 106 StrikeRegister set(5000 /* max size */, 1000 /* current time */, |
| 107 500 /* window secs */, kOrbit); |
| 108 |
| 109 uint8 nonce[32]; |
| 110 NonceSetTimeAndOrbit(nonce, 1101, kOrbit); |
| 111 for (unsigned i = 0; i < 100000; i++) { |
| 112 NonceSetTimeAndOrbit(nonce, 1101 + i/500, kOrbit); |
| 113 memcpy(nonce + 12, &i, sizeof(i)); |
| 114 set.Insert(nonce, 1100); |
| 115 } |
| 116 } |
| 117 |
| 118 |
| 119 // For the following test we create a slow, but simple, version of a |
| 120 // StrikeRegister. The behaviour of this object is much easier to understand |
| 121 // than the fully fledged version. We then create a test to show, empirically, |
| 122 // that the two objects have identical behaviour. |
| 123 |
| 124 // A SlowStrikeRegister has the same public interface as a StrikeRegister, but |
| 125 // is much slower. Hopefully it is also more obviously correct and we can |
| 126 // empirically test that their behaviours are identical. |
| 127 class SlowStrikeRegister { |
| 128 public: |
| 129 SlowStrikeRegister(unsigned max_entries, uint32 current_time, |
| 130 uint32 window_secs, const uint8 orbit[8]) |
| 131 : max_entries_(max_entries), |
| 132 window_secs_(window_secs), |
| 133 horizon_(current_time + window_secs) { |
| 134 memcpy(orbit_, orbit, sizeof(orbit_)); |
| 135 } |
| 136 |
| 137 bool Insert(const uint8 nonce_bytes[32], const uint32 current_time) { |
| 138 // Same overflow and underflow check as the real StrikeRegister. |
| 139 if (current_time < window_secs_ || |
| 140 current_time + window_secs_ < current_time) { |
| 141 nonces_.clear(); |
| 142 horizon_ = current_time; |
| 143 return false; |
| 144 } |
| 145 |
| 146 // Check to see if the orbit is correct. |
| 147 if (memcmp(nonce_bytes + 4, orbit_, sizeof(orbit_))) { |
| 148 return false; |
| 149 } |
| 150 const uint32 nonce_time = TimeFromBytes(nonce_bytes); |
| 151 // We have dropped one or more nonces with a time value of |horizon_|, so |
| 152 // we have to reject anything with a timestamp less than or equal to that. |
| 153 if (nonce_time <= horizon_) { |
| 154 return false; |
| 155 } |
| 156 |
| 157 // Check that the timestamp is in the current window. |
| 158 if (nonce_time < (current_time - window_secs_) || |
| 159 nonce_time > (current_time + window_secs_)) { |
| 160 return false; |
| 161 } |
| 162 |
| 163 const string nonce(reinterpret_cast<const char*>(nonce_bytes), 32); |
| 164 |
| 165 set<string>::const_iterator it = nonces_.find(nonce); |
| 166 if (it != nonces_.end()) { |
| 167 return false; |
| 168 } |
| 169 |
| 170 if (nonces_.size() == max_entries_) { |
| 171 DropOldestEntry(); |
| 172 } |
| 173 |
| 174 nonces_.insert(nonce); |
| 175 return true; |
| 176 } |
| 177 |
| 178 private: |
| 179 // TimeFromBytes returns a big-endian uint32 from |d|. |
| 180 static uint32 TimeFromBytes(const uint8 d[4]) { |
| 181 return static_cast<uint32>(d[0]) << 24 | |
| 182 static_cast<uint32>(d[1]) << 16 | |
| 183 static_cast<uint32>(d[2]) << 8 | |
| 184 static_cast<uint32>(d[3]); |
| 185 } |
| 186 |
| 187 void DropOldestEntry() { |
| 188 set<string>::iterator oldest = nonces_.begin(), it; |
| 189 uint32 oldest_time = |
| 190 TimeFromBytes(reinterpret_cast<const uint8*>(oldest->data())); |
| 191 |
| 192 for (it = oldest; it != nonces_.end(); it++) { |
| 193 uint32 t = TimeFromBytes(reinterpret_cast<const uint8*>(it->data())); |
| 194 if (t < oldest_time || |
| 195 (t == oldest_time && memcmp(it->data(), oldest->data(), 32) < 0)) { |
| 196 oldest_time = t; |
| 197 oldest = it; |
| 198 } |
| 199 } |
| 200 |
| 201 nonces_.erase(oldest); |
| 202 horizon_ = oldest_time; |
| 203 } |
| 204 |
| 205 const unsigned max_entries_; |
| 206 const unsigned window_secs_; |
| 207 uint8 orbit_[8]; |
| 208 uint32 horizon_; |
| 209 |
| 210 set<string> nonces_; |
| 211 }; |
| 212 |
| 213 TEST(StrikeRegisterStressTest, Stress) { |
| 214 // Fixed seed gives reproducibility for this test. |
| 215 ignore_result(base::RandGenerator(42)); |
| 216 unsigned max_entries = 64; |
| 217 uint32 current_time = 10000, window = 200; |
| 218 scoped_ptr<StrikeRegister> s1(new StrikeRegister( |
| 219 max_entries, current_time, window, kOrbit)); |
| 220 scoped_ptr<SlowStrikeRegister> s2(new SlowStrikeRegister( |
| 221 max_entries, current_time, window, kOrbit)); |
| 222 uint64 i; |
| 223 |
| 224 // When making changes it's worth removing the limit on this test and running |
| 225 // it for a while. For the initial development an opt binary was left running |
| 226 // for 10 minutes. |
| 227 const uint64 kMaxIterations = 10000; |
| 228 for (i = 0; i < kMaxIterations; i++) { |
| 229 if (base::RandDouble() < 0.001) { |
| 230 // 0.1% chance of resetting the sets. |
| 231 max_entries = base::RandGenerator(300) + 2; |
| 232 current_time = base::RandGenerator(10000); |
| 233 window = base::RandGenerator(500); |
| 234 s1.reset(new StrikeRegister(max_entries, current_time, window, kOrbit)); |
| 235 s2.reset(new SlowStrikeRegister(max_entries, current_time, window, |
| 236 kOrbit)); |
| 237 } |
| 238 |
| 239 int32 time_delta = base::RandGenerator(window * 4); |
| 240 time_delta -= window * 2; |
| 241 const uint32 time = current_time + time_delta; |
| 242 if (time_delta < 0 && time > current_time) { |
| 243 continue; // overflow |
| 244 } |
| 245 |
| 246 uint8 nonce[32]; |
| 247 NonceSetTimeAndOrbit(nonce, time, kOrbit); |
| 248 memset(nonce + 12, 0, 20); |
| 249 |
| 250 // There are 2048 possible nonce values: |
| 251 const uint32 v = base::RandGenerator(2048); |
| 252 nonce[30] = v >> 8; |
| 253 nonce[31] = v; |
| 254 |
| 255 const bool r2 = s2->Insert(nonce, time); |
| 256 const bool r1 = s1->Insert(nonce, time); |
| 257 |
| 258 if (r1 != r2) { |
| 259 break; |
| 260 } |
| 261 |
| 262 if (i % 10 == 0) { |
| 263 s1->Validate(); |
| 264 } |
| 265 } |
| 266 |
| 267 if (i != kMaxIterations) { |
| 268 FAIL() << "Failed after " << i << " iterations"; |
| 269 } |
| 270 } |
| 271 |
| 272 } // anonymous namespace |
OLD | NEW |