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.
5 #include "net/quic/crypto/strike_register.h"
10 #include "base/basictypes.h"
11 #include "testing/gtest/include/gtest/gtest.h"
15 using net::StrikeRegister
;
19 const uint8 kOrbit
[8] = { 1, 2, 3, 4, 5, 6, 7, 8 };
21 // StrikeRegisterTests don't look at the random bytes so this function can
22 // simply set the random bytes to 0.
23 void SetNonce(uint8 nonce
[32], unsigned time
, const uint8 orbit
[8]) {
24 nonce
[0] = time
>> 24;
25 nonce
[1] = time
>> 16;
28 memcpy(nonce
+ 4, orbit
, 8);
29 memset(nonce
+ 12, 0, 20);
32 TEST(StrikeRegisterTest
, SimpleHorizon
) {
33 // The set must reject values created on or before its own creation time.
34 StrikeRegister
set(10 /* max size */, 1000 /* current time */,
35 100 /* window secs */, kOrbit
,
36 StrikeRegister::DENY_REQUESTS_AT_STARTUP
);
38 SetNonce(nonce
, 999, kOrbit
);
39 ASSERT_FALSE(set
.Insert(nonce
, 1000));
40 SetNonce(nonce
, 1000, kOrbit
);
41 ASSERT_FALSE(set
.Insert(nonce
, 1000));
44 TEST(StrikeRegisterTest
, NoStartupMode
) {
45 // Check that a strike register works immediately if NO_STARTUP_PERIOD_NEEDED
47 StrikeRegister
set(10 /* max size */, 0 /* current time */,
48 100 /* window secs */, kOrbit
,
49 StrikeRegister::NO_STARTUP_PERIOD_NEEDED
);
51 SetNonce(nonce
, 0, kOrbit
);
52 ASSERT_TRUE(set
.Insert(nonce
, 0));
53 ASSERT_FALSE(set
.Insert(nonce
, 0));
56 TEST(StrikeRegisterTest
, WindowFuture
) {
57 // The set must reject values outside the window.
58 StrikeRegister
set(10 /* max size */, 1000 /* current time */,
59 100 /* window secs */, kOrbit
,
60 StrikeRegister::DENY_REQUESTS_AT_STARTUP
);
62 SetNonce(nonce
, 1101, kOrbit
);
63 ASSERT_FALSE(set
.Insert(nonce
, 1000));
64 SetNonce(nonce
, 999, kOrbit
);
65 ASSERT_FALSE(set
.Insert(nonce
, 1100));
68 TEST(StrikeRegisterTest
, BadOrbit
) {
69 // The set must reject values with the wrong orbit
70 StrikeRegister
set(10 /* max size */, 1000 /* current time */,
71 100 /* window secs */, kOrbit
,
72 StrikeRegister::DENY_REQUESTS_AT_STARTUP
);
74 static const uint8 kBadOrbit
[8] = { 0, 0, 0, 0, 1, 1, 1, 1 };
75 SetNonce(nonce
, 1101, kBadOrbit
);
76 ASSERT_FALSE(set
.Insert(nonce
, 1100));
79 TEST(StrikeRegisterTest
, OneValue
) {
80 StrikeRegister
set(10 /* max size */, 1000 /* current time */,
81 100 /* window secs */, kOrbit
,
82 StrikeRegister::DENY_REQUESTS_AT_STARTUP
);
84 SetNonce(nonce
, 1101, kOrbit
);
85 ASSERT_TRUE(set
.Insert(nonce
, 1100));
88 TEST(StrikeRegisterTest
, RejectDuplicate
) {
89 // The set must reject values with the wrong orbit
90 StrikeRegister
set(10 /* max size */, 1000 /* current time */,
91 100 /* window secs */, kOrbit
,
92 StrikeRegister::DENY_REQUESTS_AT_STARTUP
);
94 SetNonce(nonce
, 1101, kOrbit
);
95 ASSERT_TRUE(set
.Insert(nonce
, 1100));
96 ASSERT_FALSE(set
.Insert(nonce
, 1100));
99 TEST(StrikeRegisterTest
, HorizonUpdating
) {
100 StrikeRegister
set(5 /* max size */, 1000 /* current time */,
101 100 /* window secs */, kOrbit
,
102 StrikeRegister::DENY_REQUESTS_AT_STARTUP
);
104 for (unsigned i
= 0; i
< 5; i
++) {
105 SetNonce(nonce
[i
], 1101, kOrbit
);
107 ASSERT_TRUE(set
.Insert(nonce
[i
], 1100));
110 // This should push the oldest value out and force the horizon to be updated.
111 SetNonce(nonce
[5], 1102, kOrbit
);
112 ASSERT_TRUE(set
.Insert(nonce
[5], 1100));
114 // This should be behind the horizon now:
115 SetNonce(nonce
[5], 1101, kOrbit
);
117 ASSERT_FALSE(set
.Insert(nonce
[5], 1100));
120 TEST(StrikeRegisterTest
, InsertMany
) {
121 StrikeRegister
set(5000 /* max size */, 1000 /* current time */,
122 500 /* window secs */, kOrbit
,
123 StrikeRegister::DENY_REQUESTS_AT_STARTUP
);
126 SetNonce(nonce
, 1101, kOrbit
);
127 for (unsigned i
= 0; i
< 100000; i
++) {
128 SetNonce(nonce
, 1101 + i
/500, kOrbit
);
129 memcpy(nonce
+ 12, &i
, sizeof(i
));
130 set
.Insert(nonce
, 1100);
135 // For the following test we create a slow, but simple, version of a
136 // StrikeRegister. The behaviour of this object is much easier to understand
137 // than the fully fledged version. We then create a test to show, empirically,
138 // that the two objects have identical behaviour.
140 // A SlowStrikeRegister has the same public interface as a StrikeRegister, but
141 // is much slower. Hopefully it is also more obviously correct and we can
142 // empirically test that their behaviours are identical.
143 class SlowStrikeRegister
{
145 SlowStrikeRegister(unsigned max_entries
, uint32 current_time
,
146 uint32 window_secs
, const uint8 orbit
[8])
147 : max_entries_(max_entries
),
148 window_secs_(window_secs
),
149 creation_time_(current_time
),
150 horizon_(ExternalTimeToInternal(current_time
+ window_secs
)) {
151 memcpy(orbit_
, orbit
, sizeof(orbit_
));
154 bool Insert(const uint8 nonce_bytes
[32], const uint32 current_time_external
) {
155 const uint32 current_time
= ExternalTimeToInternal(current_time_external
);
157 // Check to see if the orbit is correct.
158 if (memcmp(nonce_bytes
+ 4, orbit_
, sizeof(orbit_
))) {
161 const uint32 nonce_time
=
162 ExternalTimeToInternal(TimeFromBytes(nonce_bytes
));
163 // We have dropped one or more nonces with a time value of |horizon_|, so
164 // we have to reject anything with a timestamp less than or equal to that.
165 if (nonce_time
<= horizon_
) {
169 // Check that the timestamp is in the current window.
170 if ((current_time
> window_secs_
&&
171 nonce_time
< (current_time
- window_secs_
)) ||
172 nonce_time
> (current_time
+ window_secs_
)) {
179 string(reinterpret_cast<const char*>(&nonce_time
), sizeof(nonce_time
));
181 string(reinterpret_cast<const char*>(nonce_bytes
) + sizeof(nonce_time
),
182 32 - sizeof(nonce_time
));
184 set
<string
>::const_iterator it
= nonces_
.find(nonce
);
185 if (it
!= nonces_
.end()) {
189 if (nonces_
.size() == max_entries_
) {
193 nonces_
.insert(nonce
);
198 // TimeFromBytes returns a big-endian uint32 from |d|.
199 static uint32
TimeFromBytes(const uint8 d
[4]) {
200 return static_cast<uint32
>(d
[0]) << 24 |
201 static_cast<uint32
>(d
[1]) << 16 |
202 static_cast<uint32
>(d
[2]) << 8 |
203 static_cast<uint32
>(d
[3]);
206 uint32
ExternalTimeToInternal(uint32 external_time
) {
207 static const uint32 kCreationTimeFromInternalEpoch
= 63115200.0;
208 uint32 internal_epoch
= 0;
209 if (creation_time_
> kCreationTimeFromInternalEpoch
) {
210 internal_epoch
= creation_time_
- kCreationTimeFromInternalEpoch
;
213 return external_time
- internal_epoch
;
216 void DropOldestEntry() {
217 set
<string
>::iterator oldest
= nonces_
.begin(), it
;
219 TimeFromBytes(reinterpret_cast<const uint8
*>(oldest
->data()));
221 for (it
= oldest
; it
!= nonces_
.end(); it
++) {
222 uint32 t
= TimeFromBytes(reinterpret_cast<const uint8
*>(it
->data()));
223 if (t
< oldest_time
||
224 (t
== oldest_time
&& memcmp(it
->data(), oldest
->data(), 32) < 0)) {
230 nonces_
.erase(oldest
);
231 horizon_
= oldest_time
;
234 const unsigned max_entries_
;
235 const unsigned window_secs_
;
236 const uint32 creation_time_
;
243 TEST(StrikeRegisterStressTest
, Stress
) {
244 // Fixed seed gives reproducibility for this test.
246 unsigned max_entries
= 64;
247 uint32 current_time
= 10000, window
= 200;
248 scoped_ptr
<StrikeRegister
> s1(
249 new StrikeRegister(max_entries
, current_time
, window
, kOrbit
,
250 StrikeRegister::DENY_REQUESTS_AT_STARTUP
));
251 scoped_ptr
<SlowStrikeRegister
> s2(
252 new SlowStrikeRegister(max_entries
, current_time
, window
, kOrbit
));
255 // When making changes it's worth removing the limit on this test and running
256 // it for a while. For the initial development an opt binary was left running
258 const uint64 kMaxIterations
= 10000;
259 for (i
= 0; i
< kMaxIterations
; i
++) {
260 if (rand() % 1000 == 0) {
261 // 0.1% chance of resetting the sets.
262 max_entries
= rand() % 300 + 2;
263 current_time
= rand() % 10000;
264 window
= rand() % 500;
265 s1
.reset(new StrikeRegister(max_entries
, current_time
, window
, kOrbit
,
266 StrikeRegister::DENY_REQUESTS_AT_STARTUP
));
268 new SlowStrikeRegister(max_entries
, current_time
, window
, kOrbit
));
271 int32 time_delta
= rand() % (window
* 4);
272 time_delta
-= window
* 2;
273 const uint32 time
= current_time
+ time_delta
;
274 if (time_delta
< 0 && time
> current_time
) {
275 continue; // overflow
279 SetNonce(nonce
, time
, kOrbit
);
281 // There are 2048 possible nonce values:
282 const uint32 v
= rand() % 2048;
286 const bool r2
= s2
->Insert(nonce
, time
);
287 const bool r1
= s1
->Insert(nonce
, time
);
298 if (i
!= kMaxIterations
) {
299 FAIL() << "Failed after " << i
<< " iterations";
303 } // anonymous namespace