]>
Commit | Line | Data |
---|---|---|
866f8763 A |
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> | |
ecaf5866 | 27 | #import <Foundation/NSKeyedArchiver_Private.h> |
866f8763 A |
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 | ||
b54c578e | 67 | - (int)get:(NSDictionary *)dict key:(NSString *)key { |
866f8763 A |
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 | ||
b54c578e A |
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 { | |
866f8763 A |
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 | ||
b54c578e | 94 | - (void)testBasics { |
866f8763 A |
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"];; | |
b54c578e | 253 | unsigned overloadDuration = [self getUnsigned:[self.rl config] key:@"overloadDuration"];; |
866f8763 A |
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 | ||
ecaf5866 | 284 | NSKeyedArchiver* encoder = [[NSKeyedArchiver alloc] initRequiringSecureCoding:YES]; |
866f8763 | 285 | [encoder encodeObject: self.rl forKey:@"unneeded"]; |
ecaf5866 | 286 | NSData* data = encoder.encodedData; |
866f8763 A |
287 | XCTAssertNotNil(data, "Still have our data object"); |
288 | XCTAssertTrue(data.length > 0u, "Encoder produced some data"); | |
289 | ||
ecaf5866 | 290 | NSKeyedUnarchiver* decoder = [[NSKeyedUnarchiver alloc] initForReadingFromData: data error:nil]; |
866f8763 A |
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 |