/* * Copyright (c) 2017 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #import <XCTest/XCTest.h> #import <TrustedPeers/TrustedPeers.h> #import "TPDummySigningKey.h" #import "TPDummyDecrypter.h" #import "TPDummyEncrypter.h" @interface TPModelTests : XCTestCase @property (nonatomic, strong) TPModel *model; @property (nonatomic, strong) TPPolicyDocument *policyDocV1; @property (nonatomic, strong) TPPolicyDocument *policyDocV2; @property (nonatomic, strong) NSString *secretName; @property (nonatomic, strong) NSData *secretKey; @end @implementation TPModelTests - (TPModel *)makeModel { id<TPDecrypter> decrypter = [TPDummyDecrypter dummyDecrypter]; TPModel *model = [[TPModel alloc] initWithDecrypter:decrypter]; [model registerPolicyDocument:self.policyDocV1]; [model registerPolicyDocument:self.policyDocV2]; return model; } - (void)setUp { self.secretName = @"foo"; TPDummyEncrypter *encrypter = [TPDummyEncrypter dummyEncrypterWithKey:[@"sekritkey" dataUsingEncoding:NSUTF8StringEncoding]]; self.secretKey = encrypter.decryptionKey; NSData *redaction = [TPPolicyDocument redactionWithEncrypter:encrypter modelToCategory:@[ @{ @"prefix": @"iCycle", @"category": @"full" } ] categoriesByView:nil introducersByCategory:nil error:NULL]; self.policyDocV1 = [TPPolicyDocument policyDocWithVersion:1 modelToCategory:@[ @{ @"prefix": @"iPhone", @"category": @"full" }, @{ @"prefix": @"iPad", @"category": @"full" }, @{ @"prefix": @"Mac", @"category": @"full" }, @{ @"prefix": @"iMac", @"category": @"full" }, @{ @"prefix": @"AppleTV", @"category": @"tv" }, @{ @"prefix": @"Watch", @"category": @"watch" }, ] categoriesByView:@{ @"WiFi": @[ @"full", @"tv", @"watch" ], @"SafariCreditCards": @[ @"full" ], @"PCSEscrow": @[ @"full" ] } introducersByCategory:@{ @"full": @[ @"full" ], @"tv": @[ @"full", @"tv" ], @"watch": @[ @"full", @"watch" ] } redactions:@{ self.secretName: redaction } hashAlgo:kTPHashAlgoSHA256]; self.policyDocV2 = [TPPolicyDocument policyDocWithVersion:2 modelToCategory:@[ @{ @"prefix": @"iCycle", @"category": @"full" }, // new @{ @"prefix": @"iPhone", @"category": @"full" }, @{ @"prefix": @"iPad", @"category": @"full" }, @{ @"prefix": @"Mac", @"category": @"full" }, @{ @"prefix": @"iMac", @"category": @"full" }, @{ @"prefix": @"AppleTV", @"category": @"tv" }, @{ @"prefix": @"Watch", @"category": @"watch" }, ] categoriesByView:@{ @"WiFi": @[ @"full", @"tv", @"watch" ], @"SafariCreditCards": @[ @"full" ], @"PCSEscrow": @[ @"full" ] } introducersByCategory:@{ @"full": @[ @"full" ], @"tv": @[ @"full", @"tv" ], @"watch": @[ @"full", @"watch" ] } redactions:@{} hashAlgo:kTPHashAlgoSHA256]; self.model = [self makeModel]; } - (TPPeerPermanentInfo *)makePeerWithMachineID:(NSString *)machineID { return [self makePeerWithMachineID:machineID modelID:@"iPhone" epoch:1 key:machineID]; } - (TPPeerPermanentInfo *)makePeerWithMachineID:(NSString *)machineID modelID:(NSString *)modelID epoch:(TPCounter)epoch key:(NSString *)key { NSData *keyData = [key dataUsingEncoding:NSUTF8StringEncoding]; id<TPSigningKey> trustSigningKey = [[TPDummySigningKey alloc] initWithPublicKeyData:keyData]; TPPeerPermanentInfo *permanentInfo = [TPPeerPermanentInfo permanentInfoWithMachineID:machineID modelID:modelID epoch:epoch trustSigningKey:trustSigningKey peerIDHashAlgo:kTPHashAlgoSHA256 error:NULL]; [self.model registerPeerWithPermanentInfo:permanentInfo]; TPPeerStableInfo *stableInfo = [self.model createStableInfoWithDictionary:@{} policyVersion:self.policyDocV1.policyVersion policyHash:self.policyDocV1.policyHash policySecrets:nil forPeerWithID:permanentInfo.peerID error:NULL]; [self.model updateStableInfo:stableInfo forPeerWithID:permanentInfo.peerID]; return permanentInfo; } static BOOL circleEquals(TPCircle *circle, NSArray<NSString*> *includedPeerIDs, NSArray<NSString*> *excludedPeerIDs) { return [circle isEqualToCircle:[TPCircle circleWithIncludedPeerIDs:includedPeerIDs excludedPeerIDs:excludedPeerIDs]]; } - (void)testModelBasics { NSString *A = [self makePeerWithMachineID:@"aaa"].peerID; NSString *B = [self makePeerWithMachineID:@"bbb"].peerID; NSString *C = [self makePeerWithMachineID:@"ccc"].peerID; TPCircle *circle; // A trusts B, establishes clique circle = [self.model advancePeerWithID:A addingPeerIDs:@[B] removingPeerIDs:@[] createClique:^NSString *{ return @"clique1"; }]; XCTAssert(circleEquals(circle, @[A, B], @[])); // B trusts A circle = [self.model advancePeerWithID:B addingPeerIDs:@[A] removingPeerIDs:@[] createClique:nil]; XCTAssert(circleEquals(circle, @[A, B], @[])); // A trusts C circle = [self.model advancePeerWithID:A addingPeerIDs:@[C] removingPeerIDs:@[] createClique:nil]; XCTAssert(circleEquals(circle, @[A, B, C], @[])); // C trusts A circle = [self.model advancePeerWithID:C addingPeerIDs:@[A] removingPeerIDs:@[] createClique:nil]; XCTAssert(circleEquals(circle, @[A, B, C], @[])); // Updating B (B should now trust C) circle = [self.model advancePeerWithID:B addingPeerIDs:nil removingPeerIDs:nil createClique:nil]; XCTAssert(circleEquals(circle, @[A, B, C], @[])); // Updating B again (should be no change) circle = [self.model advancePeerWithID:B addingPeerIDs:nil removingPeerIDs:nil createClique:nil]; XCTAssert(circleEquals(circle, @[A, B, C], @[])); // A decides to exclude B circle = [self.model advancePeerWithID:A addingPeerIDs:nil removingPeerIDs:@[B] createClique:nil]; XCTAssert(circleEquals(circle, @[A, C], @[B])); // Updating C (C should now exclude B) circle = [self.model advancePeerWithID:C addingPeerIDs:nil removingPeerIDs:nil createClique:nil]; XCTAssert(circleEquals(circle, @[A, C], @[B])); // Updating B (B should now exclude itself and include nobody) circle = [self.model advancePeerWithID:B addingPeerIDs:nil removingPeerIDs:nil createClique:nil]; XCTAssert(circleEquals(circle, @[], @[B])); // Updating B again (should be no change) circle = [self.model advancePeerWithID:B addingPeerIDs:nil removingPeerIDs:nil createClique:nil]; XCTAssert(circleEquals(circle, @[], @[B])); // C decides to exclude itself circle = [self.model advancePeerWithID:C addingPeerIDs:nil removingPeerIDs:@[C] createClique:nil]; XCTAssert(circleEquals(circle, @[], @[C])); // Updating C (should be no change) circle = [self.model advancePeerWithID:C addingPeerIDs:nil removingPeerIDs:nil createClique:nil]; XCTAssert(circleEquals(circle, @[], @[C])); // Updating A (A should now exclude C) circle = [self.model advancePeerWithID:A addingPeerIDs:nil removingPeerIDs:nil createClique:nil]; XCTAssert(circleEquals(circle, @[A], @[B, C])); } - (void)testPeerReplacement { NSString *A = [self makePeerWithMachineID:@"aaa"].peerID; NSString *B = [self makePeerWithMachineID:@"bbb"].peerID; NSString *C = [self makePeerWithMachineID:@"ccc"].peerID; TPCircle *circle; // A trusts B, establishes clique. A is in a drawer. circle = [self.model advancePeerWithID:A addingPeerIDs:@[B] removingPeerIDs:@[] createClique:^NSString *{ return @"clique1"; }]; XCTAssert(circleEquals(circle, @[A, B], @[])); // B trusts A circle = [self.model advancePeerWithID:B addingPeerIDs:@[A] removingPeerIDs:@[] createClique:nil]; XCTAssert(circleEquals(circle, @[A, B], @[])); // B decides to replace itself with C. circle = [self.model advancePeerWithID:B addingPeerIDs:@[C] removingPeerIDs:@[B] createClique:nil]; XCTAssert(circleEquals(circle, @[C], @[B])); // B should be able to update itself without forgetting it trusts C. circle = [self.model advancePeerWithID:B addingPeerIDs:nil removingPeerIDs:nil createClique:nil]; XCTAssert(circleEquals(circle, @[C], @[B])); // When A wakes up, it should trust C instead of B. circle = [self.model advancePeerWithID:A addingPeerIDs:nil removingPeerIDs:nil createClique:nil]; XCTAssert(circleEquals(circle, @[A, C], @[B])); } - (void)testVoucher { TPPeerPermanentInfo *aaa = [self makePeerWithMachineID:@"aaa"]; TPPeerPermanentInfo *bbb = [self makePeerWithMachineID:@"bbb"]; TPPeerPermanentInfo *ccc = [self makePeerWithMachineID:@"ccc"]; NSString *A = aaa.peerID; NSString *B = bbb.peerID; NSString *C = ccc.peerID; TPCircle *circle; // A establishes clique. circle = [self.model advancePeerWithID:A addingPeerIDs:nil removingPeerIDs:nil createClique:^NSString *{ return @"clique1"; }]; XCTAssert(circleEquals(circle, @[A], @[])); // B trusts A circle = [self.model advancePeerWithID:B addingPeerIDs:@[A] removingPeerIDs:@[] createClique:nil]; XCTAssert(circleEquals(circle, @[A, B], @[])); // C trusts A circle = [self.model advancePeerWithID:C addingPeerIDs:@[A] removingPeerIDs:@[] createClique:nil]; XCTAssert(circleEquals(circle, @[A, C], @[])); // B gets a voucher from A TPVoucher *voucher = [self.model createVoucherForCandidate:bbb withSponsorID:A error:NULL]; XCTAssertNotNil(voucher); XCTAssertEqual(TPResultOk, [self.model registerVoucher:voucher]); // Updating C, it sees the voucher and now trusts B circle = [self.model advancePeerWithID:C addingPeerIDs:nil removingPeerIDs:nil createClique:nil]; XCTAssert(circleEquals(circle, @[A, B, C], @[])); // Updating A, it sees the voucher (sponsored by A itself) and now trusts B. // (A updating its dynamicInfo also expires the voucher.) circle = [self.model advancePeerWithID:A addingPeerIDs:nil removingPeerIDs:nil createClique:nil]; XCTAssert(circleEquals(circle, @[A, B], @[])); } - (void)testExpiredVoucher { TPPeerPermanentInfo *aaa = [self makePeerWithMachineID:@"aaa"]; TPPeerPermanentInfo *bbb = [self makePeerWithMachineID:@"bbb"]; TPPeerPermanentInfo *ccc = [self makePeerWithMachineID:@"ccc"]; TPPeerPermanentInfo *ddd = [self makePeerWithMachineID:@"ddd"]; NSString *A = aaa.peerID; NSString *B = bbb.peerID; NSString *C = ccc.peerID; NSString *D = ddd.peerID; TPCircle *circle; // A establishes clique. circle = [self.model advancePeerWithID:A addingPeerIDs:nil removingPeerIDs:nil createClique:^NSString *{ return @"clique1"; }]; XCTAssert(circleEquals(circle, @[A], @[])); // B trusts A circle = [self.model advancePeerWithID:B addingPeerIDs:@[A] removingPeerIDs:@[] createClique:nil]; XCTAssert(circleEquals(circle, @[A, B], @[])); // C trusts A circle = [self.model advancePeerWithID:C addingPeerIDs:@[A] removingPeerIDs:@[] createClique:nil]; XCTAssert(circleEquals(circle, @[A, C], @[])); // B gets a voucher from A (but doesn't register the voucher yet because A would notice it) TPVoucher *voucher = [self.model createVoucherForCandidate:bbb withSponsorID:A error:NULL]; // A advances its clock by deciding to trust D circle = [self.model advancePeerWithID:A addingPeerIDs:@[D] removingPeerIDs:nil createClique:nil]; XCTAssert(circleEquals(circle, @[A, D], @[])); // Register the voucher, which is now expired because A has advanced its clock [self.model registerVoucher:voucher]; // Updating C, it ignores the expired voucher for B circle = [self.model advancePeerWithID:C addingPeerIDs:nil removingPeerIDs:nil createClique:nil]; XCTAssert(circleEquals(circle, @[A, C, D], @[])); } - (void)testVoucherWithBadSignature { TPPeerPermanentInfo *aaa = [self makePeerWithMachineID:@"aaa"]; TPPeerPermanentInfo *bbb = [self makePeerWithMachineID:@"bbb"]; NSString *A = aaa.peerID; NSString *B = bbb.peerID; TPCircle *circle; // A establishes clique. circle = [self.model advancePeerWithID:A addingPeerIDs:nil removingPeerIDs:nil createClique:^NSString *{ return @"clique1"; }]; XCTAssert(circleEquals(circle, @[A], @[])); // B trusts A circle = [self.model advancePeerWithID:B addingPeerIDs:@[A] removingPeerIDs:@[] createClique:nil]; XCTAssert(circleEquals(circle, @[A, B], @[])); // B gets a voucher from A, but signed by B's key TPVoucher *voucher = [TPVoucher voucherWithBeneficiaryID:B sponsorID:A clock:[self.model getDynamicInfoForPeerWithID:A].clock trustSigningKey:bbb.trustSigningKey error:NULL]; XCTAssertNotNil(voucher); XCTAssertEqual(TPResultSignatureMismatch, [self.model registerVoucher:voucher]); } - (void)testVoucherPolicy { TPPeerPermanentInfo *aaa = [self makePeerWithMachineID:@"aaa" modelID:@"watch" epoch:1 key:@"aaa"]; TPPeerPermanentInfo *bbb = [self makePeerWithMachineID:@"bbb"]; NSString *A = aaa.peerID; // B is a phone trying to get a voucher from A which is a watch TPVoucher *voucher = [self.model createVoucherForCandidate:bbb withSponsorID:A error:NULL]; XCTAssertNil(voucher); } - (void)testDynamicInfoReplay { NSString *A = [self makePeerWithMachineID:@"aaa"].peerID; NSString *B = [self makePeerWithMachineID:@"bbb"].peerID; TPCircle *circle; // A establishes clique, trusts B. circle = [self.model advancePeerWithID:A addingPeerIDs:@[B] removingPeerIDs:nil createClique:^NSString *{ return @"clique1"; }]; XCTAssert(circleEquals(circle, @[A, B], @[])); // Attacker snapshots A's dynamicInfo TPPeerDynamicInfo *dyn = [self.model getDynamicInfoForPeerWithID:A]; // A excludes B circle = [self.model advancePeerWithID:A addingPeerIDs:nil removingPeerIDs:@[B] createClique:nil]; XCTAssert(circleEquals(circle, @[A], @[B])); // Attacker replays the old snapshot XCTAssertEqual(TPResultClockViolation, [self.model updateDynamicInfo:dyn forPeerWithID:A]); circle = [self.model getCircleForPeerWithID:A]; XCTAssert(circleEquals(circle, @[A], @[B])); } - (void)testPhoneApprovingWatch { NSString *phoneA = [self makePeerWithMachineID:@"phoneA" modelID:@"iPhone7,1" epoch:1 key:@"phoneA"].peerID; NSString *watch = [self makePeerWithMachineID:@"watch" modelID:@"Watch1,1" epoch:1 key:@"watch"].peerID; TPCircle *circle; // phoneA establishes clique, trusts watch. circle = [self.model advancePeerWithID:phoneA addingPeerIDs:@[watch] removingPeerIDs:nil createClique:^NSString *{ return @"clique1"; }]; XCTAssert(circleEquals(circle, @[phoneA, watch], @[])); } - (void)testWatchApprovingPhone { NSString *phoneA = [self makePeerWithMachineID:@"phoneA" modelID:@"iPhone7,1" epoch:1 key:@"phoneA"].peerID; NSString *phoneB = [self makePeerWithMachineID:@"phoneB" modelID:@"iPhone7,1" epoch:1 key:@"phoneB"].peerID; NSString *watch = [self makePeerWithMachineID:@"watch" modelID:@"Watch1,1" epoch:1 key:@"watch"].peerID; TPCircle *circle; // phoneA establishes clique, trusts watch. circle = [self.model advancePeerWithID:phoneA addingPeerIDs:@[watch] removingPeerIDs:nil createClique:^NSString *{ return @"clique1"; }]; XCTAssert(circleEquals(circle, @[phoneA, watch], @[])); // watch trusts phoneA and phoneB circle = [self.model advancePeerWithID:watch addingPeerIDs:@[phoneA, phoneB] removingPeerIDs:nil createClique:nil]; XCTAssert(circleEquals(circle, @[phoneA, phoneB, watch], @[])); // phoneA updates, and it should ignore phoneB, so no change. circle = [self.model advancePeerWithID:phoneA addingPeerIDs:nil removingPeerIDs:nil createClique:nil]; XCTAssert(circleEquals(circle, @[phoneA, watch], @[])); } - (void)testNilCreateClique { NSString *A = [self makePeerWithMachineID:@"aaa"].peerID; TPCircle *circle; // Try to establish dynamicInfo without providing createClique circle = [self.model advancePeerWithID:A addingPeerIDs:nil removingPeerIDs:nil createClique:nil]; XCTAssertNil(circle); } - (void)testCliqueConvergence { NSString *A = [self makePeerWithMachineID:@"aaa"].peerID; NSString *B = [self makePeerWithMachineID:@"bbb"].peerID; TPCircle *circle; // A establishes clique1 circle = [self.model advancePeerWithID:A addingPeerIDs:@[] removingPeerIDs:@[] createClique:^NSString *{ return @"clique1"; }]; XCTAssert(circleEquals(circle, @[A], @[])); XCTAssert([[self.model getDynamicInfoForPeerWithID:A].clique isEqualToString:@"clique1"]); // B establishes clique2 circle = [self.model advancePeerWithID:B addingPeerIDs:@[] removingPeerIDs:@[] createClique:^NSString *{ return @"clique2"; }]; XCTAssert(circleEquals(circle, @[B], @[])); XCTAssert([[self.model getDynamicInfoForPeerWithID:B].clique isEqualToString:@"clique2"]); // A trusts B. A should now switch to clique2, which is later than clique1 in lexical order. circle = [self.model advancePeerWithID:A addingPeerIDs:@[B] removingPeerIDs:@[] createClique:nil]; XCTAssert(circleEquals(circle, @[A, B], @[])); XCTAssert([[self.model getDynamicInfoForPeerWithID:A].clique isEqualToString:@"clique2"]); } - (void)testRemovalCounts { NSString *A = [self makePeerWithMachineID:@"aaa"].peerID; NSString *B = [self makePeerWithMachineID:@"bbb"].peerID; NSString *C = [self makePeerWithMachineID:@"ccc"].peerID; // A establishes clique with B and C [self.model advancePeerWithID:A addingPeerIDs:@[B, C] removingPeerIDs:@[] createClique:^NSString *{ return @"clique1"; }]; XCTAssertEqual(0ULL, [self.model getDynamicInfoForPeerWithID:A].removals); // B trusts A [self.model advancePeerWithID:B addingPeerIDs:@[A] removingPeerIDs:@[] createClique:nil]; XCTAssertEqual(0ULL, [self.model getDynamicInfoForPeerWithID:B].removals); // A removes C [self.model advancePeerWithID:A addingPeerIDs:nil removingPeerIDs:@[C] createClique:nil]; XCTAssertEqual(1ULL, [self.model getDynamicInfoForPeerWithID:A].removals); // B updates, and now shows 1 removal [self.model advancePeerWithID:B addingPeerIDs:nil removingPeerIDs:nil createClique:nil]; XCTAssertEqual(1ULL, [self.model getDynamicInfoForPeerWithID:B].removals); } - (void)testCommunicatingModels { TPPeerPermanentInfo *aaa = [self makePeerWithMachineID:@"aaa"]; TPPeerPermanentInfo *bbb = [self makePeerWithMachineID:@"bbb"]; TPPeerPermanentInfo *ccc = [self makePeerWithMachineID:@"ccc"]; NSString *A = aaa.peerID; NSString *B = bbb.peerID; NSString *C = ccc.peerID; // A lives on self.model, where it trusts B and C [self.model advancePeerWithID:A addingPeerIDs:@[B, C] removingPeerIDs:nil createClique:^NSString *{ return @"clique1"; }]; // B lives on model2, where it trusts A TPModel *model2 = [self makeModel]; [model2 registerPeerWithPermanentInfo:aaa]; [model2 registerPeerWithPermanentInfo:bbb]; [model2 updateStableInfo:[self.model getStableInfoForPeerWithID:A] forPeerWithID:A]; [model2 updateStableInfo:[self.model getStableInfoForPeerWithID:B] forPeerWithID:B]; [model2 advancePeerWithID:B addingPeerIDs:@[A] removingPeerIDs:nil createClique:^NSString *{ return @"clique1"; }]; // A's circle and dynamicInfo are transmitted from model to model2 TPCircle *circle = [self.model getCircleForPeerWithID:A]; TPPeerDynamicInfo *dyn = [self.model getDynamicInfoForPeerWithID:A]; [model2 updateDynamicInfo:dyn forPeerWithID:A]; [model2 registerCircle:circle]; // B updates in model2, but C is not yet registered so is ignored. circle = [model2 advancePeerWithID:B addingPeerIDs:nil removingPeerIDs:nil createClique:nil]; XCTAssert(circleEquals(circle, @[A, B], @[])); // Now C registers in model2 [model2 registerPeerWithPermanentInfo:ccc]; // B updates in model2, and now it trusts C. circle = [model2 advancePeerWithID:B addingPeerIDs:nil removingPeerIDs:nil createClique:nil]; XCTAssert(circleEquals(circle, @[A, B, C], @[])); } - (void)testCommunicatingModelsWithVouchers { TPPeerPermanentInfo *aaa = [self makePeerWithMachineID:@"aaa"]; TPPeerPermanentInfo *bbb = [self makePeerWithMachineID:@"bbb"]; TPPeerPermanentInfo *ccc = [self makePeerWithMachineID:@"ccc"]; NSString *A = aaa.peerID; NSString *B = bbb.peerID; NSString *C = ccc.peerID; // A lives on self.model, where it trusts B [self.model advancePeerWithID:A addingPeerIDs:@[B] removingPeerIDs:nil createClique:^NSString *{ return @"clique1"; }]; // B lives on model2, where it trusts A TPModel *model2 = [self makeModel]; [model2 registerPeerWithPermanentInfo:aaa]; [model2 registerPeerWithPermanentInfo:bbb]; [model2 updateStableInfo:[self.model getStableInfoForPeerWithID:A] forPeerWithID:A]; [model2 updateStableInfo:[self.model getStableInfoForPeerWithID:B] forPeerWithID:B]; [model2 advancePeerWithID:B addingPeerIDs:@[A] removingPeerIDs:nil createClique:^NSString *{ return @"clique1"; }]; // A's circle and dynamicInfo are transmitted from model to model2 TPCircle *circle = [self.model getCircleForPeerWithID:A]; TPPeerDynamicInfo *dyn = [self.model getDynamicInfoForPeerWithID:A]; [model2 updateDynamicInfo:dyn forPeerWithID:A]; [model2 registerCircle:circle]; // A writes a voucher for C, and it is transmitted to model2 TPVoucher *voucher = [self.model createVoucherForCandidate:ccc withSponsorID:A error:NULL]; [model2 registerVoucher:voucher]; // B updates in model2, but C is not yet registered so is ignored. circle = [model2 advancePeerWithID:B addingPeerIDs:nil removingPeerIDs:nil createClique:nil]; XCTAssert(circleEquals(circle, @[A, B], @[])); // Now C registers in model2 [model2 registerPeerWithPermanentInfo:ccc]; [model2 updateStableInfo:[self.model getStableInfoForPeerWithID:C] forPeerWithID:C]; // B updates in model2, and now it trusts C. circle = [model2 advancePeerWithID:B addingPeerIDs:nil removingPeerIDs:nil createClique:nil]; XCTAssert(circleEquals(circle, @[A, B, C], @[])); } - (void)testReregisterPeer { TPPeerPermanentInfo *aaa = [self makePeerWithMachineID:@"aaa"]; NSString *A = aaa.peerID; [self.model advancePeerWithID:A addingPeerIDs:nil removingPeerIDs:nil createClique:^NSString *{ return @"clique1"; }]; // Registering the peer again should not overwrite its dynamicInfo or other state. [self.model registerPeerWithPermanentInfo:aaa]; XCTAssertNotNil([self.model getDynamicInfoForPeerWithID:A]); } - (void)testPeerAccessors { TPPeerPermanentInfo *aaa = [self makePeerWithMachineID:@"aaa"]; NSString *A = aaa.peerID; XCTAssert([self.model hasPeerWithID:A]); TPPeerPermanentInfo *aaa2 = [self.model getPermanentInfoForPeerWithID:A]; XCTAssertEqualObjects(aaa, aaa2); TPPeerStableInfo *info = [self.model createStableInfoWithDictionary:@{ @"hello": @"world" } policyVersion:1 policyHash:@"" policySecrets:nil forPeerWithID:A error:NULL]; XCTAssertEqual(TPResultOk, [self.model updateStableInfo:info forPeerWithID:A]); XCTAssertEqualObjects([self.model getStableInfoForPeerWithID:A], info); [self.model deletePeerWithID:A]; XCTAssertFalse([self.model hasPeerWithID:A]); } - (void)testCircleAccessors { TPCircle *circle = [TPCircle circleWithIncludedPeerIDs:@[@"A, B"] excludedPeerIDs:nil]; XCTAssertNil([self.model circleWithID:circle.circleID]); [self.model registerCircle:circle]; XCTAssertNotNil([self.model circleWithID:circle.circleID]); [self.model deleteCircleWithID:circle.circleID]; XCTAssertNil([self.model circleWithID:circle.circleID]); } - (void)testLatestEpoch { NSString *A = [self makePeerWithMachineID:@"aaa" modelID:@"iPhone" epoch:0 key:@"aaa"].peerID; NSString *B = [self makePeerWithMachineID:@"bbb" modelID:@"iPhone" epoch:1 key:@"aaa"].peerID; NSString *C = [self makePeerWithMachineID:@"ccc" modelID:@"iPhone" epoch:2 key:@"aaa"].peerID; TPCounter epoch = [self.model latestEpochAmongPeerIDs:[NSSet setWithArray:@[A, B, C]]]; XCTAssertEqual(epoch, 2ULL); } - (void)testPeerStatus { NSString *A = [self makePeerWithMachineID:@"aaa" modelID:@"iPhone" epoch:0 key:@"aaa"].peerID; NSString *B = [self makePeerWithMachineID:@"bbb" modelID:@"iPhone" epoch:0 key:@"bbb"].peerID; NSString *C = [self makePeerWithMachineID:@"ccc" modelID:@"iPhone" epoch:0 key:@"ccc"].peerID; NSString *D = [self makePeerWithMachineID:@"ddd" modelID:@"iPhone" epoch:1 key:@"ddd"].peerID; NSString *E = [self makePeerWithMachineID:@"eee" modelID:@"iPhone" epoch:2 key:@"eee"].peerID; XCTAssertEqual([self.model statusOfPeerWithID:A], 0); [self.model advancePeerWithID:A addingPeerIDs:@[B, C] removingPeerIDs:@[] createClique:^NSString *{ return @"clique1"; }]; XCTAssertEqual([self.model statusOfPeerWithID:A], 0); [self.model advancePeerWithID:B addingPeerIDs:@[A, C] removingPeerIDs:@[] createClique:nil]; XCTAssertEqual([self.model statusOfPeerWithID:A], TPPeerStatusPartiallyReciprocated); [self.model advancePeerWithID:C addingPeerIDs:@[A] removingPeerIDs:@[] createClique:nil]; XCTAssertEqual([self.model statusOfPeerWithID:A], TPPeerStatusPartiallyReciprocated | TPPeerStatusFullyReciprocated); [self.model advancePeerWithID:C addingPeerIDs:@[] removingPeerIDs:@[A] createClique:nil]; XCTAssertEqual([self.model statusOfPeerWithID:A], TPPeerStatusPartiallyReciprocated | TPPeerStatusExcluded); [self.model advancePeerWithID:A addingPeerIDs:@[] removingPeerIDs:@[] createClique:nil]; XCTAssertEqual([self.model statusOfPeerWithID:A], TPPeerStatusExcluded); [self.model advancePeerWithID:B addingPeerIDs:@[D] removingPeerIDs:@[] createClique:nil]; XCTAssertEqual([self.model statusOfPeerWithID:B], TPPeerStatusPartiallyReciprocated | TPPeerStatusOutdatedEpoch); [self.model advancePeerWithID:C addingPeerIDs:@[E] removingPeerIDs:@[] createClique:nil]; [self.model advancePeerWithID:B addingPeerIDs:@[] removingPeerIDs:@[] createClique:nil]; XCTAssertEqual([self.model statusOfPeerWithID:B], TPPeerStatusPartiallyReciprocated | TPPeerStatusOutdatedEpoch | TPPeerStatusAncientEpoch); } - (void)testCalculateUnusedCircleIDs { NSString *A = [self makePeerWithMachineID:@"aaa" modelID:@"iPhone" epoch:0 key:@"aaa"].peerID; NSString *B = [self makePeerWithMachineID:@"bbb" modelID:@"iPhone" epoch:0 key:@"bbb"].peerID; [self.model advancePeerWithID:A addingPeerIDs:@[B] removingPeerIDs:@[] createClique:^NSString *{ return @"clique1"; }]; [self.model advancePeerWithID:B addingPeerIDs:@[B] removingPeerIDs:@[] createClique:nil]; NSSet<NSString*>* unused; unused = [self.model calculateUnusedCircleIDs]; XCTAssertEqualObjects(unused, [NSSet set]); NSString *circleID = [self.model getCircleForPeerWithID:A].circleID; [self.model advancePeerWithID:A addingPeerIDs:@[] removingPeerIDs:@[B] createClique:nil]; unused = [self.model calculateUnusedCircleIDs]; XCTAssertEqualObjects(unused, [NSSet setWithObject:circleID]); } - (void)testGetPeerIDsTrustedByPeerWithID { NSString *A = [self makePeerWithMachineID:@"aaa" modelID:@"iPhone7,1" epoch:0 key:@"aaa"].peerID; NSString *B = [self makePeerWithMachineID:@"bbb" modelID:@"iPhone6,2" epoch:0 key:@"bbb"].peerID; NSString *C = [self makePeerWithMachineID:@"ccc" modelID:@"Watch1,1" epoch:0 key:@"ccc"].peerID; [self makePeerWithMachineID:@"ddd" modelID:@"iPhone7,1" epoch:0 key:@"ddd"]; [self.model advancePeerWithID:A addingPeerIDs:@[B, C] removingPeerIDs:@[] createClique:^NSString *{ return @"clique1"; }]; // Everyone can access WiFi. Only full peers can access SafariCreditCards NSSet<NSString *>* peerIDs; NSSet<NSString *>* expected; peerIDs = [self.model getPeerIDsTrustedByPeerWithID:A toAccessView:@"WiFi" error:NULL]; expected = [NSSet setWithArray:@[A, B, C]]; XCTAssertEqualObjects(peerIDs, expected); peerIDs = [self.model getPeerIDsTrustedByPeerWithID:A toAccessView:@"SafariCreditCards" error:NULL]; expected = [NSSet setWithArray:@[A, B]]; XCTAssertEqualObjects(peerIDs, expected); } - (void)testVectorClock { NSString *A = [self makePeerWithMachineID:@"aaa"].peerID; NSString *B = [self makePeerWithMachineID:@"bbb"].peerID; NSString *C = [self makePeerWithMachineID:@"ccc"].peerID; [self.model advancePeerWithID:A addingPeerIDs:@[B] removingPeerIDs:@[] createClique:^NSString *{ return @"clique1"; }]; [self.model advancePeerWithID:B addingPeerIDs:@[A] removingPeerIDs:@[] createClique:nil]; NSDictionary *dict; NSDictionary *expected; dict = [self.model vectorClock]; expected = @{ A: @4, B: @5, C: @3 }; XCTAssertEqualObjects(dict, expected); [self.model advancePeerWithID:C addingPeerIDs:@[A] removingPeerIDs:@[B] createClique:nil]; [self.model advancePeerWithID:A addingPeerIDs:@[] removingPeerIDs:@[] createClique:nil]; [self.model advancePeerWithID:B addingPeerIDs:@[] removingPeerIDs:@[] createClique:nil]; dict = [self.model vectorClock]; expected = @{ A: @7, B: @8, C: @6 }; XCTAssertEqualObjects(dict, expected); } - (void)testICycleApprovingPhoneWithNewPolicy { NSString *phoneA = [self makePeerWithMachineID:@"phoneA" modelID:@"iPhone7,1" epoch:1 key:@"phoneA"].peerID; NSString *phoneB = [self makePeerWithMachineID:@"phoneB" modelID:@"iPhone7,1" epoch:1 key:@"phoneB"].peerID; NSString *icycle = [self makePeerWithMachineID:@"icycle" modelID:@"iCycle1,1" epoch:1 key:@"icycle"].peerID; TPCircle *circle; // phoneA establishes clique, trusts icycle circle = [self.model advancePeerWithID:phoneA addingPeerIDs:@[icycle] removingPeerIDs:nil createClique:^NSString *{ return @"clique1"; }]; XCTAssert(circleEquals(circle, @[phoneA, icycle], @[])); // icycle trusts phoneA and phoneB circle = [self.model advancePeerWithID:icycle addingPeerIDs:@[phoneA, phoneB] removingPeerIDs:nil createClique:nil]; XCTAssert(circleEquals(circle, @[phoneA, phoneB, icycle], @[])); // phoneA updates, and it doesn't know iCycles can approve phones, so it should ignore phoneB, so no change. circle = [self.model advancePeerWithID:phoneA addingPeerIDs:nil removingPeerIDs:nil createClique:nil]; XCTAssert(circleEquals(circle, @[phoneA, icycle], @[])); // icycle presents a new policy that says iCycles can approve phones TPPeerStableInfo *stableInfo = [self.model createStableInfoWithDictionary:@{} policyVersion:self.policyDocV2.policyVersion policyHash:self.policyDocV2.policyHash policySecrets:nil forPeerWithID:icycle error:NULL]; [self.model updateStableInfo:stableInfo forPeerWithID:icycle]; // phoneA updates again, sees the new policy that says iCycles can approve phones, and now trusts phoneB circle = [self.model advancePeerWithID:phoneA addingPeerIDs:nil removingPeerIDs:nil createClique:nil]; XCTAssert(circleEquals(circle, @[phoneA, phoneB, icycle], @[])); } - (void)testICycleApprovingPhoneWithRedactedPolicy { NSString *phoneA = [self makePeerWithMachineID:@"phoneA" modelID:@"iPhone7,1" epoch:1 key:@"phoneA"].peerID; NSString *phoneB = [self makePeerWithMachineID:@"phoneB" modelID:@"iPhone7,1" epoch:1 key:@"phoneB"].peerID; NSString *icycle = [self makePeerWithMachineID:@"icycle" modelID:@"iCycle1,1" epoch:1 key:@"icycle"].peerID; TPCircle *circle; // phoneA establishes clique, trusts icycle circle = [self.model advancePeerWithID:phoneA addingPeerIDs:@[icycle] removingPeerIDs:nil createClique:^NSString *{ return @"clique1"; }]; XCTAssert(circleEquals(circle, @[phoneA, icycle], @[])); // icycle trusts phoneA and phoneB circle = [self.model advancePeerWithID:icycle addingPeerIDs:@[phoneA, phoneB] removingPeerIDs:nil createClique:nil]; XCTAssert(circleEquals(circle, @[phoneA, phoneB, icycle], @[])); // phoneA updates, and it doesn't know iCycles can approve phones, so it should ignore phoneB, so no change. circle = [self.model advancePeerWithID:phoneA addingPeerIDs:nil removingPeerIDs:nil createClique:nil]; XCTAssert(circleEquals(circle, @[phoneA, icycle], @[])); // icycle presents a new policy that says iCycles can approve phones TPPeerStableInfo *stableInfo = [self.model createStableInfoWithDictionary:@{} policyVersion:self.policyDocV1.policyVersion policyHash:self.policyDocV1.policyHash policySecrets:@{ self.secretName: self.secretKey } forPeerWithID:icycle error:NULL]; [self.model updateStableInfo:stableInfo forPeerWithID:icycle]; // phoneA updates again, sees the new policy that says iCycles can approve phones, and now trusts phoneB circle = [self.model advancePeerWithID:phoneA addingPeerIDs:nil removingPeerIDs:nil createClique:nil]; XCTAssert(circleEquals(circle, @[phoneA, phoneB, icycle], @[])); } @end