]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/tests/CKKSRateLimiterTests.m
Security-58286.31.2.tar.gz
[apple/security.git] / keychain / ckks / tests / CKKSRateLimiterTests.m
1 /*
2 * Copyright (c) 2017 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 #if OCTAGON
25
26 #import <XCTest/XCTest.h>
27 #import "keychain/ckks/CKKSOutgoingQueueEntry.h"
28 #import "keychain/ckks/CKKSRateLimiter.h"
29
30 @interface CKKSRateLimiterTests : XCTestCase
31 - (CKKSOutgoingQueueEntry *)oqe;
32 @property CKKSRateLimiter *rl;
33 @property NSDictionary *conf;
34 @property CKKSOutgoingQueueEntry *oqe;
35 @property NSDate *date;
36 @property NSDate *compare;
37 @end
38
39 @implementation CKKSRateLimiterTests
40
41 - (void)setUp {
42 [super setUp];
43 self.rl = [CKKSRateLimiter new];
44 self.conf = [self.rl config];
45 self.oqe = [[CKKSOutgoingQueueEntry alloc] initWithCKKSItem:
46 [[CKKSItem alloc] initWithUUID:@"123"
47 parentKeyUUID:@""
48 zoneID:[[CKRecordZoneID alloc] initWithZoneName:@"testzone" ownerName:CKCurrentUserDefaultName]
49 encItem:nil
50 wrappedkey:nil
51 generationCount:1
52 encver:0]
53 action:@""
54 state:@""
55 waitUntil:nil
56 accessGroup:@"defaultgroup"];
57 }
58
59 - (void)tearDown {
60 [super tearDown];
61 self.rl = nil;
62 self.conf = nil;
63 self.oqe = nil;
64 }
65
66 - (int) get:(NSDictionary *)dict key:(NSString *)key {
67 id obj = dict[key];
68 XCTAssertNotNil(obj, "Key %@ is in the dictionary", key);
69 XCTAssert([obj isKindOfClass:[NSNumber class]], "Value for %@ is an NSNumber (%@)", key, [obj class]);
70 XCTAssertGreaterThan([obj intValue], 0, "Value for %@ is at least non-zero", key);
71 return [obj intValue];
72 }
73
74 - (void) testConfig {
75 [self get:[self.rl config] key:@"rateAll"];
76 [self get:[self.rl config] key:@"rateGroup"];
77 [self get:[self.rl config] key:@"rateUUID"];
78 [self get:[self.rl config] key:@"capacityAll"];
79 [self get:[self.rl config] key:@"capacityGroup"];
80 [self get:[self.rl config] key:@"capacityUUID"];
81 [self get:[self.rl config] key:@"trimSize"];
82 [self get:[self.rl config] key:@"trimTime"];
83 }
84
85 - (void) testBasics {
86 NSDate *date = [NSDate date];
87 NSDate *limit = nil;
88
89 XCTAssertEqual([self.rl judge:self.oqe at:date limitTime:&limit], 0, "judge single item");
90 XCTAssertNil(limit, "NSDate argument nil for zero result");
91 XCTAssertEqual([self.rl stateSize], 3UL, "RL contains three nodes after single judgment");
92 [self.rl reset];
93 XCTAssertEqual([self.rl stateSize], 0UL, "RL is empty after reset");
94 }
95
96 - (void)testAll {
97 NSDate *date = [NSDate date];
98 NSDate *limit = nil;
99 int capacityAll = [self get:[self.rl config] key:@"capacityAll"];
100 int rateAll = [self get:[self.rl config] key:@"rateAll"];
101
102 for (int idx = 0; idx < capacityAll; ++idx) {
103 self.oqe.accessgroup = [NSString stringWithFormat:@"%d", idx];
104 self.oqe.uuid = [NSString stringWithFormat:@"%d", idx];
105 XCTAssertEqual([self.rl judge:self.oqe at:date limitTime:&limit], 0, "Repeat (%d) All succeeds, limit %d", idx, capacityAll);
106 XCTAssertNil(limit, "Time nil while under All limit");
107 }
108
109 self.oqe.accessgroup = @"Ga";
110 self.oqe.uuid = @"Ua";
111 XCTAssertEqual([self.rl judge:self.oqe at:date limitTime:&limit], 1, "Repeat All implies congestion");
112 int delta = [limit timeIntervalSinceDate:date];
113 XCTAssert(delta > 0 && delta <= rateAll, "send-OK at most one All token into the future");
114 self.oqe.accessgroup = @"Gb";
115 self.oqe.uuid = @"Ub";
116 XCTAssertEqual([self.rl judge:self.oqe at:date limitTime:&limit], 1, "Repeat All, still congested");
117 delta = [limit timeIntervalSinceDate:date];
118 XCTAssert(delta > rateAll && delta <= (2 * rateAll), "send-OK between one and two All tokens into the future");
119
120 self.oqe.accessgroup = @"Gc";
121 self.oqe.uuid = @"Uc";
122 date = [limit dateByAddingTimeInterval:rateAll];
123 XCTAssertEqual([self.rl judge:self.oqe at:date limitTime:&limit], 0, "Repeat All is fine after waiting");
124 XCTAssertEqual([self.rl stateSize], (unsigned long)(2 * (capacityAll + 3) + 1), "state size is %d", 2 * (capacityAll + 3) + 1);
125 }
126
127 - (void)testGroup {
128 NSDate *date = [NSDate date];
129 NSDate *limit = nil;
130 int capacityGroup = [self get:[self.rl config] key:@"capacityGroup"];
131 int rateGroup = [self get:[self.rl config] key:@"rateGroup"];
132
133 for (int idx = 0; idx < capacityGroup; ++idx) {
134 self.oqe.uuid = [NSString stringWithFormat:@"%d",idx];
135 XCTAssertEqual([self.rl judge:self.oqe at:date limitTime:&limit], 0, "Repeat (%d) Group succeeds, limit %d", idx, capacityGroup);
136 XCTAssertNil(limit, "sendTime nil while under Group limit");
137 }
138
139 self.oqe.uuid = @"a";
140 XCTAssertEqual([self.rl judge:self.oqe at:date limitTime:&limit], 2, "Repeat Group entry not good");
141 int delta = [limit timeIntervalSinceDate:date];
142 XCTAssert(delta > 0 && delta <= rateGroup, "send-OK at most one Group token into the future");
143
144 self.oqe.uuid = @"b";
145 XCTAssertEqual([self.rl judge:self.oqe at:date limitTime:&limit], 3, "Repeat Group entry still not good");
146 delta = [limit timeIntervalSinceDate:date];
147 XCTAssert(delta > rateGroup && delta <= (2 * rateGroup), "send-OK between one and two Group tokens into the future");
148 self.oqe.uuid = @"c";
149 XCTAssertEqual([self.rl judge:self.oqe at:date limitTime:&limit], 3, "Repeat Group entry extra bad after not quitting");
150 delta = [limit timeIntervalSinceDate:date];
151 XCTAssert(delta > (2 * rateGroup) && delta <= (3 * rateGroup), "send-OK between two and three Group tokens into the future");
152
153 self.oqe.uuid = @"d";
154 date = [limit dateByAddingTimeInterval:rateGroup];
155 XCTAssertEqual([self.rl judge:self.oqe at:date limitTime:&limit], 0, "Repeat Group is fine after waiting");
156 XCTAssertEqual([self.rl stateSize], (unsigned long)(capacityGroup + 6), "State size is %d", capacityGroup + 6);
157
158 }
159
160 - (void)testUUID {
161 NSDate *date = [NSDate date];
162 NSDate *limit = nil;
163 int capacityUUID = [self get:[self.rl config] key:@"capacityUUID"];
164 int rateUUID = [self get:[self.rl config] key:@"rateUUID"];
165
166 for (int idx = 0; idx < capacityUUID; ++idx) {
167 XCTAssertEqual([self.rl judge:self.oqe at:date limitTime:&limit], 0, "Repeat (%d) UUID succeeds, limit %d", idx, capacityUUID);
168 XCTAssertNil(limit, "Time unmodified while under UUID limit");
169 }
170
171 XCTAssertEqual([self.rl judge:self.oqe at:date limitTime:&limit], 4, "Repeat UUID over limit is bad behavior");
172 int delta = [limit timeIntervalSinceDate:date];
173 XCTAssert(delta > 0 && delta <= rateUUID, "Received send-OK at most one UUID token into the future");
174 XCTAssertEqual([self.rl judge:self.oqe at:date limitTime:&limit], 4, "Repeat over-limit UUID is bad behavior");
175 delta = [limit timeIntervalSinceDate:date];
176 XCTAssert(delta > rateUUID && delta <= (2 * rateUUID), "send-OK between one and two UUID tokens into the future");
177
178 // test UUID success after borrow
179 date = [limit dateByAddingTimeInterval:rateUUID];
180 XCTAssertEqual([self.rl judge:self.oqe at:date limitTime:&limit], 0, "Repeat UUID is fine after waiting");
181 XCTAssertEqual([self.rl stateSize], 3UL, "state size is 3");
182
183 }
184
185 - (void)testTrimTime {
186 NSDate *date = [NSDate date];
187 NSDate *limit = nil;
188 int trimTime = [self get:[self.rl config] key:@"trimTime"];
189 int capacityAll = [self get:[self.rl config] key:@"capacityAll"];
190
191 for (int idx = 0; idx < capacityAll; ++idx) {
192 self.oqe.accessgroup = [NSString stringWithFormat:@"%d", idx];
193 self.oqe.uuid = [NSString stringWithFormat:@"%d", idx];
194 XCTAssertEqual([self.rl judge:self.oqe at:date limitTime:&limit], 0, "Repeat (%d) All succeeds, limit %d", idx, capacityAll);
195 XCTAssertNil(limit, "Time nil while under All limit");
196 }
197
198 XCTAssertEqual([self.rl stateSize], (unsigned long)(2 * capacityAll + 1), "state size is %d", 2 * capacityAll + 1);
199
200 date = [date dateByAddingTimeInterval:trimTime + 1];
201 XCTAssertEqual([self.rl judge:self.oqe at:date limitTime:&limit], 0, "Entry addition succeeds after long silence");
202 XCTAssertEqual([self.rl stateSize], 3UL, "Trim has wiped out all buckets except from call immediately before");
203 }
204
205 - (void)testTrimSize {
206 NSDate *date = [NSDate date];
207 NSDate *limit = nil;
208 NSDate *compare = [date copy];
209 int trimSize = [self get:[self.rl config] key:@"trimSize"];
210 int trimTime = [self get:[self.rl config] key:@"trimTime"];
211 int rateAll = [self get:[self.rl config] key:@"rateAll"];
212
213 self.oqe.accessgroup = @"a";
214 self.oqe.uuid = @"a";
215 XCTAssertEqual([self.rl judge:self.oqe at:date limitTime:&limit], 0, "Adding single item, no problem");
216
217 // Cannot fill state completely due to repeated judgements, it'll trigger overload otherwise
218 for (int idx = 0; [self.rl stateSize] < (unsigned long)(trimSize - 2); ++idx) {
219 self.oqe.accessgroup = [NSString stringWithFormat:@"%d", idx];
220 self.oqe.uuid = [NSString stringWithFormat:@"%d", idx];
221 for (int i = 0; i < rateAll; ++i) {
222 XCTAssertLessThan([self.rl judge:self.oqe at:date limitTime:&limit], 5, "Adding items and hitting their dates");
223 date = [compare copy];
224 }
225 }
226
227 unsigned long statesize = [self.rl stateSize];
228
229 self.oqe.accessgroup = @"b";
230 self.oqe.uuid = @"b";
231 XCTAssertLessThan([self.rl judge:self.oqe at:date limitTime:&limit], 5, "Top off state");
232
233 date = [compare dateByAddingTimeInterval:trimTime];
234
235 XCTAssertLessThan([self.rl judge:self.oqe at:date limitTime:&limit], 5, "New entry after at least one has expired fits");
236 XCTAssertEqual([self.rl stateSize], statesize, "Item 'a' was trimmed and 'b' got added");
237 }
238
239 - (void)testOverload {
240 NSDate *date = [NSDate date];
241 NSDate *limit = nil;
242 int trimSize = [self get:[self.rl config] key:@"trimSize"];
243 //int rateAll = [self get:[self.rl config] key:@"rateAll"];;
244 int overloadDuration = [self get:[self.rl config] key:@"overloadDuration"];;
245
246 for (int idx = 0; idx < (trimSize / 2); ++idx) {
247 self.oqe.accessgroup = [NSString stringWithFormat:@"%d", idx];
248 self.oqe.uuid = [NSString stringWithFormat:@"%d", idx];
249 XCTAssertLessThan([self.rl judge:self.oqe at:date limitTime:&limit], 5, "No overload triggered yet ramming data into RL");
250 }
251
252 NSDate *preOverload = [limit copy];
253 self.oqe.accessgroup = @"a";
254 self.oqe.uuid = @"a";
255 XCTAssertEqual([self.rl judge:self.oqe at:date limitTime:&limit], 5, "Triggered overload");
256 NSDate *postOverload = [preOverload dateByAddingTimeInterval:overloadDuration];
257 // NSDates are doubles so we add a little rounding error leeway
258 XCTAssertEqualWithAccuracy(limit.timeIntervalSinceReferenceDate, postOverload.timeIntervalSinceReferenceDate, 1, "Got expected overload duration");
259
260 unsigned long statesize = [self.rl stateSize];
261
262 self.oqe.accessgroup = @"b";
263 self.oqe.uuid = @"b";
264 XCTAssertEqual([self.rl judge:self.oqe at:date limitTime:&limit], 5, "Overload");
265 XCTAssertEqual(statesize, [self.rl stateSize], "No items added during overload");
266
267 XCTAssertEqual([self.rl judge:self.oqe at:limit limitTime:&limit], 0, "Judgment succeeds post-overload");
268 }
269
270 - (void)testEncoding {
271 NSDate* date = [NSDate date];
272 NSDate* limit = nil;
273 [self.rl judge:self.oqe at:date limitTime:&limit];
274
275 NSMutableData* data = [[NSMutableData alloc] init];
276 NSKeyedArchiver* encoder = [[NSKeyedArchiver alloc] initForWritingWithMutableData: data];
277 [encoder encodeObject: self.rl forKey:@"unneeded"];
278 [encoder finishEncoding];
279 XCTAssertNotNil(data, "Still have our data object");
280 XCTAssertTrue(data.length > 0u, "Encoder produced some data");
281
282 NSKeyedUnarchiver* decoder = [[NSKeyedUnarchiver alloc] initForReadingWithData: data];
283 decoder.requiresSecureCoding = YES;
284 CKKSRateLimiter* rl = [decoder decodeObjectOfClass: [CKKSRateLimiter class] forKey:@"unneeded"];
285 XCTAssertNotNil(rl, "Decoded data into a CKKSRateLimiter");
286
287 XCTAssertEqualObjects(self.rl, rl, "RateLimiter objects are equal after encoding/decoding");
288 }
289
290 @end
291
292 #endif //OCTAGON