cygprofile: increase timeouts to allow showing web contents
[chromium-blink-merge.git] / device / devices_app / usb / device_impl_unittest.cc
blob433cbff3efbb80dbccc77c24e4a23de5ad24a265
1 // Copyright 2015 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.
5 #include <map>
6 #include <queue>
7 #include <set>
8 #include <vector>
10 #include "base/bind.h"
11 #include "base/macros.h"
12 #include "base/message_loop/message_loop.h"
13 #include "base/run_loop.h"
14 #include "base/stl_util.h"
15 #include "device/devices_app/usb/device_impl.h"
16 #include "device/usb/mock_usb_device.h"
17 #include "device/usb/mock_usb_device_handle.h"
18 #include "testing/gtest/include/gtest/gtest.h"
19 #include "third_party/mojo/src/mojo/public/cpp/bindings/interface_request.h"
21 using ::testing::Invoke;
22 using ::testing::_;
24 namespace device {
25 namespace usb {
27 namespace {
29 class ConfigBuilder {
30 public:
31 ConfigBuilder(uint8_t value) { config_.configuration_value = value; }
33 ConfigBuilder& AddInterface(uint8_t interface_number,
34 uint8_t alternate_setting,
35 uint8_t class_code,
36 uint8_t subclass_code,
37 uint8_t protocol_code) {
38 UsbInterfaceDescriptor interface;
39 interface.interface_number = interface_number;
40 interface.alternate_setting = alternate_setting;
41 interface.interface_class = class_code;
42 interface.interface_subclass = subclass_code;
43 interface.interface_protocol = protocol_code;
44 config_.interfaces.push_back(interface);
45 return *this;
48 const UsbConfigDescriptor& config() const { return config_; }
50 private:
51 UsbConfigDescriptor config_;
54 void ExpectDeviceInfoAndThen(const std::string& guid,
55 uint16_t vendor_id,
56 uint16_t product_id,
57 const std::string& manufacturer_name,
58 const std::string& product_name,
59 const std::string& serial_number,
60 const base::Closure& continuation,
61 DeviceInfoPtr device_info) {
62 EXPECT_EQ(guid, device_info->guid);
63 EXPECT_EQ(vendor_id, device_info->vendor_id);
64 EXPECT_EQ(product_id, device_info->product_id);
65 EXPECT_EQ(manufacturer_name, device_info->manufacturer_name);
66 EXPECT_EQ(product_name, device_info->product_name);
67 EXPECT_EQ(serial_number, device_info->serial_number);
68 continuation.Run();
71 void ExpectResultAndThen(bool expected_result,
72 const base::Closure& continuation,
73 bool actual_result) {
74 EXPECT_EQ(expected_result, actual_result);
75 continuation.Run();
78 void ExpectTransferInAndThen(TransferStatus expected_status,
79 const std::vector<uint8_t>& expected_bytes,
80 const base::Closure& continuation,
81 TransferStatus actual_status,
82 mojo::Array<uint8_t> actual_bytes) {
83 EXPECT_EQ(expected_status, actual_status);
84 ASSERT_EQ(expected_bytes.size(), actual_bytes.size());
85 for (size_t i = 0; i < actual_bytes.size(); ++i) {
86 EXPECT_EQ(expected_bytes[i], actual_bytes[i])
87 << "Contents differ at index: " << i;
89 continuation.Run();
92 void ExpectPacketsAndThen(
93 TransferStatus expected_status,
94 const std::vector<std::vector<uint8_t>>& expected_packets,
95 const base::Closure& continuation,
96 TransferStatus actual_status,
97 mojo::Array<mojo::Array<uint8_t>> actual_packets) {
98 EXPECT_EQ(expected_status, actual_status);
99 ASSERT_EQ(expected_packets.size(), actual_packets.size());
100 for (size_t i = 0; i < expected_packets.size(); ++i) {
101 EXPECT_EQ(expected_packets[i].size(), actual_packets[i].size())
102 << "Packet sizes differ at index: " << i;
103 for (size_t j = 0; j < expected_packets[i].size(); ++j) {
104 EXPECT_EQ(expected_packets[i][j], actual_packets[i][j])
105 << "Contents of packet " << i << " differ at index " << j;
108 continuation.Run();
111 void ExpectTransferStatusAndThen(TransferStatus expected_status,
112 const base::Closure& continuation,
113 TransferStatus actual_status) {
114 EXPECT_EQ(expected_status, actual_status);
115 continuation.Run();
118 class USBDeviceImplTest : public testing::Test {
119 public:
120 USBDeviceImplTest()
121 : message_loop_(new base::MessageLoop),
122 is_device_open_(false),
123 allow_reset_(false),
124 current_config_(0) {}
126 ~USBDeviceImplTest() override {}
128 protected:
129 MockUsbDevice& mock_device() { return *mock_device_.get(); }
130 bool is_device_open() const { return is_device_open_; }
131 MockUsbDeviceHandle& mock_handle() { return *mock_handle_.get(); }
133 void set_allow_reset(bool allow_reset) { allow_reset_ = allow_reset; }
135 // Creates a mock device and binds a Device proxy to a Device service impl
136 // wrapping the mock device.
137 DevicePtr GetMockDeviceProxy(uint16_t vendor_id,
138 uint16_t product_id,
139 const std::string& manufacturer,
140 const std::string& product,
141 const std::string& serial) {
142 is_device_open_ = true;
144 mock_device_ =
145 new MockUsbDevice(vendor_id, product_id, manufacturer, product, serial);
146 mock_handle_ = new MockUsbDeviceHandle(mock_device_.get());
148 DevicePtr proxy;
149 new DeviceImpl(mock_handle_, mojo::GetProxy(&proxy));
151 // Set up mock handle calls to respond based on mock device configs
152 // established by the test.
153 ON_CALL(mock_handle(), Close())
154 .WillByDefault(Invoke(this, &USBDeviceImplTest::CloseMockHandle));
155 ON_CALL(mock_handle(), SetConfiguration(_, _))
156 .WillByDefault(Invoke(this, &USBDeviceImplTest::SetConfiguration));
157 ON_CALL(mock_handle(), ClaimInterface(_, _))
158 .WillByDefault(Invoke(this, &USBDeviceImplTest::ClaimInterface));
159 ON_CALL(mock_handle(), ReleaseInterface(_))
160 .WillByDefault(Invoke(this, &USBDeviceImplTest::ReleaseInterface));
161 ON_CALL(mock_handle(), SetInterfaceAlternateSetting(_, _, _))
162 .WillByDefault(
163 Invoke(this, &USBDeviceImplTest::SetInterfaceAlternateSetting));
164 ON_CALL(mock_handle(), ResetDevice(_))
165 .WillByDefault(Invoke(this, &USBDeviceImplTest::ResetDevice));
166 ON_CALL(mock_handle(), ControlTransfer(_, _, _, _, _, _, _, _, _, _))
167 .WillByDefault(Invoke(this, &USBDeviceImplTest::ControlTransfer));
168 ON_CALL(mock_handle(), GenericTransfer(_, _, _, _, _, _))
169 .WillByDefault(Invoke(this, &USBDeviceImplTest::GenericTransfer));
170 ON_CALL(mock_handle(), IsochronousTransfer(_, _, _, _, _, _, _, _))
171 .WillByDefault(Invoke(this, &USBDeviceImplTest::IsochronousTransfer));
173 return proxy.Pass();
176 DevicePtr GetMockDeviceProxy() {
177 return GetMockDeviceProxy(0x1234, 0x5678, "ACME", "Frobinator", "ABCDEF");
180 void AddMockConfig(const ConfigBuilder& builder) {
181 const UsbConfigDescriptor& config = builder.config();
182 DCHECK(!ContainsKey(mock_configs_, config.configuration_value));
183 mock_configs_[config.configuration_value] = config;
186 void AddMockInboundData(const std::vector<uint8_t>& data) {
187 mock_inbound_data_.push(data);
190 void AddMockOutboundData(const std::vector<uint8_t>& data) {
191 mock_outbound_data_.push(data);
194 private:
195 void CloseMockHandle() {
196 EXPECT_TRUE(is_device_open_);
197 is_device_open_ = false;
200 void SetConfiguration(uint8_t value,
201 const UsbDeviceHandle::ResultCallback& callback) {
202 if (mock_configs_.find(value) != mock_configs_.end()) {
203 current_config_ = value;
204 callback.Run(true);
205 } else {
206 callback.Run(false);
210 void ClaimInterface(uint8_t interface_number,
211 const UsbDeviceHandle::ResultCallback& callback) {
212 for (const auto& config : mock_configs_) {
213 for (const auto& interface : config.second.interfaces) {
214 if (interface.interface_number == interface_number) {
215 claimed_interfaces_.insert(interface_number);
216 callback.Run(true);
217 return;
221 callback.Run(false);
224 bool ReleaseInterface(uint8_t interface_number) {
225 if (ContainsKey(claimed_interfaces_, interface_number)) {
226 claimed_interfaces_.erase(interface_number);
227 return true;
229 return false;
232 void SetInterfaceAlternateSetting(
233 uint8_t interface_number,
234 uint8_t alternate_setting,
235 const UsbDeviceHandle::ResultCallback& callback) {
236 for (const auto& config : mock_configs_) {
237 for (const auto& interface : config.second.interfaces) {
238 if (interface.interface_number == interface_number &&
239 interface.alternate_setting == alternate_setting) {
240 callback.Run(true);
241 return;
245 callback.Run(false);
248 void ResetDevice(const UsbDeviceHandle::ResultCallback& callback) {
249 callback.Run(allow_reset_);
252 void InboundTransfer(const UsbDeviceHandle::TransferCallback& callback) {
253 ASSERT_GE(mock_inbound_data_.size(), 1u);
254 const std::vector<uint8_t>& bytes = mock_inbound_data_.front();
255 size_t length = bytes.size();
256 scoped_refptr<net::IOBuffer> buffer = new net::IOBuffer(length);
257 std::copy(bytes.begin(), bytes.end(), buffer->data());
258 mock_inbound_data_.pop();
259 callback.Run(USB_TRANSFER_COMPLETED, buffer, length);
262 void OutboundTransfer(scoped_refptr<net::IOBuffer> buffer,
263 size_t length,
264 const UsbDeviceHandle::TransferCallback& callback) {
265 ASSERT_GE(mock_outbound_data_.size(), 1u);
266 const std::vector<uint8_t>& bytes = mock_outbound_data_.front();
267 ASSERT_EQ(bytes.size(), length);
268 for (size_t i = 0; i < length; ++i) {
269 EXPECT_EQ(bytes[i], buffer->data()[i])
270 << "Contents differ at index: " << i;
272 mock_outbound_data_.pop();
273 callback.Run(USB_TRANSFER_COMPLETED, buffer, length);
276 void ControlTransfer(UsbEndpointDirection direction,
277 UsbDeviceHandle::TransferRequestType request_type,
278 UsbDeviceHandle::TransferRecipient recipient,
279 uint8_t request,
280 uint16_t value,
281 uint16_t index,
282 scoped_refptr<net::IOBuffer> buffer,
283 size_t length,
284 unsigned int timeout,
285 const UsbDeviceHandle::TransferCallback& callback) {
286 if (direction == USB_DIRECTION_INBOUND)
287 InboundTransfer(callback);
288 else
289 OutboundTransfer(buffer, length, callback);
292 void GenericTransfer(UsbEndpointDirection direction,
293 uint8_t endpoint,
294 scoped_refptr<net::IOBuffer> buffer,
295 size_t length,
296 unsigned int timeout,
297 const UsbDeviceHandle::TransferCallback& callback) {
298 if (direction == USB_DIRECTION_INBOUND)
299 InboundTransfer(callback);
300 else
301 OutboundTransfer(buffer, length, callback);
304 void IsochronousTransfer(UsbEndpointDirection direction,
305 uint8_t endpoint,
306 scoped_refptr<net::IOBuffer> buffer,
307 size_t length,
308 unsigned int packets,
309 unsigned int packet_length,
310 unsigned int timeout,
311 const UsbDeviceHandle::TransferCallback& callback) {
312 if (direction == USB_DIRECTION_INBOUND)
313 InboundTransfer(callback);
314 else
315 OutboundTransfer(buffer, length, callback);
318 scoped_ptr<base::MessageLoop> message_loop_;
319 scoped_refptr<MockUsbDevice> mock_device_;
320 scoped_refptr<MockUsbDeviceHandle> mock_handle_;
321 bool is_device_open_;
322 bool allow_reset_;
324 std::map<uint8_t, UsbConfigDescriptor> mock_configs_;
325 uint8_t current_config_;
327 std::queue<std::vector<uint8_t>> mock_inbound_data_;
328 std::queue<std::vector<uint8_t>> mock_outbound_data_;
330 std::set<uint8_t> claimed_interfaces_;
332 DISALLOW_COPY_AND_ASSIGN(USBDeviceImplTest);
335 } // namespace
337 TEST_F(USBDeviceImplTest, Close) {
338 DevicePtr device = GetMockDeviceProxy();
340 EXPECT_TRUE(is_device_open());
342 EXPECT_CALL(mock_handle(), Close());
344 base::RunLoop loop;
345 device->Close(loop.QuitClosure());
346 loop.Run();
348 EXPECT_FALSE(is_device_open());
351 // Test that the information returned via the Device::GetDeviceInfo matches that
352 // of the underlying device.
353 TEST_F(USBDeviceImplTest, GetDeviceInfo) {
354 DevicePtr device =
355 GetMockDeviceProxy(0x1234, 0x5678, "ACME", "Frobinator", "ABCDEF");
357 base::RunLoop loop;
358 device->GetDeviceInfo(base::Bind(&ExpectDeviceInfoAndThen,
359 mock_device().guid(), 0x1234, 0x5678, "ACME",
360 "Frobinator", "ABCDEF", loop.QuitClosure()));
361 loop.Run();
363 EXPECT_CALL(mock_handle(), Close());
366 TEST_F(USBDeviceImplTest, SetInvalidConfiguration) {
367 DevicePtr device = GetMockDeviceProxy();
369 EXPECT_CALL(mock_handle(), SetConfiguration(42, _));
371 // SetConfiguration should fail because 42 is not a valid mock configuration.
372 base::RunLoop loop;
373 device->SetConfiguration(
374 42, base::Bind(&ExpectResultAndThen, false, loop.QuitClosure()));
375 loop.Run();
377 EXPECT_CALL(mock_handle(), Close());
380 TEST_F(USBDeviceImplTest, SetValidConfiguration) {
381 DevicePtr device = GetMockDeviceProxy();
383 EXPECT_CALL(mock_handle(), SetConfiguration(42, _));
385 AddMockConfig(ConfigBuilder(42));
387 // SetConfiguration should succeed because 42 is a valid mock configuration.
388 base::RunLoop loop;
389 device->SetConfiguration(
390 42, base::Bind(&ExpectResultAndThen, true, loop.QuitClosure()));
391 loop.Run();
393 EXPECT_CALL(mock_handle(), Close());
396 // Verify that the result of Reset() reflects the underlying UsbDeviceHandle's
397 // ResetDevice() result.
398 TEST_F(USBDeviceImplTest, Reset) {
399 DevicePtr device = GetMockDeviceProxy();
401 EXPECT_CALL(mock_handle(), ResetDevice(_));
403 set_allow_reset(true);
406 base::RunLoop loop;
407 device->Reset(base::Bind(&ExpectResultAndThen, true, loop.QuitClosure()));
408 loop.Run();
411 EXPECT_CALL(mock_handle(), ResetDevice(_));
413 set_allow_reset(false);
416 base::RunLoop loop;
417 device->Reset(base::Bind(&ExpectResultAndThen, false, loop.QuitClosure()));
418 loop.Run();
421 EXPECT_CALL(mock_handle(), Close());
424 TEST_F(USBDeviceImplTest, ClaimAndReleaseInterface) {
425 DevicePtr device = GetMockDeviceProxy();
427 // Now add a mock interface #1.
428 AddMockConfig(ConfigBuilder(0).AddInterface(1, 0, 1, 2, 3));
430 EXPECT_CALL(mock_handle(), ClaimInterface(2, _));
433 // Try to claim an invalid interface and expect failure.
434 base::RunLoop loop;
435 device->ClaimInterface(
436 2, base::Bind(&ExpectResultAndThen, false, loop.QuitClosure()));
437 loop.Run();
440 EXPECT_CALL(mock_handle(), ClaimInterface(1, _));
443 base::RunLoop loop;
444 device->ClaimInterface(
445 1, base::Bind(&ExpectResultAndThen, true, loop.QuitClosure()));
446 loop.Run();
449 EXPECT_CALL(mock_handle(), ReleaseInterface(2));
452 // Releasing a non-existent interface should fail.
453 base::RunLoop loop;
454 device->ReleaseInterface(
455 2, base::Bind(&ExpectResultAndThen, false, loop.QuitClosure()));
456 loop.Run();
459 EXPECT_CALL(mock_handle(), ReleaseInterface(1));
462 // Now this should release the claimed interface and close the handle.
463 base::RunLoop loop;
464 device->ReleaseInterface(
465 1, base::Bind(&ExpectResultAndThen, true, loop.QuitClosure()));
466 loop.Run();
469 EXPECT_CALL(mock_handle(), Close());
472 TEST_F(USBDeviceImplTest, SetInterfaceAlternateSetting) {
473 DevicePtr device = GetMockDeviceProxy();
475 AddMockConfig(ConfigBuilder(0)
476 .AddInterface(1, 0, 1, 2, 3)
477 .AddInterface(1, 42, 1, 2, 3)
478 .AddInterface(2, 0, 1, 2, 3));
480 EXPECT_CALL(mock_handle(), SetInterfaceAlternateSetting(1, 42, _));
483 base::RunLoop loop;
484 device->SetInterfaceAlternateSetting(
485 1, 42, base::Bind(&ExpectResultAndThen, true, loop.QuitClosure()));
486 loop.Run();
489 EXPECT_CALL(mock_handle(), SetInterfaceAlternateSetting(1, 100, _));
492 base::RunLoop loop;
493 device->SetInterfaceAlternateSetting(
494 1, 100, base::Bind(&ExpectResultAndThen, false, loop.QuitClosure()));
495 loop.Run();
498 EXPECT_CALL(mock_handle(), Close());
501 TEST_F(USBDeviceImplTest, ControlTransfer) {
502 DevicePtr device = GetMockDeviceProxy();
504 std::vector<uint8_t> fake_data;
505 fake_data.push_back(41);
506 fake_data.push_back(42);
507 fake_data.push_back(43);
509 AddMockInboundData(fake_data);
511 EXPECT_CALL(mock_handle(),
512 ControlTransfer(USB_DIRECTION_INBOUND, UsbDeviceHandle::STANDARD,
513 UsbDeviceHandle::DEVICE, 5, 6, 7, _, _, 0, _));
516 auto params = ControlTransferParams::New();
517 params->type = CONTROL_TRANSFER_TYPE_STANDARD;
518 params->recipient = CONTROL_TRANSFER_RECIPIENT_DEVICE;
519 params->request = 5;
520 params->value = 6;
521 params->index = 7;
522 base::RunLoop loop;
523 device->ControlTransferIn(
524 params.Pass(), static_cast<uint32_t>(fake_data.size()), 0,
525 base::Bind(&ExpectTransferInAndThen, TRANSFER_STATUS_COMPLETED,
526 fake_data, loop.QuitClosure()));
527 loop.Run();
530 AddMockConfig(ConfigBuilder(0).AddInterface(7, 0, 1, 2, 3));
531 AddMockOutboundData(fake_data);
533 EXPECT_CALL(mock_handle(),
534 ControlTransfer(USB_DIRECTION_OUTBOUND, UsbDeviceHandle::STANDARD,
535 UsbDeviceHandle::INTERFACE, 5, 6, 7, _, _, 0, _));
538 auto params = ControlTransferParams::New();
539 params->type = CONTROL_TRANSFER_TYPE_STANDARD;
540 params->recipient = CONTROL_TRANSFER_RECIPIENT_INTERFACE;
541 params->request = 5;
542 params->value = 6;
543 params->index = 7;
544 base::RunLoop loop;
545 device->ControlTransferOut(
546 params.Pass(), mojo::Array<uint8_t>::From(fake_data), 0,
547 base::Bind(&ExpectTransferStatusAndThen, TRANSFER_STATUS_COMPLETED,
548 loop.QuitClosure()));
549 loop.Run();
552 EXPECT_CALL(mock_handle(), Close());
555 TEST_F(USBDeviceImplTest, GenericTransfer) {
556 DevicePtr device = GetMockDeviceProxy();
558 std::string message1 = "say hello please";
559 std::vector<uint8_t> fake_outbound_data(message1.size());
560 std::copy(message1.begin(), message1.end(), fake_outbound_data.begin());
562 std::string message2 = "hello world!";
563 std::vector<uint8_t> fake_inbound_data(message2.size());
564 std::copy(message2.begin(), message2.end(), fake_inbound_data.begin());
566 AddMockConfig(ConfigBuilder(0).AddInterface(7, 0, 1, 2, 3));
567 AddMockOutboundData(fake_outbound_data);
568 AddMockInboundData(fake_inbound_data);
570 EXPECT_CALL(mock_handle(), GenericTransfer(USB_DIRECTION_OUTBOUND, 0, _,
571 fake_outbound_data.size(), 0, _));
574 base::RunLoop loop;
575 device->GenericTransferOut(
576 0, mojo::Array<uint8_t>::From(fake_outbound_data), 0,
577 base::Bind(&ExpectTransferStatusAndThen, TRANSFER_STATUS_COMPLETED,
578 loop.QuitClosure()));
579 loop.Run();
582 EXPECT_CALL(mock_handle(), GenericTransfer(USB_DIRECTION_INBOUND, 0, _,
583 fake_inbound_data.size(), 0, _));
586 base::RunLoop loop;
587 device->GenericTransferIn(
588 0, static_cast<uint32_t>(fake_inbound_data.size()), 0,
589 base::Bind(&ExpectTransferInAndThen, TRANSFER_STATUS_COMPLETED,
590 fake_inbound_data, loop.QuitClosure()));
591 loop.Run();
594 EXPECT_CALL(mock_handle(), Close());
597 TEST_F(USBDeviceImplTest, IsochronousTransfer) {
598 DevicePtr device = GetMockDeviceProxy();
600 std::string outbound_packet_data = "aaaaaaaabbbbbbbbccccccccdddddddd";
601 std::vector<uint8_t> fake_outbound_packets(outbound_packet_data.size());
602 std::copy(outbound_packet_data.begin(), outbound_packet_data.end(),
603 fake_outbound_packets.begin());
605 std::string inbound_packet_data = "ddddddddccccccccbbbbbbbbaaaaaaaa";
606 std::vector<uint8_t> fake_inbound_packets(inbound_packet_data.size());
607 std::copy(inbound_packet_data.begin(), inbound_packet_data.end(),
608 fake_inbound_packets.begin());
610 AddMockConfig(ConfigBuilder(0).AddInterface(7, 0, 1, 2, 3));
611 AddMockOutboundData(fake_outbound_packets);
612 AddMockInboundData(fake_inbound_packets);
614 EXPECT_CALL(mock_handle(),
615 IsochronousTransfer(USB_DIRECTION_OUTBOUND, 0, _,
616 fake_outbound_packets.size(), 4, 8, 0, _));
619 base::RunLoop loop;
620 mojo::Array<mojo::Array<uint8_t>> packets =
621 mojo::Array<mojo::Array<uint8_t>>::New(4);
622 for (size_t i = 0; i < 4; ++i) {
623 std::vector<uint8_t> bytes(8);
624 std::copy(outbound_packet_data.begin() + i * 8,
625 outbound_packet_data.begin() + i * 8 + 8, bytes.begin());
626 packets[i].Swap(&bytes);
628 device->IsochronousTransferOut(
629 0, packets.Pass(), 0,
630 base::Bind(&ExpectTransferStatusAndThen, TRANSFER_STATUS_COMPLETED,
631 loop.QuitClosure()));
632 loop.Run();
635 EXPECT_CALL(mock_handle(),
636 IsochronousTransfer(USB_DIRECTION_INBOUND, 0, _,
637 fake_inbound_packets.size(), 4, 8, 0, _));
640 base::RunLoop loop;
641 std::vector<std::vector<uint8_t>> packets(4);
642 for (size_t i = 0; i < 4; ++i) {
643 packets[i].resize(8);
644 std::copy(inbound_packet_data.begin() + i * 8,
645 inbound_packet_data.begin() + i * 8 + 8, packets[i].begin());
647 device->IsochronousTransferIn(
648 0, 4, 8, 0, base::Bind(&ExpectPacketsAndThen, TRANSFER_STATUS_COMPLETED,
649 packets, loop.QuitClosure()));
650 loop.Run();
653 EXPECT_CALL(mock_handle(), Close());
656 } // namespace usb
657 } // namespace device