summaryrefslogtreecommitdiffstats
path: root/src/wifi/qwificontroller.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/wifi/qwificontroller.cpp')
-rw-r--r--src/wifi/qwificontroller.cpp461
1 files changed, 461 insertions, 0 deletions
diff --git a/src/wifi/qwificontroller.cpp b/src/wifi/qwificontroller.cpp
new file mode 100644
index 0000000..ea49764
--- /dev/null
+++ b/src/wifi/qwificontroller.cpp
@@ -0,0 +1,461 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use the contact form at
+** http://www.qt.io
+**
+** This file is part of Qt Enterprise Embedded.
+**
+** Licensees holding valid Qt Enterprise licenses may use this file in
+** accordance with the Qt Enterprise License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** the contact form at http://www.qt.io
+**
+****************************************************************************/
+#include "qwificontroller_p.h"
+#include "qwifimanager_p.h"
+#include "qwifidevice.h"
+
+#include <QtCore/QCoreApplication>
+#include <QtCore/QProcess>
+#include <QtCore/QByteArray>
+#include <QtCore/QFile>
+
+#ifdef Q_OS_ANDROID_NO_SDK
+#include <QtNetwork/QLocalSocket>
+#include <cutils/sockets.h>
+#include <unistd.h>
+#endif
+
+#ifdef Q_OS_ANDROID_NO_SDK
+/*
+ * Work API differences between Android versions
+ */
+int q_wifi_start_supplicant()
+{
+#if Q_ANDROID_VERSION_MAJOR == 4 && Q_ANDROID_VERSION_MINOR < 1
+ return wifi_start_supplicant();
+#else
+ return wifi_start_supplicant(0);
+#endif
+}
+
+int q_wifi_stop_supplicant()
+{
+#if Q_ANDROID_VERSION_MAJOR == 4 && Q_ANDROID_VERSION_MINOR < 1
+ return wifi_stop_supplicant();
+#else
+ return wifi_stop_supplicant(0);
+#endif
+}
+
+int q_wifi_connect_to_supplicant(const char *ifname)
+{
+#if Q_ANDROID_VERSION_MAJOR == 4 && (Q_ANDROID_VERSION_MINOR < 4 && Q_ANDROID_VERSION_MINOR >= 1)
+ return wifi_connect_to_supplicant(ifname);
+#else
+ Q_UNUSED(ifname);
+ return wifi_connect_to_supplicant();
+#endif
+}
+
+void q_wifi_close_supplicant_connection(const char *ifname)
+{
+#if Q_ANDROID_VERSION_MAJOR == 4 && (Q_ANDROID_VERSION_MINOR < 4 && Q_ANDROID_VERSION_MINOR >= 1)
+ wifi_close_supplicant_connection(ifname);
+#else
+ Q_UNUSED(ifname);
+ wifi_close_supplicant_connection();
+#endif
+}
+
+int q_wifi_wait_for_event(const char *ifname, char *buf, size_t len)
+{
+#if Q_ANDROID_VERSION_MAJOR == 4 && (Q_ANDROID_VERSION_MINOR < 4 && Q_ANDROID_VERSION_MINOR >= 1)
+ return wifi_wait_for_event(ifname, buf, len);
+#else
+ Q_UNUSED(ifname);
+ return wifi_wait_for_event(buf, len);
+#endif
+}
+
+int q_wifi_command(const char *ifname, const char *command, char *reply, size_t *reply_len)
+{
+#if Q_ANDROID_VERSION_MAJOR == 4 && (Q_ANDROID_VERSION_MINOR < 4 && Q_ANDROID_VERSION_MINOR >= 1)
+ return wifi_command(ifname, command, reply, reply_len);
+#else
+ Q_UNUSED(ifname);
+ return wifi_command(command, reply, reply_len);
+#endif
+}
+
+/*
+ * This function is borrowed from /system/core/libnetutils/dhcp_utils.c
+ *
+ * Wait for a system property to be assigned a specified value.
+ * If desired_value is NULL, then just wait for the property to
+ * be created with any value. maxwait is the maximum amount of
+ * time in seconds to wait before giving up.
+ */
+const int NAP_TIME = 200; // wait for 200ms at a time when polling for property values
+int wait_for_property(const char *name, const char *desired_value, int maxwait)
+{
+ char value[PROPERTY_VALUE_MAX] = {'\0'};
+ int maxnaps = (maxwait * 1000) / NAP_TIME;
+
+ if (maxnaps < 1) {
+ maxnaps = 1;
+ }
+
+ while (maxnaps-- > 0) {
+ usleep(NAP_TIME * 1000);
+ if (property_get(name, value, NULL)) {
+ if (desired_value == NULL ||
+ strcmp(value, desired_value) == 0) {
+ return 0;
+ }
+ }
+ }
+ return -1; /* failure */
+}
+
+#endif // Q_OS_ANDROID_NO_SDK
+
+QT_BEGIN_NAMESPACE
+
+Q_LOGGING_CATEGORY(B2QT_WIFI, "qt.b2qt.wifi")
+
+class QWifiManagerEvent : public QEvent
+{
+public:
+ QWifiManagerEvent(QEvent::Type type, const QByteArray &data = QByteArray())
+ : QEvent(type)
+ , m_data(data)
+ {
+ }
+
+ QByteArray data() const { return m_data; }
+
+private:
+ QByteArray m_data;
+};
+
+class QWifiEventThread : public QThread
+{
+public:
+ QWifiEventThread(QWifiController *controller)
+ : m_controller(controller)
+ {
+ m_interface = QWifiDevice::wifiInterfaceName();
+ }
+
+ void run() {
+ qCDebug(B2QT_WIFI) << "running wifi event thread";
+ QWifiManagerEvent *event = 0;
+ char buffer[2048];
+ forever {
+ int size = q_wifi_wait_for_event(m_interface, buffer, sizeof(buffer) - 1);
+ if (size > 0) {
+ buffer[size] = 0;
+ event = 0;
+ qCDebug(B2QT_WIFI) << "[event]: " << buffer;
+ if (m_controller->isWifiThreadExitRequested()) {
+ qCDebug(B2QT_WIFI) << "exit wifi event thread";
+ return;
+ }
+ if (strstr(buffer, "SCAN-RESULTS")) {
+ event = new QWifiManagerEvent(WIFI_SCAN_RESULTS);
+ } else if (strstr(buffer, "CTRL-EVENT-CONNECTED")) {
+ event = new QWifiManagerEvent(WIFI_CONNECTED);
+ } else if (strstr(buffer, "CTRL-EVENT-DISCONNECTED")) {
+ event = new QWifiManagerEvent(WIFI_DISCONNECTED);
+ } else if (strstr(buffer, "Trying to associate")) {
+ event = new QWifiManagerEvent(WIFI_AUTHENTICATING);
+ } else if (strstr(buffer, "Handshake failed")) {
+ event = new QWifiManagerEvent(WIFI_HANDSHAKE_FAILED);
+ }
+ if (event)
+ QCoreApplication::postEvent(m_controller->wifiManager(), event);
+ }
+ }
+ }
+
+private:
+ QWifiController *m_controller;
+ QByteArray m_interface;
+};
+
+
+QWifiController::QWifiController(QWifiManager *manager, QWifiManagerPrivate *managerPrivate) :
+ m_manager(manager),
+ m_managerPrivate(managerPrivate),
+ m_exitEventThread(false),
+#ifdef Q_OS_ANDROID_NO_SDK
+ m_qcSocket(0),
+#endif
+ m_eventThread(0)
+{
+ m_interface = QWifiDevice::wifiInterfaceName();
+ m_eventThread = new QWifiEventThread(this);
+
+ qRegisterMetaType<QWifiManager::BackendState>("QWifiManager::BackendState");
+#ifdef Q_OS_ANDROID_NO_SDK
+ qRegisterMetaType<QAbstractSocket::SocketState>("QAbstractSocket::SocketState");
+#endif
+}
+
+QWifiController::~QWifiController()
+{
+ exitWifiEventThread();
+ delete m_eventThread;
+#ifdef Q_OS_ANDROID_NO_SDK
+ delete m_qcSocket;
+#endif
+}
+
+void QWifiController::allocateOnThisThread()
+{
+#ifdef Q_OS_ANDROID_NO_SDK
+ m_qcSocket = new QLocalSocket;
+ int qcFd = socket_local_client("qconnectivity", ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM);
+ if (qcFd != -1) {
+ m_qcSocket->setSocketDescriptor(qcFd);
+ } else {
+ qCWarning(B2QT_WIFI) << "failed to get file descriptor of a qconnectivity socket!";
+ }
+#endif
+}
+
+void QWifiController::run()
+{
+ qCDebug(B2QT_WIFI) << "running wifi backend controller thread";
+ allocateOnThisThread();
+ Method method;
+ forever {
+ m_methodsMutex.lock();
+ if (m_methods.isEmpty())
+ methodCallRequested.wait(&m_methodsMutex);
+ method = m_methods.takeFirst();
+ m_methodsMutex.unlock();
+ switch (method) {
+ case InitializeBackend:
+ initializeBackend();
+ break;
+ case TerminateBackend:
+ terminateBackend();
+ break;
+ case AcquireIPAddress:
+ acquireIPAddress();
+ break;
+ case StopDhcp:
+ stopDhcp();
+ break;
+ case ExitEventLoop:
+ qCDebug(B2QT_WIFI) << "exit wifi backend controller thread";
+ return;
+ }
+ }
+}
+
+void QWifiController::call(Method method)
+{
+ QMutexLocker locker(&m_methodsMutex);
+ m_methods.append(method);
+ methodCallRequested.wakeOne();
+}
+
+void QWifiController::initializeBackend()
+{
+ qCDebug(B2QT_WIFI) << "initializing wifi backend";
+ emit backendStateChanged(QWifiManager::Initializing);
+
+#ifdef Q_OS_ANDROID_NO_SDK
+ qCDebug(B2QT_WIFI) << "initialize driver";
+ if (!(is_wifi_driver_loaded() || wifi_load_driver() == 0)) {
+ qCWarning(B2QT_WIFI) << "failed to load a driver";
+ return;
+ }
+#else
+ qCDebug(B2QT_WIFI) << "run ifconfig (up)";
+ QProcess ifconfig;
+ ifconfig.start(QStringLiteral("ifconfig"), QStringList() << QLatin1String(m_interface) << QStringLiteral("up"));
+ ifconfig.waitForFinished();
+ if (ifconfig.exitStatus() != QProcess::NormalExit && ifconfig.exitCode() != 0) {
+ qCWarning(B2QT_WIFI) << "failed to bring up wifi interface!";
+ return;
+ }
+#endif
+ resetSupplicantSocket();
+ startWifiEventThread();
+ qCDebug(B2QT_WIFI) << "wifi backend started successfully";
+ emit backendStateChanged(QWifiManager::Running);
+}
+
+void QWifiController::resetSupplicantSocket() const
+{
+ qCDebug(B2QT_WIFI) << "reset supplicant socket";
+ // close down the previous connection to supplicant if
+ // one exists before re-connecting.
+ if (q_wifi_stop_supplicant() < 0)
+ qCWarning(B2QT_WIFI) << "failed to stop supplicant!";
+ q_wifi_close_supplicant_connection(m_interface);
+ qCDebug(B2QT_WIFI) << "start supplicant";
+ if (q_wifi_start_supplicant() != 0) {
+ qCWarning(B2QT_WIFI) << "failed to start a supplicant!";
+ return;
+ }
+#ifdef Q_OS_ANDROID_NO_SDK
+ if (wait_for_property("init.svc.wpa_supplicant", "running", 5) < 0) {
+ qCWarning(B2QT_WIFI) << "timed out waiting for supplicant to start!";
+ return;
+ }
+#endif
+ qCDebug(B2QT_WIFI) << "connect to supplicant";
+ if (q_wifi_connect_to_supplicant(m_interface) != 0) {
+ qCWarning(B2QT_WIFI) << "failed to connect to a supplicant!";
+ return;
+ }
+}
+
+void QWifiController::terminateBackend()
+{
+ qCDebug(B2QT_WIFI) << "terminating wifi backend";
+ emit backendStateChanged(QWifiManager::Terminating);
+ exitWifiEventThread();
+ if (q_wifi_stop_supplicant() < 0)
+ qCWarning(B2QT_WIFI) << "failed to stop supplicant!";
+ q_wifi_close_supplicant_connection(m_interface);
+#ifndef Q_OS_ANDROID_NO_SDK
+ qCDebug(B2QT_WIFI) << "run ifconfig (down)";
+ QProcess ifconfig;
+ ifconfig.start(QStringLiteral("ifconfig"), QStringList() << QLatin1String(m_interface) << QStringLiteral("down"));
+ ifconfig.waitForFinished();
+ if (ifconfig.exitStatus() != QProcess::NormalExit && ifconfig.exitCode() != 0) {
+ qCWarning(B2QT_WIFI) << "failed to bring down wifi interface!";
+ return;
+ }
+#endif
+ stopDhcp();
+ qCDebug(B2QT_WIFI) << "wifi backend stopped successfully";
+ emit backendStateChanged(QWifiManager::NotRunning);
+}
+
+void QWifiController::startWifiEventThread()
+{
+ m_exitEventThread = false;
+ m_eventThread->start();
+}
+
+void QWifiController::exitWifiEventThread()
+{
+ if (m_eventThread->isRunning()) {
+ m_exitEventThread = true;
+ m_managerPrivate->call(QStringLiteral("SCAN"));
+ m_eventThread->wait();
+ }
+}
+
+#ifdef Q_OS_ANDROID_NO_SDK
+bool QWifiController::getQConnectivityReply()
+{
+ bool arrived = false;
+ if (m_qcSocket->canReadLine()) {
+ arrived = true;
+ QByteArray received = m_qcSocket->readLine(m_qcSocket->bytesAvailable());
+ if (received != "success" && received != "failed") {
+ qCWarning(B2QT_WIFI) << "unknown message: " << received;
+ received = "failed";
+ }
+ emit dhcpRequestFinished(QLatin1String(received));
+ }
+ return arrived;
+}
+#else
+void QWifiController::killDhcpProcess(const QString &path) const
+{
+ QFile pidFile(path);
+ if (pidFile.exists()) {
+ pidFile.open(QIODevice::ReadOnly);
+ QByteArray pid = pidFile.readAll();
+ QProcess kill;
+ kill.start(QStringLiteral("kill"), QStringList() << QLatin1String(pid.trimmed()));
+ kill.waitForFinished();
+ if (kill.exitStatus() != QProcess::NormalExit && kill.exitCode() != 0)
+ qCWarning(B2QT_WIFI) << "killing dhcp process failed!";
+ }
+ pidFile.close();
+}
+#endif
+
+void QWifiController::acquireIPAddress()
+{
+ qCDebug(B2QT_WIFI, "acquireIPAddress");
+#ifdef Q_OS_ANDROID_NO_SDK
+ QByteArray request = m_interface + " connect\n";
+ m_qcSocket->abort();
+ m_qcSocket->connectToServer(QStringLiteral(ANDROID_SOCKET_DIR "/qconnectivity"));
+ bool timeout = false;
+ if (m_qcSocket->waitForConnected()) {
+ m_qcSocket->write(request, request.length());
+ m_qcSocket->flush();
+ do {
+ if (m_qcSocket->waitForReadyRead()) {
+ if (getQConnectivityReply())
+ break;
+ } else {
+ timeout = true;
+ qCWarning(B2QT_WIFI) << "waiting a message from qconnectivity timed out!";
+ break;
+ }
+ } while (true);
+ } else {
+ timeout = true;
+ qCWarning(B2QT_WIFI) << "connecting to qconnectivity server socket timed out!";
+ }
+ if (timeout)
+ emit dhcpRequestFinished(QStringLiteral("failed"));
+#else
+ QString filePath = QLatin1String("/var/run/udhcpc." + m_interface + ".pid");
+ killDhcpProcess(filePath);
+ QStringList args;
+ args << QStringLiteral("-R") << QStringLiteral("-n") << QStringLiteral("-p")
+ << filePath << QStringLiteral("-i") << QLatin1String(m_interface);
+
+ QProcess udhcpc;
+ udhcpc.start(QStringLiteral("udhcpc"), args);
+ udhcpc.waitForFinished();
+ if (udhcpc.exitStatus() != QProcess::NormalExit && udhcpc.exitCode() != 0)
+ qCWarning(B2QT_WIFI) << "udhcpc process failed!";
+
+ QString status = QLatin1String("success");
+ if (udhcpc.readAll().contains("No lease"))
+ status = QLatin1String("failed");
+
+ emit dhcpRequestFinished(status);
+#endif
+}
+
+void QWifiController::stopDhcp() const
+{
+ qCDebug(B2QT_WIFI, "stopDhcp");
+#ifdef Q_OS_ANDROID_NO_SDK
+ QByteArray request = m_interface + " disconnect\n";
+ m_qcSocket->abort();
+ m_qcSocket->connectToServer(QStringLiteral(ANDROID_SOCKET_DIR "/qconnectivity"));
+ if (m_qcSocket->waitForConnected()) {
+ m_qcSocket->write(request, request.length());
+ m_qcSocket->flush();
+ } else {
+ qCWarning(B2QT_WIFI) << "connecting to qconnectivity server socket timed out!";
+ }
+#else
+ QString filePath = QLatin1String("/var/run/udhcpc." + m_interface + ".pid");
+ killDhcpProcess(filePath);
+#endif
+}
+
+QT_END_NAMESPACE