// Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "relayserver.h" #include "iostool.h" #include "mobiledevicelib.h" #ifdef Q_OS_UNIX #include #include #include #endif // avoid utils dependency #define QTC_CHECK(cond) if (cond) {} else { qWarning() << "assert failed " << #cond << " " \ << __FILE__ << ":" << __LINE__; } do {} while (0) namespace Ios { Relayer::Relayer(RelayServer *parent, QTcpSocket *clientSocket) : QObject(parent), m_serverFileDescriptor(0), m_clientSocket(0), m_serverNotifier(0) { setClientSocket(clientSocket); } Relayer::~Relayer() { closeConnection(); } void Relayer::setClientSocket(QTcpSocket *clientSocket) { QTC_CHECK(!m_clientSocket); m_clientSocket = clientSocket; if (m_clientSocket) { const auto errorOccurred = &QAbstractSocket::errorOccurred; connect(m_clientSocket, errorOccurred, this, &Relayer::handleClientHasError); connect(m_clientSocket, &QAbstractSocket::disconnected, this, [this](){server()->removeRelayConnection(this);}); } } bool Relayer::startRelay(int serverFileDescriptor) { QTC_CHECK(!m_serverFileDescriptor); m_serverFileDescriptor = serverFileDescriptor; if (!m_clientSocket || m_serverFileDescriptor <= 0) return false; fcntl(serverFileDescriptor,F_SETFL, fcntl(serverFileDescriptor, F_GETFL) | O_NONBLOCK); connect(m_clientSocket, &QIODevice::readyRead, this, &Relayer::handleClientHasData); m_serverNotifier = new QSocketNotifier(m_serverFileDescriptor, QSocketNotifier::Read, this); connect(m_serverNotifier, &QSocketNotifier::activated, this, &Relayer::handleSocketHasData); // no way to check if an error did happen? if (m_clientSocket->bytesAvailable() > 0) handleClientHasData(); return true; } void Relayer::handleSocketHasData(int socket) { m_serverNotifier->setEnabled(false); char buf[255]; while (true) { qptrdiff rRead = readData(socket, &buf, sizeof(buf)-1); if (rRead == -1) { if (errno == EINTR) continue; if (errno == EAGAIN) { m_serverNotifier->setEnabled(true); return; } iosTool()->writeError(qt_error_string(errno)); close(socket); iosTool()->stopRelayServers(-1); return; } if (rRead == 0) { iosTool()->stopRelayServers(0); return; } if (iosTool()->echoRelays()) { iosTool()->writeMaybeBin(QString::fromLatin1("%1 serverReplies:") .arg((quintptr)(void *)this), buf, rRead); } qint64 pos = 0; while (true) { qint64 writtenNow = m_clientSocket->write(buf + int(pos), rRead); if (writtenNow == -1) { iosTool()->writeError(m_clientSocket->errorString()); iosTool()->stopRelayServers(-1); return; } if (writtenNow < rRead) { pos += writtenNow; rRead -= qptrdiff(writtenNow); } else { break; } } m_clientSocket->flush(); } } void Relayer::handleClientHasData() { char buf[255]; while (true) { qint64 toRead = m_clientSocket->bytesAvailable(); if (qint64(sizeof(buf)-1) < toRead) toRead = sizeof(buf)-1; qint64 rRead = m_clientSocket->read(buf, toRead); if (rRead == -1) { iosTool()->writeError(m_clientSocket->errorString()); iosTool()->stopRelayServers(); return; } if (rRead == 0) { if (!m_clientSocket->isOpen()) iosTool()->stopRelayServers(); return; } int pos = 0; int irep = 0; if (iosTool()->echoRelays()) { iosTool()->writeMaybeBin(QString::fromLatin1("%1 clientAsks:") .arg((quintptr)(void *)this), buf, rRead); } while (true) { qptrdiff written = writeData(m_serverFileDescriptor, buf + pos, rRead); if (written == -1) { if (errno == EINTR) continue; if (errno == EAGAIN) { if (++irep > 10) { sleep(1); irep = 0; } continue; } iosTool()->writeError(qt_error_string(errno)); iosTool()->stopRelayServers(); return; } if (written == 0) { iosTool()->stopRelayServers(); return; } if (written < rRead) { pos += written; rRead -= written; } else { break; } } } } void Relayer::handleClientHasError(QAbstractSocket::SocketError error) { iosTool()->writeError(tr("iOS debugging connection failed with error %1").arg(error)); server()->removeRelayConnection(this); } int Relayer::readData(int socketFd, void *buf, size_t size) { return read(socketFd, buf, size); } int Relayer::writeData(int socketFd, const void *data, size_t size) { return write(socketFd, data, size); } void Relayer::closeConnection() { if (m_serverFileDescriptor > 0) { ::close(m_serverFileDescriptor); m_serverFileDescriptor = -1; if (m_serverNotifier) { delete m_serverNotifier; m_serverNotifier = nullptr; } } if (m_clientSocket->isOpen()) m_clientSocket->close(); delete m_clientSocket; m_clientSocket = nullptr; } IosTool *Relayer::iosTool() const { return (server() ? server()->iosTool() : 0); } RelayServer *Relayer::server() const { return qobject_cast(parent()); } RemotePortRelayer::RemotePortRelayer(QmlRelayServer *parent, QTcpSocket *clientSocket) : Relayer(parent, clientSocket) { m_remoteConnectTimer.setSingleShot(true); m_remoteConnectTimer.setInterval(reconnectMsecDelay); connect(&m_remoteConnectTimer, &QTimer::timeout, this, &RemotePortRelayer::tryRemoteConnect); } void RemotePortRelayer::tryRemoteConnect() { iosTool()->writeMsg(QLatin1String("tryRemoteConnect")); if (m_serverFileDescriptor > 0) return; ServiceSocket ss; QmlRelayServer *grServer = qobject_cast(server()); if (!grServer) return; if (grServer->m_deviceSession->connectToPort(grServer->m_remotePort, &ss)) { if (ss > 0) { iosTool()->writeMsg( QString::fromLatin1("tryRemoteConnect *succeeded* on remote port %1") .arg(grServer->m_remotePort)); startRelay(ss); emit didConnect(grServer); return; } } iosTool()->writeMsg(QString::fromLatin1("tryRemoteConnect *failed* on remote port %1") .arg(grServer->m_remotePort)); m_remoteConnectTimer.start(); } RelayServer::RelayServer(IosTool *parent): QObject(parent) { } RelayServer::~RelayServer() { stopServer(); } bool RelayServer::startServer() { QTC_CHECK(!m_ipv4Server.isListening()); QTC_CHECK(!m_ipv6Server.isListening()); connect(&m_ipv4Server, &QTcpServer::newConnection, this, &RelayServer::handleNewRelayConnection); connect(&m_ipv6Server, &QTcpServer::newConnection, this, &RelayServer::handleNewRelayConnection); m_port = 0; if (m_ipv4Server.listen(QHostAddress(QHostAddress::LocalHost), 0)) m_port = m_ipv4Server.serverPort(); if (m_ipv6Server.listen(QHostAddress(QHostAddress::LocalHostIPv6), m_port)) m_port = m_ipv6Server.serverPort(); return m_port > 0; } void RelayServer::stopServer() { qDeleteAll(m_connections); if (m_ipv4Server.isListening()) m_ipv4Server.close(); if (m_ipv6Server.isListening()) m_ipv6Server.close(); } quint16 RelayServer::serverPort() const { return m_port; } IosTool *RelayServer::iosTool() const { return qobject_cast(parent()); } void RelayServer::handleNewRelayConnection() { iosTool()->writeMsg(QLatin1String("handleNewRelayConnection")); newRelayConnection(); } void RelayServer::removeRelayConnection(Relayer *relayer) { m_connections.removeAll(relayer); relayer->deleteLater(); } GdbRelayServer::GdbRelayServer(IosTool *parent, int serverFileDescriptor, ServiceConnRef conn) : RelayServer(parent), m_serverFileDescriptor(serverFileDescriptor), m_serviceConn(conn) { if (m_serverFileDescriptor > 0) fcntl(m_serverFileDescriptor, F_SETFL, fcntl(m_serverFileDescriptor, F_GETFL, 0) | O_NONBLOCK); } void GdbRelayServer::newRelayConnection() { QTcpSocket *clientSocket = m_ipv4Server.hasPendingConnections() ? m_ipv4Server.nextPendingConnection() : m_ipv6Server.nextPendingConnection(); if (m_connections.size() > 0) { delete clientSocket; return; } if (clientSocket) { Relayer *newConnection = new ServiceConnectionRelayer(this, clientSocket, m_serviceConn); m_connections.append(newConnection); newConnection->startRelay(m_serverFileDescriptor); } } QmlRelayServer::QmlRelayServer(IosTool *parent, int remotePort, Ios::DeviceSession *deviceSession) : RelayServer(parent), m_remotePort(remotePort), m_deviceSession(deviceSession) { parent->writeMsg(QLatin1String("created qml server")); } void QmlRelayServer::newRelayConnection() { QTcpSocket *clientSocket = m_ipv4Server.hasPendingConnections() ? m_ipv4Server.nextPendingConnection() : m_ipv6Server.nextPendingConnection(); if (clientSocket) { iosTool()->writeMsg(QString::fromLatin1("setting up relayer for new connection")); RemotePortRelayer *newConnection = new RemotePortRelayer(this, clientSocket); m_connections.append(newConnection); newConnection->tryRemoteConnect(); } } ServiceConnectionRelayer::ServiceConnectionRelayer(RelayServer *parent, QTcpSocket *clientSocket, ServiceConnRef conn) : Relayer(parent, clientSocket), m_serviceConn(conn) { } int ServiceConnectionRelayer::readData(int socketFd, void *buf, size_t size) { Q_UNUSED(socketFd) if (!buf || !m_serviceConn) return 0; MobileDeviceLib &mlib = MobileDeviceLib::instance(); return mlib.serviceConnectionReceive(m_serviceConn, buf, size); } int ServiceConnectionRelayer::writeData(int socketFd, const void *data, size_t size) { Q_UNUSED(socketFd) if (!data || !m_serviceConn) return 0; MobileDeviceLib &mLib = MobileDeviceLib::instance(); return mLib.serviceConnectionSend(m_serviceConn, data, size); } void ServiceConnectionRelayer::closeConnection() { Relayer::closeConnection(); if (m_serviceConn) { MobileDeviceLib &mLib = MobileDeviceLib::instance(); mLib.serviceConnectionInvalidate(m_serviceConn); m_serviceConn = nullptr; } } }