1/* 2 * Copyright (c) 2014 Apple Inc. All rights reserved. 3 * 4 * @APPLE_LICENSE_HEADER_START@ 5 * 6 * This file contains Original Code and/or Modifications of Original Code 7 * as defined in and that are subject to the Apple Public Source License 8 * Version 2.0 (the 'License'). You may not use this file except in 9 * compliance with the License. Please obtain a copy of the License at 10 * http://www.opensource.apple.com/apsl/ and read it before using this 11 * file. 12 * 13 * The Original Code and all software distributed under the License are 14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 18 * Please see the License for the specific language governing rights and 19 * limitations under the License. 20 * 21 * @APPLE_LICENSE_HEADER_END@ 22 */ 23 24#include "usb.h" 25 26#include <Foundation/Foundation.h> 27#include <IOKit/usb/IOUSBLib.h> 28 29@interface __USB : NSObject 30 31@end 32 33@implementation __USB 34 35/* 36 * pathForResource:ofType:inBundle 37 * Stolen from UserNotificationCenter-30:localizedInfo.m 38 * 39 * Get the path to the localized resource of the right type 40 */ 41- (NSString *)pathForResource:(NSString *)resource ofType:(NSString *)type inBundle:(NSBundle *)bundle 42{ 43 NSString *userName = NSUserName(), *result = nil; 44 NSArray *preferredLanguages = nil; 45 46 if (!bundle) bundle = [NSBundle mainBundle]; 47 if (userName && ![userName isEqualToString:@""]) 48 { 49 CFPropertyListRef languages = CFPreferencesCopyValue(CFSTR("AppleLanguages"), kCFPreferencesAnyApplication, (__bridge CFStringRef)userName, kCFPreferencesAnyHost); 50 if (languages && CFGetTypeID(languages) != CFArrayGetTypeID()) { 51 CFRelease(languages); 52 languages = nil; 53 } 54 preferredLanguages = (__bridge NSArray *)languages; 55 } 56 if (bundle) 57 { 58 NSArray *bundleLocalizations = [bundle localizations], *preferredLocalizations = [NSBundle preferredLocalizationsFromArray:bundleLocalizations forPreferences:preferredLanguages]; 59 unsigned i; 60 NSUInteger count = [preferredLocalizations count]; 61 for (i = 0; !result && i < count; i++) 62 { 63 result = [bundle pathForResource:resource ofType:type inDirectory:nil forLocalization:[preferredLocalizations objectAtIndex:i]]; 64 } 65 if (!result) 66 { 67 NSString *developmentLocalization = [bundle developmentLocalization]; 68 if (developmentLocalization) { 69 result = [bundle pathForResource:resource ofType:type inDirectory:nil forLocalization:developmentLocalization]; 70 } 71 } 72 } 73 74 return result; 75} 76 77- (NSDictionary *)localizationDictForTable:(NSString *)table inBundle:(NSBundle *)bundle 78{ 79 NSDictionary *result = nil; 80 NSString *localizedStringsPath = [self pathForResource:(table ? table : @"Localizable") ofType:@"strings" inBundle:bundle]; 81 if (localizedStringsPath && ![localizedStringsPath isEqualToString:@""]) { 82 result = [NSDictionary dictionaryWithContentsOfFile:localizedStringsPath]; 83 } 84 85 return result; 86} 87 88- (NSString *)getLocalizedStringFromTableInBundle:(NSString *)string inBundle:(NSBundle *)bundle 89{ 90 NSString *localizedString; 91 NSString *tableName = @"Localizable"; 92 93 NSDictionary *dict = [self localizationDictForTable:tableName inBundle:bundle]; 94 localizedString = [dict objectForKey:string]; 95 96 if (localizedString == nil) 97 { 98 localizedString = [bundle localizedStringForKey: string 99 value: @"" // Blank is better than some weird error string 100 table: tableName]; 101 } 102 103 return localizedString ? localizedString : string; 104} 105 106- (NSString *) getLocalizedNameForDeviceName:(NSString *)deviceName 107{ 108 NSBundle *usbFamilyBundle = [NSBundle bundleWithURL:[NSURL fileURLWithPath:@"/System/Library/Extensions/IOUSBFamily.kext"]]; 109 NSBundle *spUSBReporterBundle = [NSBundle bundleWithURL:[NSURL fileURLWithPath:@"/System/Library/SystemProfiler/SPUSBReporter.spreporter"]]; 110 NSMutableString *localizedName = nil; 111 112 if (deviceName) 113 { 114 // SPUSBReporter has localization names for Apple's devices 115 localizedName = (NSMutableString *)[self getLocalizedStringFromTableInBundle:deviceName inBundle:spUSBReporterBundle]; 116 } 117 else 118 { 119 localizedName = (NSMutableString *)[self getLocalizedStringFromTableInBundle:@"kUSBDefaultUSBDeviceName" inBundle:usbFamilyBundle]; 120 } 121 122 return localizedName; 123} 124 125- (BOOL) isDeviceAuthorizable:(io_service_t) usbDevice 126{ 127 NSNumber * bDeviceClass; 128 NSNumber * locationID; 129 NSNumber * builtIn = nil; 130 BOOL isRootHub = NO; 131 132 // NOTE: We will not allow the following devices to be authorized: 133 // 134 // 1. Built-in devices (they will have a "Built-In" property. This is a temporary workaround for root hubs: locationID integerValue]) & 0x00ffffff) 135 // 2. USB Hub Devices 136 137 builtIn = CFBridgingRelease(IORegistryEntryCreateCFProperty(usbDevice, CFSTR("Built-In"), kCFAllocatorDefault, 0)); 138 locationID = CFBridgingRelease(IORegistryEntryCreateCFProperty(usbDevice, CFSTR(kUSBDevicePropertyLocationID), kCFAllocatorDefault, 0)); 139 bDeviceClass = CFBridgingRelease(IORegistryEntryCreateCFProperty(usbDevice, CFSTR(kUSBDeviceClass), kCFAllocatorDefault, 0)); 140 141 isRootHub = ((((int)[locationID integerValue]) & 0x00ffffff) == 0); 142 143 if ( ([builtIn isEqual: @(YES)]) || 144 (isRootHub) || 145 ([bDeviceClass integerValue] == kUSBHubClass) 146 ) 147 return NO; 148 else 149 return YES; 150} 151 152// This method will check to see that all the fields in the first dicionary match the ones in the second dictionary. It assumes the dictionaries 153// are the identifier dictionaries 154 155- (BOOL) areDevicesEqual:(NSDictionary *)firstDevice secondDevice:(NSDictionary *)secondDevice 156{ 157 BOOL areEqual = NO; 158 159 // Both are USB devices, now check to see if the USB vid/pid/release are the same 160 if ([[firstDevice objectForKey:@kUSBVendorID] isEqualToNumber:[secondDevice objectForKey:@kUSBVendorID]] && 161 [[firstDevice objectForKey:@kUSBProductID] isEqualToNumber:[secondDevice objectForKey:@kUSBProductID]] && 162 [[firstDevice objectForKey:@kUSBDeviceReleaseNumber] isEqualToNumber:[secondDevice objectForKey:@kUSBDeviceReleaseNumber]] 163 ) 164 { 165 // We need to check and see if we have a serial number. If we don't, then look at the locationID and if the same, then assume the device is the same. 166 if ( ([firstDevice objectForKey:@kUSBSerialNumberString] == nil) && ([secondDevice objectForKey:@kUSBSerialNumberString] == nil) ) 167 { 168 if ( [[firstDevice objectForKey:@kUSBDevicePropertyLocationID] isEqualToNumber:[secondDevice objectForKey:@kUSBDevicePropertyLocationID]] ) 169 { 170 // Same locationIDs 171 areEqual = YES; 172 } 173 } 174 else 175 { 176 // We have a serial number. If we do, we will assume that it's the same device, regardless of the locationID. This 177 // will not catch devices where we have the same serial number. If we want to ALWAYS include the locationID in the test, then we will need to modify 178 // this testing 179 if ( ([firstDevice objectForKey:@kUSBSerialNumberString] != nil) && 180 [[firstDevice objectForKey:@kUSBSerialNumberString] isEqualToString:[secondDevice objectForKey:@kUSBSerialNumberString]] 181 ) 182 { 183 // Same serial #'s 184 areEqual = YES; 185 } 186 } 187 } 188 189 return areEqual; 190} 191 192@end 193 194CFDictionaryRef _IOUSBDeviceCopyIdentifier( io_service_t service ) 195{ 196 CFMutableDictionaryRef properties = 0; 197 CFMutableDictionaryRef identifier = 0; 198 199 IORegistryEntryCreateCFProperties( service, &properties, kCFAllocatorDefault, 0 ); 200 201 if ( properties ) 202 { 203 identifier = IOServiceMatching( kIOUSBDeviceClassName ); 204 205 if ( identifier ) 206 { 207 CFTypeRef value; 208 209 value = CFDictionaryGetValue( properties, CFSTR( kUSBDevicePropertyLocationID ) ); 210 211 CFDictionarySetValue( identifier, CFSTR( kUSBDevicePropertyLocationID ), value ); 212 213 value = CFDictionaryGetValue( properties, CFSTR( kUSBProductID ) ); 214 215 CFDictionarySetValue( identifier, CFSTR( kUSBProductID ), value ); 216 217 value = CFDictionaryGetValue( properties, CFSTR( kUSBProductString ) ); 218 219 if ( value ) 220 { 221 CFDictionarySetValue( identifier, CFSTR( kUSBProductString ), value ); 222 } 223 224 value = CFDictionaryGetValue( properties, CFSTR( kUSBDeviceReleaseNumber ) ); 225 226 CFDictionarySetValue( identifier, CFSTR( kUSBDeviceReleaseNumber ), value ); 227 228 value = CFDictionaryGetValue( properties, CFSTR( kUSBSerialNumberString ) ); 229 230 if ( value ) 231 { 232 CFDictionarySetValue( identifier, CFSTR( kUSBSerialNumberString ), value ); 233 } 234 235 value = CFDictionaryGetValue( properties, CFSTR( kUSBVendorID ) ); 236 237 CFDictionarySetValue( identifier, CFSTR( kUSBVendorID ), value ); 238 } 239 240 CFRelease( properties ); 241 } 242 243 return identifier; 244} 245 246CFStringRef _IOUSBDeviceCopyName( CFDictionaryRef identifier ) 247{ 248 CFStringRef name; 249 250 name = CFDictionaryGetValue( identifier, CFSTR( kUSBProductString ) ); 251 252 return CFBridgingRetain( [ [ [ __USB alloc ] init ] getLocalizedNameForDeviceName: ( __bridge id ) name ] ); 253} 254 255Boolean _IOUSBDeviceIsEqual( CFDictionaryRef identifier1, CFDictionaryRef identifier2 ) 256{ 257 return [ [ [ __USB alloc ] init ] areDevicesEqual: ( __bridge id ) identifier1 secondDevice: ( __bridge id ) identifier2 ]; 258} 259 260Boolean _IOUSBDeviceIsValid( io_service_t service ) 261{ 262 return [ [ [ __USB alloc ] init ] isDeviceAuthorizable: service ]; 263} 264