+/*
+ * Copyright (c) 2016 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 "SCTest.h"
+#import "SCTestUtils.h"
+#import <Network/Network_Private.h>
+#import <CoreFoundation/CFXPCBridge.h>
+#import <config_agent_info.h>
+
+#define TEST_DOMAIN "filemaker.com"
+#define PATH_QUIESCE_TIME 1.5 // seconds
+
+@interface SCTestConfigAgent : SCTest
+@property NSString *serviceID;
+@property NSString *proxyKey;
+@property NSString *dnsKey;
+@property NSArray<NSArray<NSDictionary *> *> *testProxy;
+@property NSArray<NWEndpoint *> *testDNS;
+@property SCDynamicStoreRef store;
+@property (copy) NSArray<NSArray<NSDictionary *> *> *pathProxy;
+@property (copy) NSArray<NWEndpoint *> *pathDNS;
+@end
+
+@implementation SCTestConfigAgent
+
+- (instancetype)initWithOptions:(NSDictionary *)options
+{
+ self = [super initWithOptions:options];
+ if (self) {
+ _serviceID = @"8F66B505-EAEF-4611-BD4D-C523FD9451F0";
+ _store = SCDynamicStoreCreate(kCFAllocatorDefault,
+ CFSTR("SCTest"),
+ NULL,
+ NULL);
+ _proxyKey = (__bridge_transfer NSString *)[self copyStateKeyWithServiceID:(__bridge CFStringRef)(self.serviceID) forEntity:kSCEntNetProxies];
+ _dnsKey = (__bridge_transfer NSString *)[self copyStateKeyWithServiceID:(__bridge CFStringRef)(self.serviceID) forEntity:kSCEntNetDNS];
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ if (self.store != NULL) {
+ CFRelease(self.store);
+ self.store = NULL;
+ }
+}
+
++ (NSString *)command
+{
+ return @"config_agent";
+}
+
++ (NSString *)commandDescription
+{
+ return @"Tests the Config Agent code path";
+}
+
+- (void)start
+{
+ if (self.options[kSCTestConfigAgentRemoveProxy]) {
+ [self removeFromSCDynamicStore:self.proxyKey];
+ }
+
+ if (self.options[kSCTestConfigAgentRemoveDNS]) {
+ [self removeFromSCDynamicStore:self.dnsKey];
+ }
+
+ NSDictionary *proxyConfig = [self parseProxyAgentOptions];
+ if (proxyConfig != nil) {
+ [self publishToSCDynamicStore:self.proxyKey value:proxyConfig];
+ self.testProxy = @[@[proxyConfig]];
+ }
+
+ NSDictionary *dnsConfig = [self parseDNSAgentOptions];
+ if (dnsConfig != nil) {
+ [self publishToSCDynamicStore:self.dnsKey value:dnsConfig];
+ self.testDNS = [self createDNSArray:dnsConfig];
+ }
+
+ [self cleanupAndExitWithErrorCode:0];
+}
+
+- (CFStringRef)copyStateKeyWithServiceID:(CFStringRef)serviceID
+ forEntity:(CFStringRef)entity
+{
+ return SCDynamicStoreKeyCreateNetworkServiceEntity(kCFAllocatorDefault,
+ kSCDynamicStoreDomainState,
+ serviceID,
+ entity);
+}
+
+- (NSArray<NWEndpoint *> *)createDNSArray:(NSDictionary *)dnsConfig
+{
+ NSArray<NSString *> *dnsServers;
+ NSMutableArray<NWEndpoint *> *dnsArray;
+
+ dnsServers = [dnsConfig objectForKey:(__bridge NSString *)kSCPropNetDNSServerAddresses];
+ if (dnsServers == nil || [dnsServers count] == 0) {
+ return nil;
+ }
+
+ dnsArray = [[NSMutableArray alloc] init];
+ for (NSString *server in dnsServers) {
+ NWEndpoint *endpoint = (NWEndpoint *)[NWAddressEndpoint endpointWithHostname:server port:@"0"];
+ [dnsArray addObject:endpoint];
+ }
+
+ return dnsArray;
+}
+
+- (void)publishToSCDynamicStore:(NSString *)key
+ value:(NSDictionary *)value
+{
+
+ BOOL ok = SCDynamicStoreSetValue(self.store, (__bridge CFStringRef)key, (__bridge CFPropertyListRef _Nonnull)(value));
+ if (!ok) {
+ int error = SCError();
+ if (error == kSCStatusNoKey) {
+ return;
+ }
+ SCTestLog("Could not set in SCDynamicStore: Error: %s", SCErrorString(error));
+ return;
+ }
+}
+
+- (void)removeFromSCDynamicStore:(NSString *)key
+{
+ BOOL ok = SCDynamicStoreRemoveValue(self.store, (__bridge CFStringRef)key);
+ if (!ok) {
+ int error = SCError();
+ if (error == kSCStatusNoKey) {
+ return;
+ }
+ SCTestLog("Could not remove key: %@, Error: %s", key, SCErrorString(error));
+ return;
+ }
+}
+
+- (NSDictionary *)parseProxyAgentOptions
+{
+ NSMutableDictionary *proxyConfig = [[NSMutableDictionary alloc] init];
+
+#define NS_NUMBER(x) [NSNumber numberWithInt:x]
+
+#define SET_PROXY_CONFIG(proxyType) \
+ do { \
+ if (self.options[kSCTestConfigAgent ## proxyType ## Proxy] != nil) { \
+ NSString *serverAndPortString = self.options[kSCTestConfigAgent ## proxyType ## Proxy]; \
+ NSArray<NSString *> *serverAndPortArray = [serverAndPortString componentsSeparatedByString:@":"]; \
+ if ([serverAndPortArray count] != 2) { \
+ SCTestLog("server address or port missing"); \
+ ERR_EXIT; \
+ } \
+ NSString *server = [serverAndPortArray objectAtIndex:0]; \
+ NSString *port = [serverAndPortArray objectAtIndex:1]; \
+ [proxyConfig setObject:server forKey:(__bridge NSString *)kSCPropNetProxies ## proxyType ## Proxy]; \
+ [proxyConfig setObject:NS_NUMBER(port.intValue) forKey:(__bridge NSString *)kSCPropNetProxies ## proxyType ## Port]; \
+ [proxyConfig setObject:NS_NUMBER(1) forKey:(__bridge NSString *)kSCPropNetProxies ## proxyType ## Enable]; \
+ } \
+ } while(0);
+
+ SET_PROXY_CONFIG(HTTP);
+ SET_PROXY_CONFIG(HTTPS);
+ SET_PROXY_CONFIG(FTP);
+ SET_PROXY_CONFIG(Gopher);
+ SET_PROXY_CONFIG(SOCKS);
+
+ if ([proxyConfig count] > 0) {
+ NSString *matchDomain = self.options[kSCTestConfigAgentProxyMatchDomain] ? self.options[kSCTestConfigAgentProxyMatchDomain] : @TEST_DOMAIN;
+ [proxyConfig setObject:@[matchDomain] forKey:(__bridge NSString *)kSCPropNetProxiesSupplementalMatchDomains];
+ } else {
+ proxyConfig = nil;
+ }
+
+ return proxyConfig;
+#undef SET_PROXY_CONFIG
+}
+
+- (NSDictionary *)parseDNSAgentOptions
+{
+ NSMutableDictionary *dnsConfig;
+ NSString *dnsServerString;
+ NSString *dnsDomainString;
+ NSArray<NSString *> *dnsServers;
+ NSArray<NSString *> *dnsDomains;
+
+ dnsConfig = [[NSMutableDictionary alloc] init];
+ dnsServerString = self.options[kSCTestConfigAgentDNSServers];
+ if (dnsServerString == nil) {
+ return nil;
+ }
+
+ dnsDomainString = self.options[kSCTestConfigAgentDNSDomains];
+ if (dnsDomainString == nil) {
+ dnsDomainString = @TEST_DOMAIN;
+ }
+
+ dnsServers = [dnsServerString componentsSeparatedByString:@","];
+ [dnsConfig setObject:dnsServers forKey:(__bridge NSString *)kSCPropNetDNSServerAddresses];
+
+ dnsDomains = [dnsDomainString componentsSeparatedByString:@","];
+ [dnsConfig setObject:dnsDomains forKey:(__bridge NSString *)kSCPropNetDNSSupplementalMatchDomains];
+
+ return dnsConfig;
+}
+
+- (void)cleanupAndExitWithErrorCode:(int)error
+{
+ CFRelease(self.store);
+ [super cleanupAndExitWithErrorCode:error];
+}
+
+- (BOOL)setup
+{
+ return YES;
+}
+
+- (BOOL)unitTest
+{
+ if(![self setup]) {
+ return NO;
+ }
+
+ BOOL allUnitTestsPassed = YES;
+ allUnitTestsPassed &= [self unitTestInstallProxy];
+ allUnitTestsPassed &= [self unitTestInstallProxyWithLargeConfig];
+ allUnitTestsPassed &= [self unitTestInstallProxyWithConflictingDomain];
+ allUnitTestsPassed &= [self unitTestInstallDNS];
+ allUnitTestsPassed &= [self unitTestInstallDNSWithConflictingDomain];
+
+ if(![self tearDown]) {
+ return NO;
+ }
+
+ return allUnitTestsPassed;
+}
+
+- (BOOL)unitTestInstallProxy
+{
+ BOOL success = NO;
+ SCTestConfigAgent *test;
+ NSDictionary *proxyConfig;
+ NSString *hostname;
+ NSNumber *port;
+ NWHostEndpoint *hostEndpoint;
+ NWPathEvaluator *pathEvaluator;
+ NSMutableDictionary *dict;
+
+ test = [[SCTestConfigAgent alloc] initWithOptions:self.options];
+ proxyConfig = [test parseProxyAgentOptions];
+ if (proxyConfig == nil) {
+ // Use default options
+ proxyConfig = @{(__bridge NSString *)kSCPropNetProxiesHTTPEnable:@(1),
+ (__bridge NSString *)kSCPropNetProxiesHTTPPort:@(80),
+ (__bridge NSString *)kSCPropNetProxiesHTTPProxy:@"10.10.10.100",
+ (__bridge NSString *)kSCPropNetProxiesSupplementalMatchDomains:@[@TEST_DOMAIN],
+ };
+ }
+
+ hostname = [[proxyConfig objectForKey:(__bridge NSString *)kSCPropNetProxiesSupplementalMatchDomains] objectAtIndex:0];
+ port = [proxyConfig objectForKey:(__bridge NSString *)kSCPropNetProxiesHTTPPort];
+ hostEndpoint = [NWHostEndpoint endpointWithHostname:hostname port:port.stringValue];
+ pathEvaluator = [[NWPathEvaluator alloc] initWithEndpoint:hostEndpoint parameters:NULL];
+ [pathEvaluator addObserver:test
+ forKeyPath:@"path"
+ options:NSKeyValueObservingOptionNew
+ context:nil];
+
+ do {
+ [test publishToSCDynamicStore:test.proxyKey value:proxyConfig];
+ dict = [NSMutableDictionary dictionaryWithDictionary:proxyConfig];
+ [dict removeObjectForKey:(__bridge NSString *)kSCPropNetProxiesSupplementalMatchDomains];
+ test.testProxy = @[@[dict]];
+
+ // Wait for the path changes to quiesce
+ [test waitFor:PATH_QUIESCE_TIME];
+ if (![test.testProxy isEqualToArray:test.pathProxy]) {
+ SCTestLog("test proxy and applied proxy do not match. Test: %@, Applied: %@", test.testProxy, test.pathProxy);
+ break;
+ }
+
+ SCTestLog("Verified the configured proxy is the same as applied proxy");
+ [test removeFromSCDynamicStore:test.proxyKey];
+ test.testProxy = nil;
+ // Wait for the path changes to quiesce
+ [test waitFor:PATH_QUIESCE_TIME];
+ if (test.pathProxy != nil) {
+ SCTestLog("proxy applied when there is no test proxy");
+ break;
+ }
+
+ success = YES;
+ } while(0);
+
+ [pathEvaluator removeObserver:test
+ forKeyPath:@"path"];
+
+ return success;
+}
+
+- (BOOL)unitTestInstallDNS
+{
+ BOOL success = NO;
+ SCTestConfigAgent *test;
+ NSDictionary *dnsConfig;
+ NSString *hostname;
+ NWHostEndpoint *hostEndpoint;
+ NWPathEvaluator *pathEvaluator;
+
+ test = [[SCTestConfigAgent alloc] initWithOptions:self.options];
+ dnsConfig = [test parseDNSAgentOptions];
+ if (dnsConfig == nil) {
+ dnsConfig = @{ (__bridge NSString *)kSCPropNetDNSServerAddresses:@[@"10.10.10.101", @"10.10.10.102", @"10.10.10.103"],
+ (__bridge NSString *)kSCPropNetDNSSupplementalMatchDomains:@[@TEST_DOMAIN],
+ };
+ }
+
+ hostname = [[dnsConfig objectForKey:(__bridge NSString *)kSCPropNetDNSSupplementalMatchDomains] objectAtIndex:0];
+ hostEndpoint = [NWHostEndpoint endpointWithHostname:hostname port:@"80"];
+ pathEvaluator = [[NWPathEvaluator alloc] initWithEndpoint:hostEndpoint parameters:NULL];
+ [pathEvaluator addObserver:test
+ forKeyPath:@"path"
+ options:NSKeyValueObservingOptionNew
+ context:nil];
+
+ do {
+ [test publishToSCDynamicStore:test.dnsKey value:dnsConfig];
+ test.testDNS = [test createDNSArray:dnsConfig];
+
+ // Wait for the path changes to quiesce
+ [test waitFor:PATH_QUIESCE_TIME];
+ if (![test.testDNS isEqualToArray:test.pathDNS]) {
+ SCTestLog("test DNS and applied DNS do not match. Test: %@, Applied: %@", test.testDNS, test.pathDNS);
+ break;
+ }
+
+ [test removeFromSCDynamicStore:test.dnsKey];
+ test.testDNS = nil;
+ [test waitFor:PATH_QUIESCE_TIME];
+
+ SCTestLog("Verified that the configured DNS is same as applied DNS for a domain");
+ success = YES;
+ } while (0);
+
+ [pathEvaluator removeObserver:test
+ forKeyPath:@"path"];
+
+ return success;
+}
+
+- (BOOL)unitTestInstallProxyWithLargeConfig
+{
+ BOOL success = NO;
+ SCTestConfigAgent *test;
+ NSString *str = @"0123456789";
+ NSMutableString *largeStr;
+ NSDictionary *proxyConfig;
+ NSString *hostname;
+ NSNumber *port;
+ NWHostEndpoint *hostEndpoint;
+ NWPathEvaluator *pathEvaluator;
+ NSMutableDictionary *dict;
+
+ test = [[SCTestConfigAgent alloc] initWithOptions:self.options];
+ largeStr = [[NSMutableString alloc] init];
+ for (int i = 0; i < 200; i++) {
+ [largeStr appendString:str];
+ }
+
+ // We imitate a proxy config worth 2K bytes.
+ proxyConfig = @{(__bridge NSString *)kSCPropNetProxiesHTTPEnable:@(1),
+ (__bridge NSString *)kSCPropNetProxiesHTTPPort:@(80),
+ (__bridge NSString *)kSCPropNetProxiesHTTPProxy:@"10.10.10.100",
+ (__bridge NSString *)kSCPropNetProxiesProxyAutoConfigJavaScript:largeStr,
+ (__bridge NSString *)kSCPropNetProxiesProxyAutoConfigEnable:@(1),
+ (__bridge NSString *)kSCPropNetProxiesSupplementalMatchDomains:@[@TEST_DOMAIN],
+ };
+
+ hostname = [[proxyConfig objectForKey:(__bridge NSString *)kSCPropNetProxiesSupplementalMatchDomains] objectAtIndex:0];
+ port = [proxyConfig objectForKey:(__bridge NSString *)kSCPropNetProxiesHTTPPort];
+ hostEndpoint = [NWHostEndpoint endpointWithHostname:hostname port:port.stringValue];
+ pathEvaluator = [[NWPathEvaluator alloc] initWithEndpoint:hostEndpoint parameters:NULL];
+ [pathEvaluator addObserver:test
+ forKeyPath:@"path"
+ options:NSKeyValueObservingOptionNew
+ context:nil];
+
+ do {
+ [test publishToSCDynamicStore:test.proxyKey value:proxyConfig];
+ dict = [NSMutableDictionary dictionaryWithDictionary:proxyConfig];
+ [dict removeObjectForKey:(__bridge NSString *)kSCPropNetProxiesSupplementalMatchDomains];
+ test.testProxy = @[@[dict]];
+
+ // Wait for the path changes to quiesce
+ [test waitFor:PATH_QUIESCE_TIME];
+ if ([test.testProxy isEqualToArray:test.pathProxy]) {
+ SCTestLog("applied proxy does not contain Out of Band Agent UUID");
+ break;
+ }
+
+ // Now we verify that we are able to fetch the proxy configuration from configd
+ for (NSArray<NSDictionary *> *config in test.pathProxy) {
+ xpc_object_t xpcConfig = _CFXPCCreateXPCObjectFromCFObject((__bridge CFArrayRef)config);
+ xpc_object_t fetchedConfig = config_agent_update_proxy_information(xpcConfig);
+ if (fetchedConfig != nil) {
+ NSArray *nsConfig = (__bridge_transfer NSArray *)(_CFXPCCreateCFObjectFromXPCObject(fetchedConfig));
+ test.pathProxy = @[nsConfig];
+ break;
+ }
+ }
+
+ if (![test.testProxy isEqualToArray:test.pathProxy]) {
+ SCTestLog("Could not fetch proxy configuration from configd. Test: %@, Applied: %@", test.testProxy, test.pathProxy);
+ break;
+ }
+
+ SCTestLog("Verified that the proxy configuration is successfully fetched from configd");
+ test.testProxy = nil;
+ [test removeFromSCDynamicStore:test.proxyKey];
+
+ // Wait for the path changes to quiesce
+ [test waitFor:PATH_QUIESCE_TIME];
+ if (test.pathProxy != nil) {
+ SCTestLog("proxy applied when there is no test proxy");
+ break;
+ }
+
+ success = YES;
+ } while (0);
+
+ [pathEvaluator removeObserver:test
+ forKeyPath:@"path"];
+
+ return success;
+}
+
+- (BOOL)unitTestInstallDNSWithConflictingDomain
+{
+ BOOL success = NO;
+ SCTestConfigAgent *test;
+ NSDictionary *dnsConfig;
+ NSString *hostname;
+ NWHostEndpoint *hostEndpoint;
+ NWPathEvaluator *pathEvaluator;
+
+ test = [[SCTestConfigAgent alloc] initWithOptions:self.options];
+ dnsConfig = @{ (__bridge NSString *)kSCPropNetDNSServerAddresses:@[@"10.10.10.101", @"10.10.10.102", @"10.10.10.103"],
+ (__bridge NSString *)kSCPropNetDNSSupplementalMatchDomains:@[@TEST_DOMAIN],
+ };
+
+ hostname = [[dnsConfig objectForKey:(__bridge NSString *)kSCPropNetDNSSupplementalMatchDomains] objectAtIndex:0];
+ hostEndpoint = [NWHostEndpoint endpointWithHostname:hostname port:@"80"];
+ pathEvaluator = [[NWPathEvaluator alloc] initWithEndpoint:hostEndpoint parameters:NULL];
+ [pathEvaluator addObserver:test
+ forKeyPath:@"path"
+ options:NSKeyValueObservingOptionNew
+ context:nil];
+
+ do {
+ NSDictionary *duplicateDnsConfig;
+ NSString *anotherFakeServiceID;
+ NSString *anotherDNSKey;
+ NSArray *array;
+ NSSet *testDNSSet;
+ NSSet *pathDNSSet;
+
+ [test publishToSCDynamicStore:test.dnsKey value:dnsConfig];
+ test.testDNS = [test createDNSArray:dnsConfig];
+
+ // Wait for the path changes to quiesce
+ [test waitFor:PATH_QUIESCE_TIME];
+ if (![test.testDNS isEqualToArray:test.pathDNS]) {
+ SCTestLog("test DNS and applied DNS do not match. Test: %@, Applied: %@", test.testDNS, test.pathDNS);
+ break;
+ }
+
+ // Now install the conflicting DNS configuration
+ duplicateDnsConfig = @{ (__bridge NSString *)kSCPropNetDNSServerAddresses:@[@"10.10.10.104", @"10.10.10.105", @"10.10.10.106"],
+ (__bridge NSString *)kSCPropNetDNSSupplementalMatchDomains:@[@TEST_DOMAIN],
+ };
+
+ anotherFakeServiceID = [NSUUID UUID].UUIDString;
+ anotherDNSKey = (__bridge_transfer NSString *)[self copyStateKeyWithServiceID:(__bridge CFStringRef)(anotherFakeServiceID) forEntity:kSCEntNetDNS];
+
+ [test publishToSCDynamicStore:anotherDNSKey value:duplicateDnsConfig];
+ array = [test.testDNS arrayByAddingObjectsFromArray:[test createDNSArray:duplicateDnsConfig]];
+ test.testDNS = array;
+
+ // Wait for the path changes to quiesce
+ [test waitFor:PATH_QUIESCE_TIME];
+
+ // Use NSSet for unordered comparison
+ testDNSSet = [NSSet setWithArray:test.testDNS];
+ pathDNSSet = [NSSet setWithArray:test.pathDNS];
+ success = [testDNSSet isEqualToSet:pathDNSSet];
+ [test removeFromSCDynamicStore:anotherDNSKey];
+ if (!success) {
+ SCTestLog("test DNS and applied DNS for duplicate domains do not match. Test: %@, Applied: %@", test.testDNS, test.pathDNS);
+ break;
+ }
+
+ [test removeFromSCDynamicStore:test.dnsKey];
+ test.testDNS = nil;
+ [test waitFor:PATH_QUIESCE_TIME];
+
+ SCTestLog("Verified that the configured DNS with duplicate domains is same as applied DNS for a domain");
+ success = YES;
+
+ } while (0);
+
+ [pathEvaluator removeObserver:test
+ forKeyPath:@"path"];
+
+ return success;
+}
+
+- (BOOL)unitTestInstallProxyWithConflictingDomain
+{
+ BOOL success = NO;
+ SCTestConfigAgent *test;
+ NSDictionary *proxyConfig;
+ NSString *hostname;
+ NSNumber *port;
+ NWHostEndpoint *hostEndpoint;
+ NWPathEvaluator *pathEvaluator;
+
+ test = [[SCTestConfigAgent alloc] initWithOptions:self.options];
+ proxyConfig = @{(__bridge NSString *)kSCPropNetProxiesHTTPEnable:@(1),
+ (__bridge NSString *)kSCPropNetProxiesHTTPPort:@(80),
+ (__bridge NSString *)kSCPropNetProxiesHTTPProxy:@"10.10.10.100",
+ (__bridge NSString *)kSCPropNetProxiesSupplementalMatchDomains:@[@TEST_DOMAIN],
+ };
+
+ hostname = [[proxyConfig objectForKey:(__bridge NSString *)kSCPropNetProxiesSupplementalMatchDomains] objectAtIndex:0];
+ port = [proxyConfig objectForKey:(__bridge NSString *)kSCPropNetProxiesHTTPPort];
+ hostEndpoint = [NWHostEndpoint endpointWithHostname:hostname port:port.stringValue];
+ pathEvaluator = [[NWPathEvaluator alloc] initWithEndpoint:hostEndpoint parameters:NULL];
+ [pathEvaluator addObserver:test
+ forKeyPath:@"path"
+ options:NSKeyValueObservingOptionNew
+ context:nil];
+
+ do {
+ NSMutableDictionary *dict;
+ NSMutableDictionary *dict2;
+ NSDictionary *duplicateProxyConfig;
+ NSString *anotherFakeServiceID;
+ NSString *anotherProxyKey;
+ NSSet *testProxySet;
+ NSSet *pathProxySet;
+
+ [test publishToSCDynamicStore:test.proxyKey value:proxyConfig];
+ dict = [NSMutableDictionary dictionaryWithDictionary:proxyConfig];
+ [dict removeObjectForKey:(__bridge NSString *)kSCPropNetProxiesSupplementalMatchDomains];
+ test.testProxy = @[@[dict]];
+
+ // Wait for the path changes to quiesce
+ [test waitFor:PATH_QUIESCE_TIME];
+ if (![test.testProxy isEqualToArray:test.pathProxy]) {
+ SCTestLog("test proxy and applied proxy do not match. Test: %@, Applied: %@", test.testProxy, test.pathProxy);
+ break;
+ }
+
+ // Now install the conflicting Proxy configuration
+ duplicateProxyConfig = @{(__bridge NSString *)kSCPropNetProxiesHTTPSEnable:@(1),
+ (__bridge NSString *)kSCPropNetProxiesHTTPSPort:@(8080),
+ (__bridge NSString *)kSCPropNetProxiesHTTPSProxy:@"10.10.10.101",
+ (__bridge NSString *)kSCPropNetProxiesSupplementalMatchDomains:@[@TEST_DOMAIN],
+ };
+ anotherFakeServiceID = [NSUUID UUID].UUIDString;
+ anotherProxyKey = (__bridge_transfer NSString *)[self copyStateKeyWithServiceID:(__bridge CFStringRef)(anotherFakeServiceID) forEntity:kSCEntNetProxies];
+
+ [test publishToSCDynamicStore:anotherProxyKey value:duplicateProxyConfig];;
+ dict2 = [NSMutableDictionary dictionaryWithDictionary:duplicateProxyConfig];
+ [dict2 removeObjectForKey:(__bridge NSString *)kSCPropNetProxiesSupplementalMatchDomains];
+ test.testProxy = @[@[dict],@[dict2]];
+
+ // Wait for the path changes to quiesce
+ [test waitFor:PATH_QUIESCE_TIME];
+
+ // Use NSSet for unordered comparison
+ testProxySet = [NSSet setWithArray:test.testProxy];
+ pathProxySet = [NSSet setWithArray:test.pathProxy];
+ success = [testProxySet isEqualToSet:pathProxySet];
+ [test removeFromSCDynamicStore:anotherProxyKey];
+ if (!success) {
+ SCTestLog("test proxy and applied proxy for duplicate domains do not match. Test: %@, Applied: %@", test.testDNS, test.pathDNS);
+ break;
+ }
+
+ SCTestLog("Verified the configured proxy with Duplicate domains is the same as applied proxy");
+
+ [test removeFromSCDynamicStore:test.proxyKey];
+ test.testProxy = nil;
+
+ // Wait for the path changes to quiesce
+ [test waitFor:PATH_QUIESCE_TIME];
+
+ if (test.pathProxy != nil) {
+ SCTestLog("proxy applied when there is no test proxy");
+ break;
+ }
+
+ success = YES;
+
+ } while(0);
+
+ [pathEvaluator removeObserver:test
+ forKeyPath:@"path"];
+
+ return success;
+}
+
+- (BOOL)tearDown
+{
+ [self removeFromSCDynamicStore:self.proxyKey];
+ [self removeFromSCDynamicStore:self.dnsKey];
+ return YES;
+}
+
+- (void)observeValueForKeyPath:(NSString *)keyPath
+ ofObject:(id)object
+ change:(NSDictionary *)change
+ context:(void *)context
+{
+ NWPathEvaluator *pathEvaluator = (NWPathEvaluator *)object;
+ if ([keyPath isEqualToString:@"path"]) {
+ self.pathProxy = pathEvaluator.path.proxySettings;
+ self.pathDNS = pathEvaluator.path.dnsServers;
+ }
+}
+
+@end