2 * Copyright (c) 2016, 2017, 2019 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@
25 #import "SCTestUtils.h"
27 #import <SystemConfiguration/scprefs_observer.h>
29 @interface SCTestPreferences : SCTest
30 @property SCPreferencesRef prefs;
31 @property dispatch_semaphore_t sem;
32 @property int counter;
35 @implementation SCTestPreferences
39 return @"preferences";
42 + (NSString *)commandDescription
44 return @"Tests the SCPreferences code path";
47 - (instancetype)initWithOptions:(NSDictionary *)options
49 self = [super initWithOptions:options];
51 _prefs = SCPreferencesCreate(kCFAllocatorDefault, CFSTR("SCTest"), NULL);
58 if (self.prefs != NULL) {
59 CFRelease(self.prefs);
66 if (self.options[kSCTestPreferencesServiceList]) {
67 NSDictionary *services = (__bridge NSDictionary *)SCPreferencesGetValue(self.prefs, kSCPrefNetworkServices);
68 if (services != nil) {
69 [self printNetworkServicesFromDict:services];
71 SCTestLog("No services present!");
75 if (self.options[kSCTestPreferencesServiceOrder]) {
76 SCNetworkSetRef set = SCNetworkSetCopyCurrent(self.prefs);
77 NSArray *serviceID = (__bridge NSArray *)SCNetworkSetGetServiceOrder(set);
78 NSDictionary *services = (__bridge NSDictionary *)SCPreferencesGetValue(self.prefs, kSCPrefNetworkServices);
80 SCTestLog("Network service order");
81 for (NSString *key in serviceID) {
82 NSDictionary *dict = [services objectForKey:key];
83 SCTestLog("\n%d: %@\n\tUserDefinedName: %@", counter++, key, [dict objectForKey:(__bridge NSString *)kSCPropNetServiceUserDefinedName]);
88 [self cleanupAndExitWithErrorCode:0];
91 - (void)printNetworkServicesFromDict:(NSDictionary *)serviceDict
94 SCTestLog("Network Services");
95 for (NSString *key in serviceDict) {
96 NSDictionary *dict = [serviceDict objectForKey:key];
97 SCTestLog("\n%d: %@\n\tUserDefinedName: %@", counter++, key, [dict objectForKey:(__bridge NSString *)kSCPropNetServiceUserDefinedName]);
103 BOOL allUnitTestsPassed = YES;
104 allUnitTestsPassed &= [self unitTestNetworkServicesSanity];
105 allUnitTestsPassed &= [self unitTestPreferencesAPI];
106 allUnitTestsPassed &= [self unitTestPreferencesNotifications];
107 allUnitTestsPassed &= [self unitTestPreferencesObserver];
108 allUnitTestsPassed &= [self unitTestPreferencesSession];
109 return allUnitTestsPassed;
113 - (BOOL)unitTestNetworkServicesSanity
115 // We verify that every service has a unique name, an interface, an IPv4 config method and and IPv6 config method.
117 NSDictionary *services;
118 SCTestPreferences *test;
120 test = [[SCTestPreferences alloc] initWithOptions:self.options];
122 sets = (__bridge_transfer NSArray *)SCNetworkSetCopyAll(test.prefs);
123 if (sets == nil || [sets count] == 0) {
124 SCTestLog("No sets present!");
128 services = (__bridge NSDictionary *)SCPreferencesGetValue(test.prefs, kSCPrefNetworkServices);
129 if (services == nil || [services count] == 0) {
130 SCTestLog("No services present!");
134 for (id setPtr in sets) {
135 SCNetworkSetRef set = (__bridge SCNetworkSetRef)setPtr;
136 NSArray *serviceArray = nil;
137 NSMutableArray *serviceNameArray = nil;
140 setID = (__bridge NSString *)SCNetworkSetGetSetID(set);
142 serviceArray = (__bridge_transfer NSArray *)SCNetworkSetCopyServices(set);
143 if (serviceArray == nil) {
144 SCTestLog("No services in set %@!", setID);
148 serviceNameArray = [[NSMutableArray alloc] init];
149 for (id servicePTR in serviceArray) {
150 NSDictionary *serviceDict;
151 NSDictionary *ipv4Dict;
152 NSDictionary *ipv6Dict;
153 NSDictionary *ipv4ProtocolConfig;
154 NSDictionary *ipv6ProtocolConfig;
155 NSString *serviceName;
157 NSString *interfaceType;
158 SCNetworkServiceRef service;
159 SCNetworkInterfaceRef interface;
160 SCNetworkProtocolRef ipv4Protocol;
161 SCNetworkProtocolRef ipv6Protocol;
164 service = (__bridge SCNetworkServiceRef)servicePTR;
165 serviceID = (__bridge NSString *)SCNetworkServiceGetServiceID(service);
167 serviceDict = [services objectForKey:serviceID];
168 if (![serviceDict isKindOfClass:[NSDictionary class]]) {
169 SCTestLog("Service is not a dictionary");
173 serviceName = (__bridge NSString *)SCNetworkServiceGetName(service);
174 if (serviceName != nil) {
175 // Check if the name is unique
176 BOOL namePresent = [serviceNameArray containsObject:serviceName];
178 [serviceNameArray addObject:serviceName];
180 SCTestLog("Duplicate services with name %@ exist", serviceName);
184 SCTestLog("Service ID %@ does not have a name", serviceID);
188 interface = SCNetworkServiceGetInterface(service);
189 if (interface == nil) {
190 SCTestLog("Service %@ does not have an interface", serviceName);
194 interfaceType = (__bridge NSString *)SCNetworkInterfaceGetInterfaceType(interface);
195 if (interfaceType == nil || [interfaceType length] == 0) {
196 SCTestLog("Service %@ does not have an interface type", serviceName);
200 if ([interfaceType containsString:@"CommCenter"]) {
201 // CommCenter services typically do not have an ipv4/v6 data OR config method. Skip such services.
204 #endif // TARGET_OS_IPHONE
205 ipv4Protocol = SCNetworkServiceCopyProtocol(service, kSCNetworkProtocolTypeIPv4);
206 ipv6Protocol = SCNetworkServiceCopyProtocol(service, kSCNetworkProtocolTypeIPv6);
208 if (ipv4Protocol != NULL) {
209 ipv4ProtocolConfig = (__bridge NSDictionary *)SCNetworkProtocolGetConfiguration(ipv4Protocol);
210 if (ipv4ProtocolConfig != nil) {
211 ipv4Dict = [ipv4ProtocolConfig copy];
213 CFRelease(ipv4Protocol);
216 if (ipv6Protocol != NULL) {
217 ipv6ProtocolConfig = (__bridge NSDictionary *)SCNetworkProtocolGetConfiguration(ipv6Protocol);
218 if (ipv6ProtocolConfig != nil) {
219 ipv6Dict = [ipv6ProtocolConfig copy];
221 CFRelease(ipv6Protocol);
224 // Check that we have at least one IP config method
225 if (ipv4Dict == nil && ipv6Dict == nil) {
226 SCTestLog("Service %@ does not have an IP dictionary", serviceName);
230 if ([ipv4Dict objectForKey:(__bridge NSString *)kSCPropNetIPv4ConfigMethod] == nil &&
231 [ipv6Dict objectForKey:(__bridge NSString *)kSCPropNetIPv6ConfigMethod] == nil) {
232 SCTestLog("Service %@ does not have an IP Config Method", serviceName);
238 SCTestLog("Verified that the Network Services have valid configurations");
244 myNotificationsCallback(SCPreferencesRef prefs, SCPreferencesNotification notificationType, void *ctx)
246 #pragma unused(prefs)
247 #pragma unused(notificationType)
248 SCTestPreferences *test = (__bridge SCTestPreferences *)ctx;
250 if (test.sem != NULL) {
251 dispatch_semaphore_signal(test.sem);
255 - (BOOL)unitTestPreferencesNotifications
257 dispatch_queue_t callbackQ;
258 const int iterations = 10;
260 SCTestPreferences *test;
261 const int timeout = 1; // second
263 test = [[SCTestPreferences alloc] initWithOptions:self.options];
264 if (test.prefs != NULL) {
265 CFRelease(test.prefs);
269 test.sem = dispatch_semaphore_create(0);
271 SCPreferencesContext ctx = {0, (__bridge void * _Nullable)(test), CFRetain, CFRelease, NULL};
272 NSDictionary *prefsOptions = @{(__bridge NSString *)kSCPreferencesOptionRemoveWhenEmpty:(__bridge NSNumber *)kCFBooleanTrue};
273 test.prefs = SCPreferencesCreateWithOptions(NULL,
275 CFSTR("SCTestPreferences.plist"),
276 kSCPreferencesUseEntitlementAuthorization,
277 (__bridge CFDictionaryRef)prefsOptions);
278 if (test.prefs == NULL) {
279 SCTestLog("Failed to create SCPreferences. Error: %s", SCErrorString(SCError()));
284 ok = SCPreferencesSetCallback(test.prefs, myNotificationsCallback, &ctx);
286 SCTestLog("Failed to set callback. Error: %s", SCErrorString(SCError()));
290 callbackQ = dispatch_queue_create("SCTestPreferences callback queue", NULL);
291 ok = SCPreferencesSetDispatchQueue(test.prefs, callbackQ);
293 SCTestLog("Failed to set dispatch queue. Error: %s", SCErrorString(SCError()));
297 for (int i = 0; i < iterations; i++) {
298 NSUUID *uuid = [NSUUID UUID];
299 ok = SCPreferencesSetValue(test.prefs, CFSTR("test"), (__bridge CFStringRef)uuid.UUIDString);
301 SCTestLog("Failed to set value. Error: %s", SCErrorString(SCError()));
304 ok = SCPreferencesCommitChanges(test.prefs);
306 SCTestLog("Failed to commit change. Error: %s", SCErrorString(SCError()));
309 if (dispatch_semaphore_wait(test.sem, dispatch_time(DISPATCH_TIME_NOW, timeout * NSEC_PER_SEC))) {
310 SCTestLog("Failed to get SCPreferences notification callback: #%d", i);
316 ok = SCPreferencesRemoveValue(test.prefs, CFSTR("test"));
318 SCTestLog("Failed to remove value. Error: %s", SCErrorString(SCError()));
321 ok = SCPreferencesCommitChanges(test.prefs);
323 SCTestLog("Failed to commit change. Error: %s", SCErrorString(SCError()));
326 if (dispatch_semaphore_wait(test.sem, dispatch_time(DISPATCH_TIME_NOW, timeout * NSEC_PER_SEC))) {
327 SCTestLog("Failed to get SCPreferences notification callback: cleanup");
332 ok = SCPreferencesSetDispatchQueue(test.prefs, NULL);
334 SCTestLog("Failed to clear dispatch queue. Error: %s", SCErrorString(SCError()));
338 SCTestLog("Verified that %d SCPreferences notification callbacks were delivered", iterations);
342 CFRelease(test.prefs);
348 #define PREFS_OBSERVER_DOMAIN "com.apple.sctest"
349 #define PREFS_OBSERVER_PLIST PREFS_OBSERVER_DOMAIN ".plist"
350 #define MANAGED_PREFERENCES_PATH "/Library/Managed Preferences"
351 #if !TARGET_OS_IPHONE
352 #define PREFS_OBSERVER_KEY "com.apple.MCX._managementStatusChangedForDomains"
353 #define PREFS_OBSERVER_TYPE scprefs_observer_type_mcx
354 #define MANAGED_PREFERENCES_USER kCFPreferencesAnyUser
356 #define PREFS_OBSERVER_KEY "com.apple.ManagedConfiguration.profileListChanged"
357 #define PREFS_OBSERVER_TYPE scprefs_observer_type_global
358 #define MANAGED_PREFERENCES_MOBILE_PATH MANAGED_PREFERENCES_PATH "/mobile"
359 #define MANAGED_PREFERENCES_USER CFSTR("mobile")
362 #import <CoreFoundation/CFPreferences_Private.h>
364 - (BOOL)unitTestPreferencesObserver
366 dispatch_queue_t callbackQ;
367 const int iterations = 10;
368 scprefs_observer_t observer;
370 SCTestPreferences *test;
371 const int timeout = 1; // second
373 test = [[SCTestPreferences alloc] initWithOptions:self.options];
374 if (test.prefs != NULL) {
375 CFRelease(test.prefs);
379 test.sem = dispatch_semaphore_create(0);
381 callbackQ = dispatch_queue_create("SCTestPreferences callback queue", NULL);
382 observer = _scprefs_observer_watch(PREFS_OBSERVER_TYPE, PREFS_OBSERVER_PLIST, callbackQ, ^{
384 if (test.sem != NULL) {
385 dispatch_semaphore_signal(test.sem);
389 NSDictionary *prefsOptions = @{(__bridge NSString *)kSCPreferencesOptionRemoveWhenEmpty:(__bridge NSNumber *)kCFBooleanTrue};
390 test.prefs = SCPreferencesCreateWithOptions(NULL,
392 CFSTR(MANAGED_PREFERENCES_PATH "/" PREFS_OBSERVER_PLIST),
394 (__bridge CFDictionaryRef)prefsOptions);
395 if (test.prefs == NULL) {
396 SCTestLog("Failed to create SCPreferences. Error: %s", SCErrorString(SCError()));
400 // let's make sure that the "/Library/Managed Configuration[/mobile]" directory exists
401 mkdir(MANAGED_PREFERENCES_PATH, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH);
403 mkdir(MANAGED_PREFERENCES_MOBILE_PATH, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH);
404 #endif // TARGET_OS_IPHONE
406 for (int i = 0; i < iterations; i++) {
408 NSUUID *uuid = [NSUUID UUID];
409 NSDictionary *keysAndValues = @{@"test" : uuid.UUIDString};
410 ok = _CFPreferencesWriteManagedDomain((__bridge CFDictionaryRef)keysAndValues,
411 MANAGED_PREFERENCES_USER,
413 CFSTR(PREFS_OBSERVER_DOMAIN));
415 SCTestLog("Failed to write (update) managed preferences");
419 notify_post(PREFS_OBSERVER_KEY);
420 #endif // TARGET_OS_IPHONE
421 if (dispatch_semaphore_wait(test.sem, dispatch_time(DISPATCH_TIME_NOW, timeout * NSEC_PER_SEC))) {
422 SCTestLog("Failed to get SCPreferences observer callback: #%d", i);
429 notify_post(PREFS_OBSERVER_KEY);
430 [test waitFor:0.01]; // delay after unrelated change
433 ok = _CFPreferencesWriteManagedDomain((__bridge CFDictionaryRef)[NSDictionary dictionary],
434 MANAGED_PREFERENCES_USER,
436 CFSTR(PREFS_OBSERVER_DOMAIN));
438 SCTestLog("Failed to write (remove) managed preferences");
442 notify_post(PREFS_OBSERVER_KEY);
443 #endif // TARGET_OS_IPHONE
444 if (dispatch_semaphore_wait(test.sem, dispatch_time(DISPATCH_TIME_NOW, timeout * NSEC_PER_SEC))) {
445 SCTestLog("Failed to get SCPreferences observer callback: cleanup");
450 SCTestLog("Verified that %d CF/SCPreferences observer callbacks were delivered", iterations);
454 _scprefs_observer_cancel(observer);
456 // cleanup the "/Library/Managed Configuration[/mobile]" directory
458 rmdir(MANAGED_PREFERENCES_MOBILE_PATH);
459 #endif // TARGET_OS_IPHONE
460 rmdir(MANAGED_PREFERENCES_PATH);
465 - (BOOL)unitTestPreferencesSession
467 SCPreferencesRef prefs;
469 prefs = SCPreferencesCreate(kCFAllocatorDefault, CFSTR("SCTest"), NULL);
471 SCTestLog("Failed to create SCPreferences. Error: %s", SCErrorString(SCError()));
476 prefs = SCPreferencesCreateWithOptions(kCFAllocatorDefault, CFSTR("SCTest"), NULL, kSCPreferencesUseEntitlementAuthorization, NULL);
478 SCTestLog("Failed to create SCPreferences w/options. Error: %s", SCErrorString(SCError()));
483 prefs = SCPreferencesCreateWithAuthorization(kCFAllocatorDefault, CFSTR("SCTest"), NULL, kSCPreferencesUseEntitlementAuthorization);
485 SCTestLog("Failed to create SCPreferences w/options. Error: %s", SCErrorString(SCError()));
490 SCTestLog("Verified that the preferences session can be created");
494 - (BOOL)unitTestPreferencesAPI
497 int iterations = 100;
498 NSDictionary *prefsOptions;
499 NSMutableArray *keys;
500 NSMutableArray *values;
501 SCTestPreferences *test;
504 test = [[SCTestPreferences alloc] initWithOptions:self.options];
505 if (test.prefs != NULL) {
506 CFRelease(test.prefs);
510 prefsOptions = @{(__bridge NSString *)kSCPreferencesOptionRemoveWhenEmpty:(__bridge NSNumber *)kCFBooleanTrue};
511 test.prefs = SCPreferencesCreateWithOptions(kCFAllocatorDefault,
513 CFSTR("SCTestPreferences.plist"),
514 kSCPreferencesUseEntitlementAuthorization,
515 (__bridge CFDictionaryRef)prefsOptions);
516 if (test.prefs == NULL) {
517 SCTestLog("Failed to create a preferences session. Error: %s", SCErrorString(SCError()));
521 keys = [[NSMutableArray alloc] init];
522 values = [[NSMutableArray alloc] init];
523 for (int i = 0; i < iterations; i++) {
524 NSUUID *uuidKey = [NSUUID UUID];
525 NSUUID *uuidValue = [NSUUID UUID];
527 ok = SCPreferencesLock(test.prefs, true);
529 SCTestLog("Failed to get preferences lock. Error: %s", SCErrorString(SCError()));
533 ok = SCPreferencesSetValue(test.prefs, (__bridge CFStringRef)uuidKey.UUIDString, (__bridge CFStringRef)uuidValue.UUIDString);
535 SCTestLog("Failed to set preferences value. Error: %s", SCErrorString(SCError()));
539 ok = SCPreferencesUnlock(test.prefs);
541 SCTestLog("Failed to release preferences lock. Error: %s", SCErrorString(SCError()));
545 [keys addObject:uuidKey.UUIDString];
546 [values addObject:uuidValue.UUIDString];
549 ok = SCPreferencesCommitChanges(test.prefs);
551 SCTestLog("Failed to commit preferences. Error: %s", SCErrorString(SCError()));
555 CFRelease(test.prefs);
556 test.prefs = SCPreferencesCreateWithOptions(kCFAllocatorDefault,
558 CFSTR("SCTestPreferences.plist"),
559 kSCPreferencesUseEntitlementAuthorization,
560 (__bridge CFDictionaryRef)prefsOptions);
561 if (test.prefs == NULL) {
562 SCTestLog("Failed to create a preferences session. Error: %s", SCErrorString(SCError()));
566 keyList = (__bridge_transfer NSArray *)SCPreferencesCopyKeyList(test.prefs);
567 if ([keyList count] < [keys count]) {
568 SCTestLog("Failed to copy all keys from preferences. Error: %s", SCErrorString(SCError()));
572 for (NSString *key in keys) {
573 NSString *valueString = (__bridge NSString *)SCPreferencesGetValue(test.prefs, (__bridge CFStringRef)key);
575 SCTestLog("Failed to get value from preferences. Error: %s", SCErrorString(SCError()));
579 BOOL ok = [values containsObject:valueString];
581 SCTestLog("Incorrect value fetched from preferences");
586 ok = SCPreferencesRemoveAllValues(test.prefs);
588 SCTestLog("Failed to remove values preferences. Error: %s", SCErrorString(SCError()));
592 ok = SCPreferencesCommitChanges(test.prefs);
594 SCTestLog("Failed to commit preferences. Error: %s", SCErrorString(SCError()));
598 CFRelease(test.prefs);
599 test.prefs = SCPreferencesCreateWithOptions(kCFAllocatorDefault,
601 CFSTR("SCTestPreferences.plist"),
602 kSCPreferencesUseEntitlementAuthorization,
603 (__bridge CFDictionaryRef)prefsOptions);
604 if (test.prefs == NULL) {
605 SCTestLog("Failed to create a preferences session. Error: %s", SCErrorString(SCError()));
609 keyList = (__bridge_transfer NSArray *)SCPreferencesCopyKeyList(test.prefs);
610 if ([keyList count] > 0) {
611 SCTestLog("Failed to remove all keys from preferences. Error: %s", SCErrorString(SCError()));
615 SCTestLog("Verified that SCPreferences APIs behave as expected");
619 - (void)cleanupAndExitWithErrorCode:(int)error
621 [super cleanupAndExitWithErrorCode:error];