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