Upload upstream chromium 69.0.3497
[platform/framework/web/chromium-efl.git] / device / serial / serial_device_enumerator_mac.cc
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "device/serial/serial_device_enumerator_mac.h"
6
7 #include <IOKit/serial/IOSerialKeys.h>
8 #include <IOKit/usb/IOUSBLib.h>
9 #include <stdint.h>
10
11 #include <algorithm>
12 #include <memory>
13 #include <unordered_set>
14 #include <utility>
15
16 #include "base/files/file_enumerator.h"
17 #include "base/files/file_path.h"
18 #include "base/files/file_util.h"
19 #include "base/mac/scoped_cftyperef.h"
20 #include "base/mac/scoped_ioobject.h"
21 #include "base/metrics/histogram_functions.h"
22 #include "base/strings/pattern.h"
23 #include "base/strings/string_util.h"
24 #include "base/strings/sys_string_conversions.h"
25 #include "base/threading/thread_restrictions.h"
26
27 namespace device {
28
29 namespace {
30
31 // Searches a service and all ancestor services for a property with the
32 // specified key, returning NULL if no such key was found.
33 CFTypeRef GetCFProperty(io_service_t service, const CFStringRef key) {
34   // We search for the specified property not only on the specified service, but
35   // all ancestors of that service. This is important because if a device is
36   // both serial and USB, in the registry tree it appears as a serial service
37   // with a USB service as its ancestor. Without searching ancestors services
38   // for the specified property, we'd miss all USB properties.
39   return IORegistryEntrySearchCFProperty(
40       service, kIOServicePlane, key, NULL,
41       kIORegistryIterateRecursively | kIORegistryIterateParents);
42 }
43
44 // Searches a service and all ancestor services for a string property with the
45 // specified key, returning NULL if no such key was found.
46 CFStringRef GetCFStringProperty(io_service_t service, const CFStringRef key) {
47   CFTypeRef value = GetCFProperty(service, key);
48   if (value && (CFGetTypeID(value) == CFStringGetTypeID()))
49     return static_cast<CFStringRef>(value);
50
51   return NULL;
52 }
53
54 // Searches a service and all ancestor services for a number property with the
55 // specified key, returning NULL if no such key was found.
56 CFNumberRef GetCFNumberProperty(io_service_t service, const CFStringRef key) {
57   CFTypeRef value = GetCFProperty(service, key);
58   if (value && (CFGetTypeID(value) == CFNumberGetTypeID()))
59     return static_cast<CFNumberRef>(value);
60
61   return NULL;
62 }
63
64 // Searches the specified service for a string property with the specified key,
65 // sets value to that property's value, and returns whether the operation was
66 // successful.
67 bool GetStringProperty(io_service_t service,
68                        const CFStringRef key,
69                        std::string* value) {
70   CFStringRef propValue = GetCFStringProperty(service, key);
71   if (propValue) {
72     *value = base::SysCFStringRefToUTF8(propValue);
73     return true;
74   }
75
76   return false;
77 }
78
79 // Searches the specified service for a uint16_t property with the specified
80 // key, sets value to that property's value, and returns whether the operation
81 // was successful.
82 bool GetUInt16Property(io_service_t service,
83                        const CFStringRef key,
84                        uint16_t* value) {
85   CFNumberRef propValue = GetCFNumberProperty(service, key);
86   if (propValue) {
87     int intValue;
88     if (CFNumberGetValue(propValue, kCFNumberIntType, &intValue)) {
89       *value = static_cast<uint16_t>(intValue);
90       return true;
91     }
92   }
93
94   return false;
95 }
96
97 // Returns value clamped to the range of [min, max].
98 int Clamp(int value, int min, int max) {
99   return std::min(std::max(value, min), max);
100 }
101
102 // Returns an array of devices as retrieved through the new method of
103 // enumerating serial devices (IOKit).  This new method gives more information
104 // about the devices than the old method.
105 std::vector<mojom::SerialDeviceInfoPtr> GetDevicesNew() {
106   std::vector<mojom::SerialDeviceInfoPtr> devices;
107
108   // Make a service query to find all serial devices.
109   CFMutableDictionaryRef matchingDict =
110       IOServiceMatching(kIOSerialBSDServiceValue);
111   if (!matchingDict)
112     return devices;
113
114   io_iterator_t it;
115   kern_return_t kr =
116       IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDict, &it);
117   if (kr != KERN_SUCCESS)
118     return devices;
119
120   base::mac::ScopedIOObject<io_iterator_t> scoped_it(it);
121   base::mac::ScopedIOObject<io_service_t> scoped_device;
122   while (scoped_device.reset(IOIteratorNext(scoped_it.get())), scoped_device) {
123     auto callout_info = mojom::SerialDeviceInfo::New();
124
125     uint16_t vendorId;
126     if (GetUInt16Property(scoped_device.get(), CFSTR(kUSBVendorID),
127                           &vendorId)) {
128       callout_info->has_vendor_id = true;
129       callout_info->vendor_id = vendorId;
130     }
131
132     uint16_t productId;
133     if (GetUInt16Property(scoped_device.get(), CFSTR(kUSBProductID),
134                           &productId)) {
135       callout_info->has_product_id = true;
136       callout_info->product_id = productId;
137     }
138
139     std::string display_name;
140     if (GetStringProperty(scoped_device.get(), CFSTR(kUSBProductString),
141                           &display_name)) {
142       callout_info->display_name = std::move(display_name);
143     }
144
145     // Each serial device has two "paths" in /dev/ associated with it: a
146     // "dialin" path starting with "tty" and a "callout" path starting with
147     // "cu". Each of these is considered a different device from Chrome's
148     // standpoint, but both should share the device's USB properties.
149     std::string dialinDevice;
150     if (GetStringProperty(scoped_device.get(), CFSTR(kIODialinDeviceKey),
151                           &dialinDevice)) {
152       mojom::SerialDeviceInfoPtr dialin_info = callout_info.Clone();
153       dialin_info->path = dialinDevice;
154       devices.push_back(std::move(dialin_info));
155     }
156
157     std::string calloutDevice;
158     if (GetStringProperty(scoped_device.get(), CFSTR(kIOCalloutDeviceKey),
159                           &calloutDevice)) {
160       callout_info->path = calloutDevice;
161       devices.push_back(std::move(callout_info));
162     }
163   }
164
165   return devices;
166 }
167
168 // Returns an array of devices as retrieved through the old method of
169 // enumerating serial devices (pattern matching in /dev/). This old method gives
170 // less information about the devices than the new method.
171 std::vector<mojom::SerialDeviceInfoPtr> GetDevicesOld() {
172   const base::FilePath kDevRoot("/dev");
173   const int kFilesAndSymLinks =
174       base::FileEnumerator::FILES | base::FileEnumerator::SHOW_SYM_LINKS;
175
176   std::set<std::string> valid_patterns;
177   valid_patterns.insert("/dev/*Bluetooth*");
178   valid_patterns.insert("/dev/*Modem*");
179   valid_patterns.insert("/dev/*bluetooth*");
180   valid_patterns.insert("/dev/*modem*");
181   valid_patterns.insert("/dev/*serial*");
182   valid_patterns.insert("/dev/tty.*");
183   valid_patterns.insert("/dev/cu.*");
184
185   std::vector<mojom::SerialDeviceInfoPtr> devices;
186   base::FileEnumerator enumerator(kDevRoot, false, kFilesAndSymLinks);
187   do {
188     const base::FilePath next_device_path(enumerator.Next());
189     const std::string next_device = next_device_path.value();
190     if (next_device.empty())
191       break;
192
193     std::set<std::string>::const_iterator i = valid_patterns.begin();
194     for (; i != valid_patterns.end(); ++i) {
195       if (base::MatchPattern(next_device, *i)) {
196         auto info = mojom::SerialDeviceInfo::New();
197         info->path = next_device;
198         devices.push_back(std::move(info));
199         break;
200       }
201     }
202   } while (true);
203   return devices;
204 }
205
206 }  // namespace
207
208 // static
209 std::unique_ptr<SerialDeviceEnumerator> SerialDeviceEnumerator::Create() {
210   return std::unique_ptr<SerialDeviceEnumerator>(
211       new SerialDeviceEnumeratorMac());
212 }
213
214 SerialDeviceEnumeratorMac::SerialDeviceEnumeratorMac() {}
215
216 SerialDeviceEnumeratorMac::~SerialDeviceEnumeratorMac() {}
217
218 std::vector<mojom::SerialDeviceInfoPtr>
219 SerialDeviceEnumeratorMac::GetDevices() {
220   base::AssertBlockingAllowed();
221
222   std::vector<mojom::SerialDeviceInfoPtr> devices = GetDevicesNew();
223   std::vector<mojom::SerialDeviceInfoPtr> old_devices = GetDevicesOld();
224
225   base::UmaHistogramSparse("Hardware.Serial.NewMinusOldDeviceListSize",
226                            Clamp(devices.size() - old_devices.size(), -10, 10));
227
228   // Add devices found from both the new and old methods of enumeration. If a
229   // device is found using both the new and the old enumeration method, then we
230   // take the device from the new enumeration method because it's able to
231   // collect more information. We do this by inserting the new devices first,
232   // because insertions are ignored if the key already exists.
233   std::unordered_set<std::string> devices_seen;
234   for (const auto& device : devices) {
235     bool inserted = devices_seen.insert(device->path).second;
236     DCHECK(inserted);
237   }
238   for (auto& device : old_devices) {
239     if (devices_seen.insert(device->path).second)
240       devices.push_back(std::move(device));
241   }
242   return devices;
243 }
244
245 }  // namespace device