mirror of
https://github.com/swift-project/pilotclient.git
synced 2026-03-23 23:45:35 +08:00
499 lines
19 KiB
C++
499 lines
19 KiB
C++
/* Copyright (C) 2014
|
||
* swift project Community / Contributors
|
||
*
|
||
* This file is part of swift Project. It is subject to the license terms in the LICENSE file found in the top-level
|
||
* directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated,
|
||
* or distributed except according to the terms contained in the LICENSE file.
|
||
*/
|
||
|
||
#include "blackmisc/dbusserver.h"
|
||
#include "blackmisc/logmessage.h"
|
||
#include "blackmisc/network/networkutils.h"
|
||
#include "blackmisc/processctrl.h"
|
||
#include "blackmisc/statusmessage.h"
|
||
|
||
#include <QDBusServer>
|
||
#include <QMetaClassInfo>
|
||
#include <QMetaObject>
|
||
#include <QStringList>
|
||
#include <QThread>
|
||
#include <QtGlobal>
|
||
|
||
using namespace BlackMisc::Network;
|
||
|
||
namespace BlackMisc
|
||
{
|
||
CDBusServer::CDBusServer(const QString &service, const QString &address, QObject *parent) : QObject(parent)
|
||
{
|
||
// Application Options:
|
||
// -h, –host=HOSTNAME Hostname or IP of the remote host
|
||
// -p, –port-ssh=PORT-SSH SSH port on the remote host
|
||
// -u, –username=USERNAME SSH username on the remote host
|
||
// -w, –password=PASSWORD SSH password on the remote host
|
||
// -m, –method=DBUS_TRANSPORT_METHOD The D-Bus transport method to use (TCP, UNIX, abstract-UNIX)
|
||
// -b, –bind=HOSTNAME The bind-address to listen for D-Bus client connections on
|
||
// -d, –bus-address=BUS_ADDRESS The DBus session bus address of the remote D-Bus daemon
|
||
// -t, –port-tcp=PORT-TCP The TCP port to listen for DBus client connections on
|
||
// -v, –verbose=VERBOSE Set verbosity level (3, 2, 1, 0, -1)=(packet,protocol,functions,important,none)
|
||
|
||
static const QString desc("Mode: %1 Address: '%2' Service: '%3'");
|
||
m_serverMode = CDBusServer::modeOfAddress(address);
|
||
this->setObjectName(desc.arg(CDBusServer::modeToString(m_serverMode), address, service.isEmpty() ? "-" : service));
|
||
switch (m_serverMode)
|
||
{
|
||
case SERVERMODE_SESSIONBUS:
|
||
{
|
||
QDBusConnection connection = QDBusConnection::connectToBus(QDBusConnection::SessionBus, coreServiceName());
|
||
connection.unregisterService(service); // allow reconnecting by removing still registered service
|
||
if (! connection.isConnected() || ! connection.registerService(service))
|
||
{
|
||
// registration fails can either mean something wrong with DBus or service already exists
|
||
CLogMessage(this).warning(u"DBus registration: %1") << connection.lastError().message();
|
||
CLogMessage(this).warning(u"Cannot register DBus service, check server running: dbus-daemon.exe --session --address=tcp:host=192.168.0.133,port=45000");
|
||
}
|
||
}
|
||
break;
|
||
case SERVERMODE_SYSTEMBUS:
|
||
{
|
||
QDBusConnection connection = QDBusConnection::connectToBus(QDBusConnection::SystemBus, coreServiceName());
|
||
connection.unregisterService(service); // allow reconnecting by removing still registered service
|
||
if (! connection.isConnected() || ! connection.registerService(service))
|
||
{
|
||
// registration fails can either mean something wrong with DBus or service already exists
|
||
CLogMessage(this).warning(u"DBus registration: %1") << connection.lastError().message();
|
||
CLogMessage(this).warning(u"Cannot register DBus service, check server running: dbus-daemon.exe --system --address=tcp:host=192.168.0.133,port=45000");
|
||
}
|
||
}
|
||
break;
|
||
case SERVERMODE_P2P:
|
||
default:
|
||
{
|
||
QString dbusAddress = isQtDBusAddress(address) ? address : "tcp:host=127.0.0.1,port=45000";
|
||
dbusAddress = dbusAddress.toLower().trimmed().replace(' ', "");
|
||
if (! dbusAddress.contains("bind=")) { dbusAddress = dbusAddress.append(",bind=*"); } // bind to all network interfaces
|
||
|
||
m_busServer.reset(new QDBusServer(dbusAddress, this));
|
||
m_busServer->setObjectName("QDBusServer: " + this->objectName());
|
||
m_busServer->setAnonymousAuthenticationAllowed(true);
|
||
|
||
// Note: P2P has no service name
|
||
if (m_busServer->isConnected())
|
||
{
|
||
CLogMessage(this).info(u"DBus P2P Server listening on address: '%1'") << m_busServer->address();
|
||
}
|
||
else
|
||
{
|
||
CLogMessage(this).warning(u"DBus P2P connection failed: %1") << lastQDBusServerError().message();
|
||
}
|
||
connect(m_busServer.data(), &QDBusServer::newConnection, this, &CDBusServer::registerObjectsWithP2PConnection);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
CDBusServer::~CDBusServer()
|
||
{
|
||
this->removeAllObjects();
|
||
QDBusConnection::disconnectFromBus(coreServiceName());
|
||
}
|
||
|
||
const QString &CDBusServer::coreServiceName()
|
||
{
|
||
static const QString sn = SWIFT_SERVICENAME;
|
||
return sn;
|
||
}
|
||
|
||
const QString &CDBusServer::coreServiceName(const QDBusConnection &connection)
|
||
{
|
||
static const QString empty;
|
||
return CDBusServer::isP2PConnection(connection) ? empty : CDBusServer::coreServiceName();
|
||
}
|
||
|
||
bool CDBusServer::isP2PAddress(const QString &address)
|
||
{
|
||
return modeOfAddress(address) == SERVERMODE_P2P;
|
||
}
|
||
|
||
bool CDBusServer::isP2PConnection(const QDBusConnection &connection)
|
||
{
|
||
if (CDBusServer::isQtDefaultConnection(connection)) { return false; }
|
||
return connection.name().contains(p2pConnectionName());
|
||
}
|
||
|
||
bool CDBusServer::dBusAddressToHostAndPort(const QString &address, QString &host, int &port)
|
||
{
|
||
const QString canonicalAddress = address.trimmed().toLower().replace(' ', "");
|
||
if (canonicalAddress.contains("host=") || canonicalAddress.contains("port="))
|
||
{
|
||
// "tcp:host=foo.com,port=123"
|
||
const QStringList parts(canonicalAddress.split(','));
|
||
for (const QString &part : parts)
|
||
{
|
||
// "host=" or "tcp:host="
|
||
if (part.contains("host=", Qt::CaseInsensitive))
|
||
{
|
||
const QString h = part.mid(part.lastIndexOf("=") + 1).trimmed();
|
||
host = h;
|
||
}
|
||
else if (part.contains("port=", Qt::CaseInsensitive))
|
||
{
|
||
const QString p = part.mid(part.lastIndexOf("=") + 1).trimmed();
|
||
bool ok;
|
||
port = p.toInt(&ok);
|
||
if (! ok) { port = -1; }
|
||
}
|
||
}
|
||
if (port < 0) { port = 45000; }
|
||
if (host.isEmpty()) { host = "127.0.0.1"; }
|
||
return true;
|
||
}
|
||
else
|
||
{
|
||
host = "";
|
||
port = -1;
|
||
return false;
|
||
}
|
||
}
|
||
|
||
bool CDBusServer::dBusAddressToHostAndPort(const QString &dbusAddress, QString &o_host, QString &o_port)
|
||
{
|
||
int port;
|
||
const bool s = dBusAddressToHostAndPort(dbusAddress, o_host, port);
|
||
o_port = QString::number(port);
|
||
return s;
|
||
}
|
||
|
||
bool CDBusServer::isQtDefaultConnection(const QDBusConnection &connection)
|
||
{
|
||
return connection.name() == QDBusConnection::sessionBus().name() ||
|
||
connection.name() == QDBusConnection::systemBus().name();
|
||
}
|
||
|
||
bool CDBusServer::isQtDBusAddress(const QString &address)
|
||
{
|
||
return address.startsWith("tcp:") || address.startsWith("unix:");
|
||
}
|
||
|
||
bool CDBusServer::isSessionOrSystemAddress(const QString &address)
|
||
{
|
||
return address == sessionBusAddress() || address == systemBusAddress();
|
||
}
|
||
|
||
QString CDBusServer::getDBusInterfaceFromClassInfo(QObject *object)
|
||
{
|
||
if (! object) { return {}; }
|
||
const QMetaObject *mo = object->metaObject();
|
||
for (int i = 0; i < mo->classInfoCount(); i++)
|
||
{
|
||
const QMetaClassInfo ci = mo->classInfo(i);
|
||
const QString name = QString(ci.name()).toLower();
|
||
if (name == "d-bus interface") { return QString(ci.value()); }
|
||
}
|
||
return {};
|
||
}
|
||
|
||
QDBusConnection::RegisterOptions CDBusServer::registerOptions()
|
||
{
|
||
return QDBusConnection::ExportAdaptors | QDBusConnection::ExportAllSignals | QDBusConnection::ExportAllSlots;
|
||
// return QDBusConnection::ExportAllContents;
|
||
}
|
||
|
||
bool CDBusServer::registerObjectsWithP2PConnection(QDBusConnection connection)
|
||
{
|
||
Q_ASSERT(! m_objects.isEmpty());
|
||
m_connections.insert(connection.name(), connection);
|
||
CLogMessage(this).info(u"New Connection from: '%1'") << connection.name();
|
||
bool success = true;
|
||
for (auto i = m_objects.cbegin(); i != m_objects.cend(); ++i)
|
||
{
|
||
const QString key(i.key());
|
||
const bool ok = connection.registerObject(key, i.value(), registerOptions());
|
||
if (ok)
|
||
{
|
||
CLogMessage(this).info(u"Adding '%1' to the new connection '%2'") << key << this->getDBusInterfaceFromClassInfo(i.value());
|
||
}
|
||
else
|
||
{
|
||
CLogMessage(this).info(u"Adding '%1' failed, connection '%2', error '%3'") << key << this->getDBusInterfaceFromClassInfo(i.value()) << connection.lastError().message();
|
||
success = false;
|
||
}
|
||
}
|
||
return success;
|
||
}
|
||
|
||
void CDBusServer::addObject(const QString &path, QObject *object)
|
||
{
|
||
if (! object) { return; }
|
||
m_objects.insert(path, object); // will be registered when P2P connection is established
|
||
|
||
switch (m_serverMode)
|
||
{
|
||
case SERVERMODE_SESSIONBUS:
|
||
{
|
||
QDBusConnection connection = QDBusConnection::connectToBus(QDBusConnection::SessionBus, coreServiceName());
|
||
if (connection.registerObject(path, object, registerOptions()))
|
||
{
|
||
CLogMessage(this).info(u"Adding '%1' '%2' to session DBus") << path << getDBusInterfaceFromClassInfo(object);
|
||
}
|
||
else
|
||
{
|
||
CLogMessage(this).error(u"Error, no success with session bus registration");
|
||
}
|
||
}
|
||
break;
|
||
case SERVERMODE_SYSTEMBUS:
|
||
{
|
||
QDBusConnection connection = QDBusConnection::connectToBus(QDBusConnection::SystemBus, coreServiceName());
|
||
if (connection.registerObject(path, object, registerOptions()))
|
||
{
|
||
CLogMessage(this).info(u"Adding '%1' '%2' to system DBus") << path << getDBusInterfaceFromClassInfo(object);
|
||
}
|
||
else
|
||
{
|
||
CLogMessage(this).error(u"Error, no success with system bus registration");
|
||
}
|
||
}
|
||
break;
|
||
case SERVERMODE_P2P:
|
||
{
|
||
for (QDBusConnection connection : as_const(m_connections))
|
||
{
|
||
if (connection.registerObject(path, object, registerOptions()))
|
||
{
|
||
CLogMessage(this).info(u"Adding '%1' '%2' to P2P DBus '%3'") << path << getDBusInterfaceFromClassInfo(object) << connection.name();
|
||
}
|
||
else
|
||
{
|
||
CLogMessage(this).error(u"Error, no success with %1 registration") << connection.name();
|
||
}
|
||
}
|
||
}
|
||
break;
|
||
default:
|
||
Q_ASSERT_X(false, "CDBusServer::addObject", "Wrong server mode");
|
||
}
|
||
}
|
||
|
||
QDBusError CDBusServer::lastQDBusServerError() const
|
||
{
|
||
if (! hasQDBusServer()) { return {}; }
|
||
return m_busServer->lastError();
|
||
}
|
||
|
||
const QDBusServer *CDBusServer::qDBusServer() const
|
||
{
|
||
return m_busServer.data();
|
||
}
|
||
|
||
bool CDBusServer::hasQDBusServer() const
|
||
{
|
||
return !m_busServer.isNull();
|
||
}
|
||
|
||
void CDBusServer::removeAllObjects()
|
||
{
|
||
if (m_objects.isEmpty()) { return; }
|
||
for (const QString &path : makeKeysRange(as_const(m_objects)))
|
||
{
|
||
switch (m_serverMode)
|
||
{
|
||
case SERVERMODE_SESSIONBUS:
|
||
QDBusConnection::sessionBus().unregisterObject(path);
|
||
break;
|
||
case SERVERMODE_SYSTEMBUS:
|
||
QDBusConnection::systemBus().unregisterObject(path);
|
||
break;
|
||
case SERVERMODE_P2P:
|
||
{
|
||
for (QDBusConnection connection : as_const(m_connections))
|
||
{
|
||
connection.unregisterObject(path);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
m_objects.clear();
|
||
}
|
||
|
||
const QDBusConnection &CDBusServer::defaultConnection()
|
||
{
|
||
static const QDBusConnection defaultConnection("default");
|
||
return defaultConnection;
|
||
}
|
||
|
||
const QString &CDBusServer::sessionBusAddress()
|
||
{
|
||
static const QString session("session");
|
||
return session;
|
||
}
|
||
|
||
const QString &CDBusServer::systemBusAddress()
|
||
{
|
||
static const QString system("system");
|
||
return system;
|
||
}
|
||
|
||
QDBusConnection CDBusServer::connectToDBus(const QString &dBusAddress, const QString &name)
|
||
{
|
||
if (dBusAddress == sessionBusAddress())
|
||
{
|
||
if (name.isEmpty()) return QDBusConnection::sessionBus();
|
||
return QDBusConnection::connectToBus(QDBusConnection::SessionBus, name);
|
||
}
|
||
else if (dBusAddress == systemBusAddress())
|
||
{
|
||
if (name.isEmpty()) return QDBusConnection::systemBus();
|
||
return QDBusConnection::connectToBus(QDBusConnection::SystemBus, name);
|
||
}
|
||
else if (isP2PAddress(dBusAddress))
|
||
{
|
||
return QDBusConnection::connectToPeer(dBusAddress,
|
||
name.isEmpty() ? CDBusServer::p2pConnectionName() : name);
|
||
}
|
||
return QDBusConnection("invalid");
|
||
}
|
||
|
||
void CDBusServer::disconnectFromDBus(const QDBusConnection &connection, const QString &dBusAddress)
|
||
{
|
||
if (CDBusServer::isQtDefaultConnection(connection)) return; // do not touch the default connections
|
||
if (CDBusServer::isP2PAddress(dBusAddress))
|
||
{
|
||
QDBusConnection::disconnectFromPeer(connection.name());
|
||
}
|
||
else
|
||
{
|
||
QDBusConnection::disconnectFromBus(connection.name());
|
||
}
|
||
}
|
||
|
||
QString CDBusServer::p2pAddress(const QString &host, const QString &port)
|
||
{
|
||
QString h = host.trimmed().toLower().remove(' ');
|
||
if (h.isEmpty()) { h = "127.0.0.1"; }
|
||
|
||
// check port
|
||
bool ok = false;
|
||
QString p = port.toLower().trimmed();
|
||
if (!p.isEmpty())
|
||
{
|
||
// cppcheck-suppress ignoredReturnValue
|
||
p.toShort(&ok);
|
||
if (!ok)
|
||
{
|
||
p = ""; // was not a number
|
||
}
|
||
}
|
||
|
||
// can handle host and port separately or combined, e.g. "myhost:1234"
|
||
if (port.isEmpty())
|
||
{
|
||
if (h.startsWith("tcp:") && h.contains("host=") && h.contains("port="))
|
||
{
|
||
// looks we already got a full string
|
||
return h;
|
||
}
|
||
|
||
// 192.168.5.3:9300 style
|
||
if (h.contains(":"))
|
||
{
|
||
const QStringList parts = h.split(":");
|
||
h = parts.at(0).trimmed();
|
||
p = parts.at(1).trimmed();
|
||
}
|
||
else
|
||
{
|
||
p = "45000";
|
||
}
|
||
}
|
||
|
||
// todo: Replace assert with input validation
|
||
Q_ASSERT_X(CNetworkUtils::isValidIPv4Address(p), "CDBusServer::p2pAddress", "Wrong IP in String");
|
||
return QStringLiteral("tcp:host=%1,port=%2").arg(h, p);
|
||
}
|
||
|
||
const QString &CDBusServer::p2pConnectionName()
|
||
{
|
||
static const QString n("p2pConnection");
|
||
return n;
|
||
}
|
||
|
||
QString CDBusServer::normalizeAddress(const QString &address)
|
||
{
|
||
const QString lc(address.toLower().trimmed());
|
||
|
||
if (lc.isEmpty()) { return sessionBusAddress(); }
|
||
if (lc == sessionBusAddress() || lc == systemBusAddress()) { return lc; }
|
||
|
||
// some aliases
|
||
if (lc.startsWith("sys")) { return systemBusAddress(); }
|
||
if (lc.startsWith("ses")) { return sessionBusAddress(); }
|
||
|
||
// Qt / P2P
|
||
if (isQtDBusAddress(address)) { return address; }
|
||
return p2pAddress(address);
|
||
}
|
||
|
||
CDBusServer::ServerMode CDBusServer::modeOfAddress(QString address)
|
||
{
|
||
address = address.toLower();
|
||
if (address == systemBusAddress()) { return SERVERMODE_SYSTEMBUS; }
|
||
else if (address == sessionBusAddress()) { return SERVERMODE_SESSIONBUS; }
|
||
else { return SERVERMODE_P2P; }
|
||
}
|
||
|
||
const QString &CDBusServer::modeToString(CDBusServer::ServerMode mode)
|
||
{
|
||
static const QString p2p = "P2P";
|
||
static const QString session = "session";
|
||
static const QString system = "system";
|
||
|
||
switch (mode)
|
||
{
|
||
case SERVERMODE_P2P: return p2p;
|
||
case SERVERMODE_SYSTEMBUS: return system;
|
||
case SERVERMODE_SESSIONBUS:
|
||
default: break;
|
||
}
|
||
return session;
|
||
}
|
||
|
||
bool CDBusServer::isDBusAvailable(const QString &address, int port, int timeoutMs)
|
||
{
|
||
QString unused;
|
||
return CNetworkUtils::canConnect(address, port, unused, timeoutMs);
|
||
}
|
||
|
||
bool CDBusServer::isDBusAvailable(const QString &address, int port, QString &message, int timeoutMs)
|
||
{
|
||
return CNetworkUtils::canConnect(address, port, message, timeoutMs);
|
||
}
|
||
|
||
bool CDBusServer::isDBusAvailable(const QString &dBusAddress, QString &message, int timeoutMs)
|
||
{
|
||
if (dBusAddress.isEmpty()) { message = "No address."; return false; }
|
||
if (isP2PAddress(dBusAddress))
|
||
{
|
||
QString host;
|
||
int port = -1;
|
||
return CDBusServer::dBusAddressToHostAndPort(dBusAddress, host, port) ?
|
||
CDBusServer::isDBusAvailable(host, port, message, timeoutMs) :
|
||
false;
|
||
}
|
||
else
|
||
{
|
||
QDBusConnection connection = CDBusServer::connectToDBus(dBusAddress);
|
||
const bool isConnected = connection.isConnected();
|
||
message = connection.lastError().message();
|
||
CDBusServer::disconnectFromDBus(connection, dBusAddress);
|
||
return isConnected;
|
||
}
|
||
}
|
||
|
||
bool CDBusServer::isDBusAvailable(const QString &dbusAddress, int timeoutMs)
|
||
{
|
||
QString unused;
|
||
return CDBusServer::isDBusAvailable(dbusAddress, unused, timeoutMs);
|
||
}
|
||
} // namespace
|