[WebAuthN] Support CTAP HID authenticators on macOS
[WebKit-https.git] / Source / WebKit / UIProcess / WebAuthentication / Cocoa / HidConnection.mm
1 /*
2  * Copyright (C) 2018 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27 #import "HidConnection.h"
28
29 #if ENABLE(WEB_AUTHN) && PLATFORM(MAC)
30
31 #import <WebCore/FidoConstants.h>
32 #import <wtf/BlockPtr.h>
33
34 namespace WebKit {
35 using namespace fido;
36
37 // FIXME(191518)
38 static void reportReceived(void* context, IOReturn status, void*, IOHIDReportType type, uint32_t reportID, uint8_t* report, CFIndex reportLength)
39 {
40     ASSERT(RunLoop::isMain());
41     auto* connection = static_cast<HidConnection*>(context);
42
43     if (status) {
44         LOG_ERROR("Couldn't receive report from the authenticator: %d", status);
45         connection->receiveReport({ });
46         return;
47     }
48     ASSERT(type == kIOHIDReportTypeInput);
49     // FIXME(191519): Determine report id and max report size dynamically.
50     ASSERT(reportID == kHidReportId);
51     ASSERT(reportLength == kHidMaxPacketSize);
52
53     Vector<uint8_t> reportData;
54     reportData.append(report, reportLength);
55     connection->receiveReport(WTFMove(reportData));
56 }
57
58 HidConnection::HidConnection(IOHIDDeviceRef device)
59     : m_device(device)
60 {
61 }
62
63 HidConnection::~HidConnection()
64 {
65     ASSERT(m_terminated);
66 }
67
68 void HidConnection::initialize()
69 {
70     IOHIDDeviceOpen(m_device.get(), kIOHIDOptionsTypeNone);
71     IOHIDDeviceScheduleWithRunLoop(m_device.get(), CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
72     m_inputBuffer.resize(kHidMaxPacketSize);
73     IOHIDDeviceRegisterInputReportCallback(m_device.get(), m_inputBuffer.data(), m_inputBuffer.size(), &reportReceived, this);
74     m_initialized = true;
75 }
76
77 void HidConnection::terminate()
78 {
79     IOHIDDeviceRegisterInputReportCallback(m_device.get(), m_inputBuffer.data(), m_inputBuffer.size(), nullptr, nullptr);
80     IOHIDDeviceUnscheduleFromRunLoop(m_device.get(), CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
81     IOHIDDeviceClose(m_device.get(), kIOHIDOptionsTypeNone);
82     m_terminated = true;
83 }
84
85 void HidConnection::send(Vector<uint8_t>&& data, DataSentCallback&& callback)
86 {
87     ASSERT(m_initialized);
88     auto task = BlockPtr<void()>::fromCallable([device = m_device, data = WTFMove(data), callback = WTFMove(callback)]() mutable {
89         ASSERT(!RunLoop::isMain());
90
91         DataSent sent = DataSent::Yes;
92         auto status = IOHIDDeviceSetReport(device.get(), kIOHIDReportTypeOutput, kHidReportId, data.data(), data.size());
93         if (status) {
94             LOG_ERROR("Couldn't send report to the authenticator: %d", status);
95             sent = DataSent::No;
96         }
97         RunLoop::main().dispatch([callback = WTFMove(callback), sent]() mutable {
98             callback(sent);
99         });
100     });
101     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), task.get());
102 }
103
104 void HidConnection::registerDataReceivedCallback(DataReceivedCallback&& callback)
105 {
106     ASSERT(m_initialized);
107     ASSERT(!m_inputCallback);
108     m_inputCallback = WTFMove(callback);
109     consumeReports();
110     registerDataReceivedCallbackInternal();
111 }
112
113 void HidConnection::unregisterDataReceivedCallback()
114 {
115     m_inputCallback = nullptr;
116 }
117
118 void HidConnection::receiveReport(Vector<uint8_t>&& report)
119 {
120     m_inputReports.append(WTFMove(report));
121     consumeReports();
122 }
123
124 void HidConnection::consumeReports()
125 {
126     while (!m_inputReports.isEmpty() && m_inputCallback)
127         m_inputCallback(m_inputReports.takeFirst());
128 }
129
130 void HidConnection::registerDataReceivedCallbackInternal()
131 {
132 }
133
134 } // namespace WebKit
135
136 #endif // ENABLE(WEB_AUTHN) && PLATFORM(MAC)
137