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