2 * Copyright (c) 2017 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
24 #import <XCTest/XCTest.h>
25 #import <TrustedPeers/TrustedPeers.h>
26 #import "TPDummySigningKey.h"
27 #import "TPDummyDecrypter.h"
28 #import "TPDummyEncrypter.h"
30 @interface TPModelTests : XCTestCase
32 @property (nonatomic, strong) TPModel *model;
33 @property (nonatomic, strong) TPPolicyDocument *policyDocV1;
34 @property (nonatomic, strong) TPPolicyDocument *policyDocV2;
35 @property (nonatomic, strong) NSString *secretName;
36 @property (nonatomic, strong) NSData *secretKey;
40 @implementation TPModelTests
42 - (TPModel *)makeModel
44 id<TPDecrypter> decrypter = [TPDummyDecrypter dummyDecrypter];
45 TPModel *model = [[TPModel alloc] initWithDecrypter:decrypter];
46 [model registerPolicyDocument:self.policyDocV1];
47 [model registerPolicyDocument:self.policyDocV2];
53 self.secretName = @"foo";
54 TPDummyEncrypter *encrypter = [TPDummyEncrypter dummyEncrypterWithKey:[@"sekritkey" dataUsingEncoding:NSUTF8StringEncoding]];
55 self.secretKey = encrypter.decryptionKey;
56 NSData *redaction = [TPPolicyDocument redactionWithEncrypter:encrypter
57 modelToCategory:@[ @{ @"prefix": @"iCycle", @"category": @"full" } ]
59 introducersByCategory:nil
63 = [TPPolicyDocument policyDocWithVersion:1
65 @{ @"prefix": @"iPhone", @"category": @"full" },
66 @{ @"prefix": @"iPad", @"category": @"full" },
67 @{ @"prefix": @"Mac", @"category": @"full" },
68 @{ @"prefix": @"iMac", @"category": @"full" },
69 @{ @"prefix": @"AppleTV", @"category": @"tv" },
70 @{ @"prefix": @"Watch", @"category": @"watch" },
73 @"WiFi": @[ @"full", @"tv", @"watch" ],
74 @"SafariCreditCards": @[ @"full" ],
75 @"PCSEscrow": @[ @"full" ]
77 introducersByCategory:@{
78 @"full": @[ @"full" ],
79 @"tv": @[ @"full", @"tv" ],
80 @"watch": @[ @"full", @"watch" ]
83 self.secretName: redaction
85 hashAlgo:kTPHashAlgoSHA256];
88 = [TPPolicyDocument policyDocWithVersion:2
90 @{ @"prefix": @"iCycle", @"category": @"full" }, // new
91 @{ @"prefix": @"iPhone", @"category": @"full" },
92 @{ @"prefix": @"iPad", @"category": @"full" },
93 @{ @"prefix": @"Mac", @"category": @"full" },
94 @{ @"prefix": @"iMac", @"category": @"full" },
95 @{ @"prefix": @"AppleTV", @"category": @"tv" },
96 @{ @"prefix": @"Watch", @"category": @"watch" },
99 @"WiFi": @[ @"full", @"tv", @"watch" ],
100 @"SafariCreditCards": @[ @"full" ],
101 @"PCSEscrow": @[ @"full" ]
103 introducersByCategory:@{
104 @"full": @[ @"full" ],
105 @"tv": @[ @"full", @"tv" ],
106 @"watch": @[ @"full", @"watch" ]
109 hashAlgo:kTPHashAlgoSHA256];
111 self.model = [self makeModel];
114 - (TPPeerPermanentInfo *)makePeerWithMachineID:(NSString *)machineID
116 return [self makePeerWithMachineID:machineID modelID:@"iPhone" epoch:1 key:machineID];
119 - (TPPeerPermanentInfo *)makePeerWithMachineID:(NSString *)machineID
120 modelID:(NSString *)modelID
121 epoch:(TPCounter)epoch
124 NSData *keyData = [key dataUsingEncoding:NSUTF8StringEncoding];
125 id<TPSigningKey> trustSigningKey = [[TPDummySigningKey alloc] initWithPublicKeyData:keyData];
126 TPPeerPermanentInfo *permanentInfo
127 = [TPPeerPermanentInfo permanentInfoWithMachineID:machineID
130 trustSigningKey:trustSigningKey
131 peerIDHashAlgo:kTPHashAlgoSHA256
133 [self.model registerPeerWithPermanentInfo:permanentInfo];
135 TPPeerStableInfo *stableInfo = [self.model createStableInfoWithDictionary:@{}
136 policyVersion:self.policyDocV1.policyVersion
137 policyHash:self.policyDocV1.policyHash
139 forPeerWithID:permanentInfo.peerID
141 [self.model updateStableInfo:stableInfo forPeerWithID:permanentInfo.peerID];
142 return permanentInfo;
145 static BOOL circleEquals(TPCircle *circle, NSArray<NSString*> *includedPeerIDs, NSArray<NSString*> *excludedPeerIDs)
147 return [circle isEqualToCircle:[TPCircle circleWithIncludedPeerIDs:includedPeerIDs excludedPeerIDs:excludedPeerIDs]];
150 - (void)testModelBasics
152 NSString *A = [self makePeerWithMachineID:@"aaa"].peerID;
153 NSString *B = [self makePeerWithMachineID:@"bbb"].peerID;
154 NSString *C = [self makePeerWithMachineID:@"ccc"].peerID;
158 // A trusts B, establishes clique
159 circle = [self.model advancePeerWithID:A addingPeerIDs:@[B] removingPeerIDs:@[] createClique:^NSString *{
162 XCTAssert(circleEquals(circle, @[A, B], @[]));
165 circle = [self.model advancePeerWithID:B addingPeerIDs:@[A] removingPeerIDs:@[] createClique:nil];
166 XCTAssert(circleEquals(circle, @[A, B], @[]));
169 circle = [self.model advancePeerWithID:A addingPeerIDs:@[C] removingPeerIDs:@[] createClique:nil];
170 XCTAssert(circleEquals(circle, @[A, B, C], @[]));
173 circle = [self.model advancePeerWithID:C addingPeerIDs:@[A] removingPeerIDs:@[] createClique:nil];
174 XCTAssert(circleEquals(circle, @[A, B, C], @[]));
176 // Updating B (B should now trust C)
177 circle = [self.model advancePeerWithID:B addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
178 XCTAssert(circleEquals(circle, @[A, B, C], @[]));
180 // Updating B again (should be no change)
181 circle = [self.model advancePeerWithID:B addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
182 XCTAssert(circleEquals(circle, @[A, B, C], @[]));
184 // A decides to exclude B
185 circle = [self.model advancePeerWithID:A addingPeerIDs:nil removingPeerIDs:@[B] createClique:nil];
186 XCTAssert(circleEquals(circle, @[A, C], @[B]));
188 // Updating C (C should now exclude B)
189 circle = [self.model advancePeerWithID:C addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
190 XCTAssert(circleEquals(circle, @[A, C], @[B]));
192 // Updating B (B should now exclude itself and include nobody)
193 circle = [self.model advancePeerWithID:B addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
194 XCTAssert(circleEquals(circle, @[], @[B]));
196 // Updating B again (should be no change)
197 circle = [self.model advancePeerWithID:B addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
198 XCTAssert(circleEquals(circle, @[], @[B]));
200 // C decides to exclude itself
201 circle = [self.model advancePeerWithID:C addingPeerIDs:nil removingPeerIDs:@[C] createClique:nil];
202 XCTAssert(circleEquals(circle, @[], @[C]));
204 // Updating C (should be no change)
205 circle = [self.model advancePeerWithID:C addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
206 XCTAssert(circleEquals(circle, @[], @[C]));
208 // Updating A (A should now exclude C)
209 circle = [self.model advancePeerWithID:A addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
210 XCTAssert(circleEquals(circle, @[A], @[B, C]));
213 - (void)testPeerReplacement
215 NSString *A = [self makePeerWithMachineID:@"aaa"].peerID;
216 NSString *B = [self makePeerWithMachineID:@"bbb"].peerID;
217 NSString *C = [self makePeerWithMachineID:@"ccc"].peerID;
221 // A trusts B, establishes clique. A is in a drawer.
222 circle = [self.model advancePeerWithID:A addingPeerIDs:@[B] removingPeerIDs:@[] createClique:^NSString *{
225 XCTAssert(circleEquals(circle, @[A, B], @[]));
228 circle = [self.model advancePeerWithID:B addingPeerIDs:@[A] removingPeerIDs:@[] createClique:nil];
229 XCTAssert(circleEquals(circle, @[A, B], @[]));
231 // B decides to replace itself with C.
232 circle = [self.model advancePeerWithID:B addingPeerIDs:@[C] removingPeerIDs:@[B] createClique:nil];
233 XCTAssert(circleEquals(circle, @[C], @[B]));
235 // B should be able to update itself without forgetting it trusts C.
236 circle = [self.model advancePeerWithID:B addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
237 XCTAssert(circleEquals(circle, @[C], @[B]));
239 // When A wakes up, it should trust C instead of B.
240 circle = [self.model advancePeerWithID:A addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
241 XCTAssert(circleEquals(circle, @[A, C], @[B]));
246 TPPeerPermanentInfo *aaa = [self makePeerWithMachineID:@"aaa"];
247 TPPeerPermanentInfo *bbb = [self makePeerWithMachineID:@"bbb"];
248 TPPeerPermanentInfo *ccc = [self makePeerWithMachineID:@"ccc"];
250 NSString *A = aaa.peerID;
251 NSString *B = bbb.peerID;
252 NSString *C = ccc.peerID;
256 // A establishes clique.
257 circle = [self.model advancePeerWithID:A addingPeerIDs:nil removingPeerIDs:nil createClique:^NSString *{
260 XCTAssert(circleEquals(circle, @[A], @[]));
263 circle = [self.model advancePeerWithID:B addingPeerIDs:@[A] removingPeerIDs:@[] createClique:nil];
264 XCTAssert(circleEquals(circle, @[A, B], @[]));
267 circle = [self.model advancePeerWithID:C addingPeerIDs:@[A] removingPeerIDs:@[] createClique:nil];
268 XCTAssert(circleEquals(circle, @[A, C], @[]));
270 // B gets a voucher from A
271 TPVoucher *voucher = [self.model createVoucherForCandidate:bbb withSponsorID:A error:NULL];
272 XCTAssertNotNil(voucher);
273 XCTAssertEqual(TPResultOk, [self.model registerVoucher:voucher]);
275 // Updating C, it sees the voucher and now trusts B
276 circle = [self.model advancePeerWithID:C addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
277 XCTAssert(circleEquals(circle, @[A, B, C], @[]));
279 // Updating A, it sees the voucher (sponsored by A itself) and now trusts B.
280 // (A updating its dynamicInfo also expires the voucher.)
281 circle = [self.model advancePeerWithID:A addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
282 XCTAssert(circleEquals(circle, @[A, B], @[]));
285 - (void)testExpiredVoucher
287 TPPeerPermanentInfo *aaa = [self makePeerWithMachineID:@"aaa"];
288 TPPeerPermanentInfo *bbb = [self makePeerWithMachineID:@"bbb"];
289 TPPeerPermanentInfo *ccc = [self makePeerWithMachineID:@"ccc"];
290 TPPeerPermanentInfo *ddd = [self makePeerWithMachineID:@"ddd"];
292 NSString *A = aaa.peerID;
293 NSString *B = bbb.peerID;
294 NSString *C = ccc.peerID;
295 NSString *D = ddd.peerID;
299 // A establishes clique.
300 circle = [self.model advancePeerWithID:A addingPeerIDs:nil removingPeerIDs:nil createClique:^NSString *{
303 XCTAssert(circleEquals(circle, @[A], @[]));
306 circle = [self.model advancePeerWithID:B addingPeerIDs:@[A] removingPeerIDs:@[] createClique:nil];
307 XCTAssert(circleEquals(circle, @[A, B], @[]));
310 circle = [self.model advancePeerWithID:C addingPeerIDs:@[A] removingPeerIDs:@[] createClique:nil];
311 XCTAssert(circleEquals(circle, @[A, C], @[]));
313 // B gets a voucher from A (but doesn't register the voucher yet because A would notice it)
314 TPVoucher *voucher = [self.model createVoucherForCandidate:bbb withSponsorID:A error:NULL];
316 // A advances its clock by deciding to trust D
317 circle = [self.model advancePeerWithID:A addingPeerIDs:@[D] removingPeerIDs:nil createClique:nil];
318 XCTAssert(circleEquals(circle, @[A, D], @[]));
320 // Register the voucher, which is now expired because A has advanced its clock
321 [self.model registerVoucher:voucher];
323 // Updating C, it ignores the expired voucher for B
324 circle = [self.model advancePeerWithID:C addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
325 XCTAssert(circleEquals(circle, @[A, C, D], @[]));
328 - (void)testVoucherWithBadSignature
330 TPPeerPermanentInfo *aaa = [self makePeerWithMachineID:@"aaa"];
331 TPPeerPermanentInfo *bbb = [self makePeerWithMachineID:@"bbb"];
333 NSString *A = aaa.peerID;
334 NSString *B = bbb.peerID;
338 // A establishes clique.
339 circle = [self.model advancePeerWithID:A addingPeerIDs:nil removingPeerIDs:nil createClique:^NSString *{
342 XCTAssert(circleEquals(circle, @[A], @[]));
345 circle = [self.model advancePeerWithID:B addingPeerIDs:@[A] removingPeerIDs:@[] createClique:nil];
346 XCTAssert(circleEquals(circle, @[A, B], @[]));
348 // B gets a voucher from A, but signed by B's key
349 TPVoucher *voucher = [TPVoucher voucherWithBeneficiaryID:B
351 clock:[self.model getDynamicInfoForPeerWithID:A].clock
352 trustSigningKey:bbb.trustSigningKey
354 XCTAssertNotNil(voucher);
355 XCTAssertEqual(TPResultSignatureMismatch, [self.model registerVoucher:voucher]);
358 - (void)testVoucherPolicy
360 TPPeerPermanentInfo *aaa = [self makePeerWithMachineID:@"aaa" modelID:@"watch" epoch:1 key:@"aaa"];
361 TPPeerPermanentInfo *bbb = [self makePeerWithMachineID:@"bbb"];
363 NSString *A = aaa.peerID;
365 // B is a phone trying to get a voucher from A which is a watch
366 TPVoucher *voucher = [self.model createVoucherForCandidate:bbb withSponsorID:A error:NULL];
367 XCTAssertNil(voucher);
370 - (void)testDynamicInfoReplay
372 NSString *A = [self makePeerWithMachineID:@"aaa"].peerID;
373 NSString *B = [self makePeerWithMachineID:@"bbb"].peerID;
377 // A establishes clique, trusts B.
378 circle = [self.model advancePeerWithID:A addingPeerIDs:@[B] removingPeerIDs:nil createClique:^NSString *{
381 XCTAssert(circleEquals(circle, @[A, B], @[]));
383 // Attacker snapshots A's dynamicInfo
384 TPPeerDynamicInfo *dyn = [self.model getDynamicInfoForPeerWithID:A];
387 circle = [self.model advancePeerWithID:A addingPeerIDs:nil removingPeerIDs:@[B] createClique:nil];
388 XCTAssert(circleEquals(circle, @[A], @[B]));
390 // Attacker replays the old snapshot
391 XCTAssertEqual(TPResultClockViolation, [self.model updateDynamicInfo:dyn forPeerWithID:A]);
393 circle = [self.model getCircleForPeerWithID:A];
394 XCTAssert(circleEquals(circle, @[A], @[B]));
397 - (void)testPhoneApprovingWatch
399 NSString *phoneA = [self makePeerWithMachineID:@"phoneA" modelID:@"iPhone7,1" epoch:1 key:@"phoneA"].peerID;
400 NSString *watch = [self makePeerWithMachineID:@"watch" modelID:@"Watch1,1" epoch:1 key:@"watch"].peerID;
404 // phoneA establishes clique, trusts watch.
405 circle = [self.model advancePeerWithID:phoneA addingPeerIDs:@[watch] removingPeerIDs:nil createClique:^NSString *{
408 XCTAssert(circleEquals(circle, @[phoneA, watch], @[]));
411 - (void)testWatchApprovingPhone
413 NSString *phoneA = [self makePeerWithMachineID:@"phoneA" modelID:@"iPhone7,1" epoch:1 key:@"phoneA"].peerID;
414 NSString *phoneB = [self makePeerWithMachineID:@"phoneB" modelID:@"iPhone7,1" epoch:1 key:@"phoneB"].peerID;
415 NSString *watch = [self makePeerWithMachineID:@"watch" modelID:@"Watch1,1" epoch:1 key:@"watch"].peerID;
419 // phoneA establishes clique, trusts watch.
420 circle = [self.model advancePeerWithID:phoneA addingPeerIDs:@[watch] removingPeerIDs:nil createClique:^NSString *{
423 XCTAssert(circleEquals(circle, @[phoneA, watch], @[]));
425 // watch trusts phoneA and phoneB
426 circle = [self.model advancePeerWithID:watch addingPeerIDs:@[phoneA, phoneB] removingPeerIDs:nil createClique:nil];
427 XCTAssert(circleEquals(circle, @[phoneA, phoneB, watch], @[]));
429 // phoneA updates, and it should ignore phoneB, so no change.
430 circle = [self.model advancePeerWithID:phoneA addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
431 XCTAssert(circleEquals(circle, @[phoneA, watch], @[]));
434 - (void)testNilCreateClique
436 NSString *A = [self makePeerWithMachineID:@"aaa"].peerID;
440 // Try to establish dynamicInfo without providing createClique
441 circle = [self.model advancePeerWithID:A addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
442 XCTAssertNil(circle);
445 - (void)testCliqueConvergence
447 NSString *A = [self makePeerWithMachineID:@"aaa"].peerID;
448 NSString *B = [self makePeerWithMachineID:@"bbb"].peerID;
452 // A establishes clique1
453 circle = [self.model advancePeerWithID:A addingPeerIDs:@[] removingPeerIDs:@[] createClique:^NSString *{
456 XCTAssert(circleEquals(circle, @[A], @[]));
457 XCTAssert([[self.model getDynamicInfoForPeerWithID:A].clique isEqualToString:@"clique1"]);
459 // B establishes clique2
460 circle = [self.model advancePeerWithID:B addingPeerIDs:@[] removingPeerIDs:@[] createClique:^NSString *{
463 XCTAssert(circleEquals(circle, @[B], @[]));
464 XCTAssert([[self.model getDynamicInfoForPeerWithID:B].clique isEqualToString:@"clique2"]);
466 // A trusts B. A should now switch to clique2, which is later than clique1 in lexical order.
467 circle = [self.model advancePeerWithID:A addingPeerIDs:@[B] removingPeerIDs:@[] createClique:nil];
468 XCTAssert(circleEquals(circle, @[A, B], @[]));
469 XCTAssert([[self.model getDynamicInfoForPeerWithID:A].clique isEqualToString:@"clique2"]);
472 - (void)testRemovalCounts
474 NSString *A = [self makePeerWithMachineID:@"aaa"].peerID;
475 NSString *B = [self makePeerWithMachineID:@"bbb"].peerID;
476 NSString *C = [self makePeerWithMachineID:@"ccc"].peerID;
478 // A establishes clique with B and C
479 [self.model advancePeerWithID:A addingPeerIDs:@[B, C] removingPeerIDs:@[] createClique:^NSString *{
482 XCTAssertEqual(0ULL, [self.model getDynamicInfoForPeerWithID:A].removals);
485 [self.model advancePeerWithID:B addingPeerIDs:@[A] removingPeerIDs:@[] createClique:nil];
486 XCTAssertEqual(0ULL, [self.model getDynamicInfoForPeerWithID:B].removals);
489 [self.model advancePeerWithID:A addingPeerIDs:nil removingPeerIDs:@[C] createClique:nil];
490 XCTAssertEqual(1ULL, [self.model getDynamicInfoForPeerWithID:A].removals);
492 // B updates, and now shows 1 removal
493 [self.model advancePeerWithID:B addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
494 XCTAssertEqual(1ULL, [self.model getDynamicInfoForPeerWithID:B].removals);
497 - (void)testCommunicatingModels
499 TPPeerPermanentInfo *aaa = [self makePeerWithMachineID:@"aaa"];
500 TPPeerPermanentInfo *bbb = [self makePeerWithMachineID:@"bbb"];
501 TPPeerPermanentInfo *ccc = [self makePeerWithMachineID:@"ccc"];
503 NSString *A = aaa.peerID;
504 NSString *B = bbb.peerID;
505 NSString *C = ccc.peerID;
507 // A lives on self.model, where it trusts B and C
508 [self.model advancePeerWithID:A addingPeerIDs:@[B, C] removingPeerIDs:nil createClique:^NSString *{
512 // B lives on model2, where it trusts A
513 TPModel *model2 = [self makeModel];
514 [model2 registerPeerWithPermanentInfo:aaa];
515 [model2 registerPeerWithPermanentInfo:bbb];
516 [model2 updateStableInfo:[self.model getStableInfoForPeerWithID:A] forPeerWithID:A];
517 [model2 updateStableInfo:[self.model getStableInfoForPeerWithID:B] forPeerWithID:B];
518 [model2 advancePeerWithID:B addingPeerIDs:@[A] removingPeerIDs:nil createClique:^NSString *{
522 // A's circle and dynamicInfo are transmitted from model to model2
523 TPCircle *circle = [self.model getCircleForPeerWithID:A];
524 TPPeerDynamicInfo *dyn = [self.model getDynamicInfoForPeerWithID:A];
525 [model2 updateDynamicInfo:dyn forPeerWithID:A];
526 [model2 registerCircle:circle];
528 // B updates in model2, but C is not yet registered so is ignored.
529 circle = [model2 advancePeerWithID:B addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
530 XCTAssert(circleEquals(circle, @[A, B], @[]));
532 // Now C registers in model2
533 [model2 registerPeerWithPermanentInfo:ccc];
535 // B updates in model2, and now it trusts C.
536 circle = [model2 advancePeerWithID:B addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
537 XCTAssert(circleEquals(circle, @[A, B, C], @[]));
540 - (void)testCommunicatingModelsWithVouchers
542 TPPeerPermanentInfo *aaa = [self makePeerWithMachineID:@"aaa"];
543 TPPeerPermanentInfo *bbb = [self makePeerWithMachineID:@"bbb"];
544 TPPeerPermanentInfo *ccc = [self makePeerWithMachineID:@"ccc"];
546 NSString *A = aaa.peerID;
547 NSString *B = bbb.peerID;
548 NSString *C = ccc.peerID;
550 // A lives on self.model, where it trusts B
551 [self.model advancePeerWithID:A addingPeerIDs:@[B] removingPeerIDs:nil createClique:^NSString *{
555 // B lives on model2, where it trusts A
556 TPModel *model2 = [self makeModel];
557 [model2 registerPeerWithPermanentInfo:aaa];
558 [model2 registerPeerWithPermanentInfo:bbb];
559 [model2 updateStableInfo:[self.model getStableInfoForPeerWithID:A] forPeerWithID:A];
560 [model2 updateStableInfo:[self.model getStableInfoForPeerWithID:B] forPeerWithID:B];
561 [model2 advancePeerWithID:B addingPeerIDs:@[A] removingPeerIDs:nil createClique:^NSString *{
565 // A's circle and dynamicInfo are transmitted from model to model2
566 TPCircle *circle = [self.model getCircleForPeerWithID:A];
567 TPPeerDynamicInfo *dyn = [self.model getDynamicInfoForPeerWithID:A];
568 [model2 updateDynamicInfo:dyn forPeerWithID:A];
569 [model2 registerCircle:circle];
571 // A writes a voucher for C, and it is transmitted to model2
572 TPVoucher *voucher = [self.model createVoucherForCandidate:ccc withSponsorID:A error:NULL];
573 [model2 registerVoucher:voucher];
575 // B updates in model2, but C is not yet registered so is ignored.
576 circle = [model2 advancePeerWithID:B addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
577 XCTAssert(circleEquals(circle, @[A, B], @[]));
579 // Now C registers in model2
580 [model2 registerPeerWithPermanentInfo:ccc];
581 [model2 updateStableInfo:[self.model getStableInfoForPeerWithID:C] forPeerWithID:C];
583 // B updates in model2, and now it trusts C.
584 circle = [model2 advancePeerWithID:B addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
585 XCTAssert(circleEquals(circle, @[A, B, C], @[]));
588 - (void)testReregisterPeer
590 TPPeerPermanentInfo *aaa = [self makePeerWithMachineID:@"aaa"];
592 NSString *A = aaa.peerID;
594 [self.model advancePeerWithID:A addingPeerIDs:nil removingPeerIDs:nil createClique:^NSString *{
598 // Registering the peer again should not overwrite its dynamicInfo or other state.
599 [self.model registerPeerWithPermanentInfo:aaa];
600 XCTAssertNotNil([self.model getDynamicInfoForPeerWithID:A]);
603 - (void)testPeerAccessors
605 TPPeerPermanentInfo *aaa = [self makePeerWithMachineID:@"aaa"];
607 NSString *A = aaa.peerID;
609 XCTAssert([self.model hasPeerWithID:A]);
611 TPPeerPermanentInfo *aaa2 = [self.model getPermanentInfoForPeerWithID:A];
612 XCTAssertEqualObjects(aaa, aaa2);
614 TPPeerStableInfo *info = [self.model createStableInfoWithDictionary:@{ @"hello": @"world" }
620 XCTAssertEqual(TPResultOk, [self.model updateStableInfo:info forPeerWithID:A]);
622 XCTAssertEqualObjects([self.model getStableInfoForPeerWithID:A], info);
624 [self.model deletePeerWithID:A];
625 XCTAssertFalse([self.model hasPeerWithID:A]);
628 - (void)testCircleAccessors
630 TPCircle *circle = [TPCircle circleWithIncludedPeerIDs:@[@"A, B"] excludedPeerIDs:nil];
631 XCTAssertNil([self.model circleWithID:circle.circleID]);
632 [self.model registerCircle:circle];
633 XCTAssertNotNil([self.model circleWithID:circle.circleID]);
634 [self.model deleteCircleWithID:circle.circleID];
635 XCTAssertNil([self.model circleWithID:circle.circleID]);
638 - (void)testLatestEpoch
640 NSString *A = [self makePeerWithMachineID:@"aaa" modelID:@"iPhone" epoch:0 key:@"aaa"].peerID;
641 NSString *B = [self makePeerWithMachineID:@"bbb" modelID:@"iPhone" epoch:1 key:@"aaa"].peerID;
642 NSString *C = [self makePeerWithMachineID:@"ccc" modelID:@"iPhone" epoch:2 key:@"aaa"].peerID;
644 TPCounter epoch = [self.model latestEpochAmongPeerIDs:[NSSet setWithArray:@[A, B, C]]];
645 XCTAssertEqual(epoch, 2ULL);
648 - (void)testPeerStatus
650 NSString *A = [self makePeerWithMachineID:@"aaa" modelID:@"iPhone" epoch:0 key:@"aaa"].peerID;
651 NSString *B = [self makePeerWithMachineID:@"bbb" modelID:@"iPhone" epoch:0 key:@"bbb"].peerID;
652 NSString *C = [self makePeerWithMachineID:@"ccc" modelID:@"iPhone" epoch:0 key:@"ccc"].peerID;
653 NSString *D = [self makePeerWithMachineID:@"ddd" modelID:@"iPhone" epoch:1 key:@"ddd"].peerID;
654 NSString *E = [self makePeerWithMachineID:@"eee" modelID:@"iPhone" epoch:2 key:@"eee"].peerID;
656 XCTAssertEqual([self.model statusOfPeerWithID:A], 0);
658 [self.model advancePeerWithID:A addingPeerIDs:@[B, C] removingPeerIDs:@[] createClique:^NSString *{
661 XCTAssertEqual([self.model statusOfPeerWithID:A], 0);
663 [self.model advancePeerWithID:B addingPeerIDs:@[A, C] removingPeerIDs:@[] createClique:nil];
664 XCTAssertEqual([self.model statusOfPeerWithID:A], TPPeerStatusPartiallyReciprocated);
666 [self.model advancePeerWithID:C addingPeerIDs:@[A] removingPeerIDs:@[] createClique:nil];
667 XCTAssertEqual([self.model statusOfPeerWithID:A], TPPeerStatusPartiallyReciprocated | TPPeerStatusFullyReciprocated);
669 [self.model advancePeerWithID:C addingPeerIDs:@[] removingPeerIDs:@[A] createClique:nil];
670 XCTAssertEqual([self.model statusOfPeerWithID:A], TPPeerStatusPartiallyReciprocated | TPPeerStatusExcluded);
672 [self.model advancePeerWithID:A addingPeerIDs:@[] removingPeerIDs:@[] createClique:nil];
673 XCTAssertEqual([self.model statusOfPeerWithID:A], TPPeerStatusExcluded);
675 [self.model advancePeerWithID:B addingPeerIDs:@[D] removingPeerIDs:@[] createClique:nil];
676 XCTAssertEqual([self.model statusOfPeerWithID:B], TPPeerStatusPartiallyReciprocated | TPPeerStatusOutdatedEpoch);
678 [self.model advancePeerWithID:C addingPeerIDs:@[E] removingPeerIDs:@[] createClique:nil];
679 [self.model advancePeerWithID:B addingPeerIDs:@[] removingPeerIDs:@[] createClique:nil];
680 XCTAssertEqual([self.model statusOfPeerWithID:B], TPPeerStatusPartiallyReciprocated | TPPeerStatusOutdatedEpoch | TPPeerStatusAncientEpoch);
683 - (void)testCalculateUnusedCircleIDs
685 NSString *A = [self makePeerWithMachineID:@"aaa" modelID:@"iPhone" epoch:0 key:@"aaa"].peerID;
686 NSString *B = [self makePeerWithMachineID:@"bbb" modelID:@"iPhone" epoch:0 key:@"bbb"].peerID;
688 [self.model advancePeerWithID:A addingPeerIDs:@[B] removingPeerIDs:@[] createClique:^NSString *{
691 [self.model advancePeerWithID:B addingPeerIDs:@[B] removingPeerIDs:@[] createClique:nil];
693 NSSet<NSString*>* unused;
694 unused = [self.model calculateUnusedCircleIDs];
695 XCTAssertEqualObjects(unused, [NSSet set]);
697 NSString *circleID = [self.model getCircleForPeerWithID:A].circleID;
699 [self.model advancePeerWithID:A addingPeerIDs:@[] removingPeerIDs:@[B] createClique:nil];
701 unused = [self.model calculateUnusedCircleIDs];
702 XCTAssertEqualObjects(unused, [NSSet setWithObject:circleID]);
705 - (void)testGetPeerIDsTrustedByPeerWithID
707 NSString *A = [self makePeerWithMachineID:@"aaa" modelID:@"iPhone7,1" epoch:0 key:@"aaa"].peerID;
708 NSString *B = [self makePeerWithMachineID:@"bbb" modelID:@"iPhone6,2" epoch:0 key:@"bbb"].peerID;
709 NSString *C = [self makePeerWithMachineID:@"ccc" modelID:@"Watch1,1" epoch:0 key:@"ccc"].peerID;
710 [self makePeerWithMachineID:@"ddd" modelID:@"iPhone7,1" epoch:0 key:@"ddd"];
712 [self.model advancePeerWithID:A addingPeerIDs:@[B, C] removingPeerIDs:@[] createClique:^NSString *{
716 // Everyone can access WiFi. Only full peers can access SafariCreditCards
718 NSSet<NSString *>* peerIDs;
719 NSSet<NSString *>* expected;
721 peerIDs = [self.model getPeerIDsTrustedByPeerWithID:A toAccessView:@"WiFi" error:NULL];
722 expected = [NSSet setWithArray:@[A, B, C]];
723 XCTAssertEqualObjects(peerIDs, expected);
725 peerIDs = [self.model getPeerIDsTrustedByPeerWithID:A toAccessView:@"SafariCreditCards" error:NULL];
726 expected = [NSSet setWithArray:@[A, B]];
727 XCTAssertEqualObjects(peerIDs, expected);
730 - (void)testVectorClock
732 NSString *A = [self makePeerWithMachineID:@"aaa"].peerID;
733 NSString *B = [self makePeerWithMachineID:@"bbb"].peerID;
734 NSString *C = [self makePeerWithMachineID:@"ccc"].peerID;
736 [self.model advancePeerWithID:A addingPeerIDs:@[B] removingPeerIDs:@[] createClique:^NSString *{
739 [self.model advancePeerWithID:B addingPeerIDs:@[A] removingPeerIDs:@[] createClique:nil];
742 NSDictionary *expected;
744 dict = [self.model vectorClock];
745 expected = @{ A: @4, B: @5, C: @3 };
746 XCTAssertEqualObjects(dict, expected);
748 [self.model advancePeerWithID:C addingPeerIDs:@[A] removingPeerIDs:@[B] createClique:nil];
749 [self.model advancePeerWithID:A addingPeerIDs:@[] removingPeerIDs:@[] createClique:nil];
750 [self.model advancePeerWithID:B addingPeerIDs:@[] removingPeerIDs:@[] createClique:nil];
752 dict = [self.model vectorClock];
753 expected = @{ A: @7, B: @8, C: @6 };
754 XCTAssertEqualObjects(dict, expected);
757 - (void)testICycleApprovingPhoneWithNewPolicy
759 NSString *phoneA = [self makePeerWithMachineID:@"phoneA" modelID:@"iPhone7,1" epoch:1 key:@"phoneA"].peerID;
760 NSString *phoneB = [self makePeerWithMachineID:@"phoneB" modelID:@"iPhone7,1" epoch:1 key:@"phoneB"].peerID;
761 NSString *icycle = [self makePeerWithMachineID:@"icycle" modelID:@"iCycle1,1" epoch:1 key:@"icycle"].peerID;
765 // phoneA establishes clique, trusts icycle
766 circle = [self.model advancePeerWithID:phoneA addingPeerIDs:@[icycle] removingPeerIDs:nil createClique:^NSString *{
769 XCTAssert(circleEquals(circle, @[phoneA, icycle], @[]));
771 // icycle trusts phoneA and phoneB
772 circle = [self.model advancePeerWithID:icycle addingPeerIDs:@[phoneA, phoneB] removingPeerIDs:nil createClique:nil];
773 XCTAssert(circleEquals(circle, @[phoneA, phoneB, icycle], @[]));
775 // phoneA updates, and it doesn't know iCycles can approve phones, so it should ignore phoneB, so no change.
776 circle = [self.model advancePeerWithID:phoneA addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
777 XCTAssert(circleEquals(circle, @[phoneA, icycle], @[]));
779 // icycle presents a new policy that says iCycles can approve phones
780 TPPeerStableInfo *stableInfo = [self.model createStableInfoWithDictionary:@{}
781 policyVersion:self.policyDocV2.policyVersion
782 policyHash:self.policyDocV2.policyHash
786 [self.model updateStableInfo:stableInfo forPeerWithID:icycle];
788 // phoneA updates again, sees the new policy that says iCycles can approve phones, and now trusts phoneB
789 circle = [self.model advancePeerWithID:phoneA addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
790 XCTAssert(circleEquals(circle, @[phoneA, phoneB, icycle], @[]));
793 - (void)testICycleApprovingPhoneWithRedactedPolicy
795 NSString *phoneA = [self makePeerWithMachineID:@"phoneA" modelID:@"iPhone7,1" epoch:1 key:@"phoneA"].peerID;
796 NSString *phoneB = [self makePeerWithMachineID:@"phoneB" modelID:@"iPhone7,1" epoch:1 key:@"phoneB"].peerID;
797 NSString *icycle = [self makePeerWithMachineID:@"icycle" modelID:@"iCycle1,1" epoch:1 key:@"icycle"].peerID;
801 // phoneA establishes clique, trusts icycle
802 circle = [self.model advancePeerWithID:phoneA addingPeerIDs:@[icycle] removingPeerIDs:nil createClique:^NSString *{
805 XCTAssert(circleEquals(circle, @[phoneA, icycle], @[]));
807 // icycle trusts phoneA and phoneB
808 circle = [self.model advancePeerWithID:icycle addingPeerIDs:@[phoneA, phoneB] removingPeerIDs:nil createClique:nil];
809 XCTAssert(circleEquals(circle, @[phoneA, phoneB, icycle], @[]));
811 // phoneA updates, and it doesn't know iCycles can approve phones, so it should ignore phoneB, so no change.
812 circle = [self.model advancePeerWithID:phoneA addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
813 XCTAssert(circleEquals(circle, @[phoneA, icycle], @[]));
815 // icycle presents a new policy that says iCycles can approve phones
816 TPPeerStableInfo *stableInfo = [self.model createStableInfoWithDictionary:@{}
817 policyVersion:self.policyDocV1.policyVersion
818 policyHash:self.policyDocV1.policyHash
820 self.secretName: self.secretKey
824 [self.model updateStableInfo:stableInfo forPeerWithID:icycle];
826 // phoneA updates again, sees the new policy that says iCycles can approve phones, and now trusts phoneB
827 circle = [self.model advancePeerWithID:phoneA addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
828 XCTAssert(circleEquals(circle, @[phoneA, phoneB, icycle], @[]));