1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtCore module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qcfsocketnotifier_p.h"
41 #include <QtCore/qcoreapplication.h>
42 #include <QtCore/qsocketnotifier.h>
43 #include <QtCore/qthread.h>
44 
45 QT_BEGIN_NAMESPACE
46 
47 /**************************************************************************
48     Socket Notifiers
49  *************************************************************************/
qt_mac_socket_callback(CFSocketRef s,CFSocketCallBackType callbackType,CFDataRef,const void *,void * info)50 void qt_mac_socket_callback(CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef,
51                             const void *, void *info)
52 {
53 
54     QCFSocketNotifier *cfSocketNotifier = static_cast<QCFSocketNotifier *>(info);
55     int nativeSocket = CFSocketGetNative(s);
56     MacSocketInfo *socketInfo = cfSocketNotifier->macSockets.value(nativeSocket);
57     QEvent notifierEvent(QEvent::SockAct);
58 
59     // There is a race condition that happen where we disable the notifier and
60     // the kernel still has a notification to pass on. We then get this
61     // notification after we've successfully disabled the CFSocket, but our Qt
62     // notifier is now gone. The upshot is we have to check the notifier
63     // every time.
64     if (callbackType == kCFSocketReadCallBack) {
65         if (socketInfo->readNotifier && socketInfo->readEnabled) {
66             socketInfo->readEnabled = false;
67             QCoreApplication::sendEvent(socketInfo->readNotifier, &notifierEvent);
68         }
69     } else if (callbackType == kCFSocketWriteCallBack) {
70         if (socketInfo->writeNotifier && socketInfo->writeEnabled) {
71             socketInfo->writeEnabled = false;
72             QCoreApplication::sendEvent(socketInfo->writeNotifier, &notifierEvent);
73         }
74     }
75 
76     if (cfSocketNotifier->maybeCancelWaitForMoreEvents)
77         cfSocketNotifier->maybeCancelWaitForMoreEvents(cfSocketNotifier->eventDispatcher);
78 }
79 
80 /*
81     Adds a loop source for the given socket to the current run loop.
82 */
qt_mac_add_socket_to_runloop(const CFSocketRef socket)83 CFRunLoopSourceRef qt_mac_add_socket_to_runloop(const CFSocketRef socket)
84 {
85     CFRunLoopSourceRef loopSource = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket, 0);
86     if (!loopSource)
87         return 0;
88 
89     CFRunLoopAddSource(CFRunLoopGetCurrent(), loopSource, kCFRunLoopCommonModes);
90     return loopSource;
91 }
92 
93 /*
94     Removes the loop source for the given socket from the current run loop.
95 */
qt_mac_remove_socket_from_runloop(const CFSocketRef socket,CFRunLoopSourceRef runloop)96 void qt_mac_remove_socket_from_runloop(const CFSocketRef socket, CFRunLoopSourceRef runloop)
97 {
98     Q_ASSERT(runloop);
99     CFRunLoopRemoveSource(CFRunLoopGetCurrent(), runloop, kCFRunLoopCommonModes);
100     CFSocketDisableCallBacks(socket, kCFSocketReadCallBack);
101     CFSocketDisableCallBacks(socket, kCFSocketWriteCallBack);
102 }
103 
QCFSocketNotifier()104 QCFSocketNotifier::QCFSocketNotifier()
105     : eventDispatcher(0)
106     , maybeCancelWaitForMoreEvents(0)
107     , enableNotifiersObserver(0)
108 {
109 
110 }
111 
~QCFSocketNotifier()112 QCFSocketNotifier::~QCFSocketNotifier()
113 {
114 
115 }
116 
setHostEventDispatcher(QAbstractEventDispatcher * hostEventDispacher)117 void QCFSocketNotifier::setHostEventDispatcher(QAbstractEventDispatcher *hostEventDispacher)
118 {
119     eventDispatcher = hostEventDispacher;
120 }
121 
setMaybeCancelWaitForMoreEventsCallback(MaybeCancelWaitForMoreEventsFn callBack)122 void QCFSocketNotifier::setMaybeCancelWaitForMoreEventsCallback(MaybeCancelWaitForMoreEventsFn callBack)
123 {
124     maybeCancelWaitForMoreEvents = callBack;
125 }
126 
registerSocketNotifier(QSocketNotifier * notifier)127 void QCFSocketNotifier::registerSocketNotifier(QSocketNotifier *notifier)
128 {
129     Q_ASSERT(notifier);
130     int nativeSocket = notifier->socket();
131     int type = notifier->type();
132 #ifndef QT_NO_DEBUG
133     if (nativeSocket < 0 || nativeSocket > FD_SETSIZE) {
134         qWarning("QSocketNotifier: Internal error");
135         return;
136     } else if (notifier->thread() != eventDispatcher->thread()
137                || eventDispatcher->thread() != QThread::currentThread()) {
138         qWarning("QSocketNotifier: socket notifiers cannot be enabled from another thread");
139         return;
140     }
141 #endif
142 
143     if (type == QSocketNotifier::Exception) {
144         qWarning("QSocketNotifier::Exception is not supported on iOS");
145         return;
146     }
147 
148     // Check if we have a CFSocket for the native socket, create one if not.
149     MacSocketInfo *socketInfo = macSockets.value(nativeSocket);
150     if (!socketInfo) {
151         socketInfo = new MacSocketInfo();
152 
153         // Create CFSocket, specify that we want both read and write callbacks (the callbacks
154         // are enabled/disabled later on).
155         const int callbackTypes = kCFSocketReadCallBack | kCFSocketWriteCallBack;
156         CFSocketContext context = {0, this, 0, 0, 0};
157         socketInfo->socket = CFSocketCreateWithNative(kCFAllocatorDefault, nativeSocket, callbackTypes, qt_mac_socket_callback, &context);
158         if (CFSocketIsValid(socketInfo->socket) == false) {
159             qWarning("QEventDispatcherMac::registerSocketNotifier: Failed to create CFSocket");
160             return;
161         }
162 
163         CFOptionFlags flags = CFSocketGetSocketFlags(socketInfo->socket);
164         // QSocketNotifier doesn't close the socket upon destruction/invalidation
165         flags &= ~kCFSocketCloseOnInvalidate;
166         // Expicitly disable automatic re-enable, as we do that manually on each runloop pass
167         flags &= ~(kCFSocketAutomaticallyReenableWriteCallBack | kCFSocketAutomaticallyReenableReadCallBack);
168         CFSocketSetSocketFlags(socketInfo->socket, flags);
169 
170         macSockets.insert(nativeSocket, socketInfo);
171     }
172 
173     if (type == QSocketNotifier::Read) {
174         Q_ASSERT(socketInfo->readNotifier == 0);
175         socketInfo->readNotifier = notifier;
176         socketInfo->readEnabled = false;
177     } else if (type == QSocketNotifier::Write) {
178         Q_ASSERT(socketInfo->writeNotifier == 0);
179         socketInfo->writeNotifier = notifier;
180         socketInfo->writeEnabled = false;
181     }
182 
183     if (!enableNotifiersObserver) {
184         // Create a run loop observer which enables the socket notifiers on each
185         // pass of the run loop, before any sources are processed.
186         CFRunLoopObserverContext context = {};
187         context.info = this;
188         enableNotifiersObserver = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopBeforeSources,
189                                                           true, 0, enableSocketNotifiers, &context);
190         Q_ASSERT(enableNotifiersObserver);
191         CFRunLoopAddObserver(CFRunLoopGetCurrent(), enableNotifiersObserver, kCFRunLoopCommonModes);
192     }
193 }
194 
unregisterSocketNotifier(QSocketNotifier * notifier)195 void QCFSocketNotifier::unregisterSocketNotifier(QSocketNotifier *notifier)
196 {
197     Q_ASSERT(notifier);
198     int nativeSocket = notifier->socket();
199     int type = notifier->type();
200 #ifndef QT_NO_DEBUG
201     if (nativeSocket < 0 || nativeSocket > FD_SETSIZE) {
202         qWarning("QSocketNotifier: Internal error");
203         return;
204     } else if (notifier->thread() != eventDispatcher->thread() || eventDispatcher->thread() != QThread::currentThread()) {
205         qWarning("QSocketNotifier: socket notifiers cannot be disabled from another thread");
206         return;
207     }
208 #endif
209 
210     if (type == QSocketNotifier::Exception) {
211         qWarning("QSocketNotifier::Exception is not supported on iOS");
212         return;
213     }
214     MacSocketInfo *socketInfo = macSockets.value(nativeSocket);
215     if (!socketInfo) {
216         qWarning("QEventDispatcherMac::unregisterSocketNotifier: Tried to unregister a not registered notifier");
217         return;
218     }
219 
220     // Decrement read/write counters and disable callbacks if necessary.
221     if (type == QSocketNotifier::Read) {
222         Q_ASSERT(notifier == socketInfo->readNotifier);
223         socketInfo->readNotifier = 0;
224         socketInfo->readEnabled = false;
225         CFSocketDisableCallBacks(socketInfo->socket, kCFSocketReadCallBack);
226     } else if (type == QSocketNotifier::Write) {
227         Q_ASSERT(notifier == socketInfo->writeNotifier);
228         socketInfo->writeNotifier = 0;
229         socketInfo->writeEnabled = false;
230         CFSocketDisableCallBacks(socketInfo->socket, kCFSocketWriteCallBack);
231     }
232 
233     // Remove CFSocket from runloop if this was the last QSocketNotifier.
234     if (socketInfo->readNotifier == 0 && socketInfo->writeNotifier == 0) {
235         unregisterSocketInfo(socketInfo);
236         delete socketInfo;
237         macSockets.remove(nativeSocket);
238     }
239 }
240 
removeSocketNotifiers()241 void QCFSocketNotifier::removeSocketNotifiers()
242 {
243     // Remove CFSockets from the runloop.
244     for (MacSocketInfo *socketInfo : qAsConst(macSockets)) {
245         unregisterSocketInfo(socketInfo);
246         delete socketInfo;
247     }
248 
249     macSockets.clear();
250 
251     destroyRunLoopObserver();
252 }
253 
destroyRunLoopObserver()254 void QCFSocketNotifier::destroyRunLoopObserver()
255 {
256     if (!enableNotifiersObserver)
257         return;
258 
259     CFRunLoopObserverInvalidate(enableNotifiersObserver);
260     CFRelease(enableNotifiersObserver);
261     enableNotifiersObserver = 0;
262 }
263 
unregisterSocketInfo(MacSocketInfo * socketInfo)264 void QCFSocketNotifier::unregisterSocketInfo(MacSocketInfo *socketInfo)
265 {
266     if (socketInfo->runloop) {
267         if (CFSocketIsValid(socketInfo->socket))
268             qt_mac_remove_socket_from_runloop(socketInfo->socket, socketInfo->runloop);
269         CFRunLoopSourceInvalidate(socketInfo->runloop);
270         CFRelease(socketInfo->runloop);
271     }
272     CFSocketInvalidate(socketInfo->socket);
273     CFRelease(socketInfo->socket);
274 }
275 
enableSocketNotifiers(CFRunLoopObserverRef ref,CFRunLoopActivity activity,void * info)276 void QCFSocketNotifier::enableSocketNotifiers(CFRunLoopObserverRef ref, CFRunLoopActivity activity, void *info)
277 {
278     Q_UNUSED(ref);
279     Q_UNUSED(activity);
280 
281     const QCFSocketNotifier *that = static_cast<QCFSocketNotifier *>(info);
282 
283     for (MacSocketInfo *socketInfo : that->macSockets) {
284         if (!CFSocketIsValid(socketInfo->socket))
285             continue;
286 
287         if (!socketInfo->runloop) {
288             // Add CFSocket to runloop.
289             if (!(socketInfo->runloop = qt_mac_add_socket_to_runloop(socketInfo->socket))) {
290                 qWarning("QEventDispatcherMac::registerSocketNotifier: Failed to add CFSocket to runloop");
291                 CFSocketInvalidate(socketInfo->socket);
292                 continue;
293             }
294 
295             // Apple docs say: "If a callback is automatically re-enabled,
296             // it is called every time the condition becomes true ... If a
297             // callback is not automatically re-enabled, then it gets called
298             // exactly once, and is not called again until you manually
299             // re-enable that callback by calling CFSocketEnableCallBacks()".
300             // So, we don't need to enable callbacks on registering.
301             socketInfo->readEnabled = (socketInfo->readNotifier != nullptr);
302             if (!socketInfo->readEnabled)
303                 CFSocketDisableCallBacks(socketInfo->socket, kCFSocketReadCallBack);
304             socketInfo->writeEnabled = (socketInfo->writeNotifier != nullptr);
305             if (!socketInfo->writeEnabled)
306                 CFSocketDisableCallBacks(socketInfo->socket, kCFSocketWriteCallBack);
307             continue;
308         }
309 
310         if (socketInfo->readNotifier && !socketInfo->readEnabled) {
311             socketInfo->readEnabled = true;
312             CFSocketEnableCallBacks(socketInfo->socket, kCFSocketReadCallBack);
313         }
314         if (socketInfo->writeNotifier && !socketInfo->writeEnabled) {
315             socketInfo->writeEnabled = true;
316             CFSocketEnableCallBacks(socketInfo->socket, kCFSocketWriteCallBack);
317         }
318     }
319 }
320 
321 QT_END_NAMESPACE
322 
323