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