Compare commits

...

6 commits

Author SHA1 Message Date
Albert Vaca Cintora
fc4b8a3297 Use QVariant instead of QDbusVariant
Needs https://bugreports.qt.io/browse/QTBUG-116002
2023-08-11 11:42:43 +02:00
Albert Vaca Cintora
790b08ee24 Add MDNS e2e test 2023-08-10 15:55:02 +02:00
Albert Vaca Cintora
3604d8b6f9 Enable MDNS by default 2023-08-10 15:55:02 +02:00
Albert Vaca Cintora
5a83bb04f7 Implement IP match function 2023-08-10 15:55:02 +02:00
Albert Vaca Cintora
46734e8d07 Better handling of network changes for MDNS 2023-08-10 15:54:54 +02:00
Albert Vaca Cintora
9a49f002b7 Replace KDNSSD with mdns.h
KDNSSD only works with Avahi (so, only on Linux) while mdns.h is a
header-only library [1] that implements mdns from scratch and should
work on all platforms.

[1] https://github.com/mjansson/mdns
2023-08-10 15:54:19 +02:00
22 changed files with 2510 additions and 160 deletions

View file

@ -7,14 +7,11 @@ endif()
set(KDECONNECT_PRIVATE_DBUS_NAME DBusKDEConnectOnly)
configure_file(dbushelper.h.in ${CMAKE_CURRENT_BINARY_DIR}/dbushelper.h)
option(MDNS_ENABLED "Use MDNS for device discovery" ON)
add_subdirectory(backends/lan)
add_subdirectory(backends/loopback)
option(MDNS_ENABLED "Use MDNS for device discovery (under development)" OFF)
if (MDNS_ENABLED)
find_package(KF${QT_MAJOR_VERSION} ${KF_MIN_VERSION} REQUIRED COMPONENTS DNSSD)
endif()
option(BLUETOOTH_ENABLED "Bluetooth support for kdeconnect" OFF)
if(BLUETOOTH_ENABLED)
find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} REQUIRED COMPONENTS Bluetooth)
@ -86,7 +83,6 @@ endif()
if (MDNS_ENABLED)
target_compile_definitions(kdeconnectcore PRIVATE -DKDECONNECT_MDNS)
target_link_libraries(kdeconnectcore PRIVATE KF${QT_MAJOR_VERSION}::DNSSD)
endif()
set_target_properties(kdeconnectcore PROPERTIES

View file

@ -12,6 +12,7 @@ if (MDNS_ENABLED)
set(backends_kdeconnect_SRCS
${backends_kdeconnect_SRCS}
backends/lan/mdnsdiscovery.cpp
backends/lan/mdns_wrapper.cpp
)
endif()

View file

@ -48,14 +48,14 @@ LanLinkProvider::LanLinkProvider(bool testMode, quint16 udpBroadcastPort, quint1
, m_udpBroadcastPort(udpBroadcastPort)
, m_udpListenPort(udpListenPort)
, m_testMode(testMode)
, m_combineBroadcastsTimer(this)
, m_combineNetworkChangeTimer(this)
#ifdef KDECONNECT_MDNS
, m_mdnsDiscovery(this)
#endif
{
m_combineBroadcastsTimer.setInterval(0); // increase this if waiting a single event-loop iteration is not enough
m_combineBroadcastsTimer.setSingleShot(true);
connect(&m_combineBroadcastsTimer, &QTimer::timeout, this, &LanLinkProvider::broadcastToNetwork);
m_combineNetworkChangeTimer.setInterval(0); // increase this if waiting a single event-loop iteration is not enough
m_combineNetworkChangeTimer.setSingleShot(true);
connect(&m_combineNetworkChangeTimer, &QTimer::timeout, this, &LanLinkProvider::combinedOnNetworkChange);
connect(&m_udpSocket, &QIODevice::readyRead, this, &LanLinkProvider::udpBroadcastReceived);
@ -71,8 +71,7 @@ LanLinkProvider::LanLinkProvider(bool testMode, quint16 udpBroadcastPort, quint1
#if QT_VERSION_MAJOR < 6
QNetworkConfigurationManager *networkManager = new QNetworkConfigurationManager(this);
connect(networkManager, &QNetworkConfigurationManager::configurationChanged, this, [this](QNetworkConfiguration config) {
if (m_lastConfig != config && config.state() == QNetworkConfiguration::Active) {
m_lastConfig = config;
if (config.state() == QNetworkConfiguration::Active) {
onNetworkChange();
}
});
@ -121,8 +120,7 @@ void LanLinkProvider::onStart()
broadcastUdpIdentityPacket();
#ifdef KDECONNECT_MDNS
m_mdnsDiscovery.startAnnouncing();
m_mdnsDiscovery.startDiscovering();
m_mdnsDiscovery.onStart();
#endif
qCDebug(KDECONNECT_CORE) << "LanLinkProvider started";
@ -131,8 +129,7 @@ void LanLinkProvider::onStart()
void LanLinkProvider::onStop()
{
#ifdef KDECONNECT_MDNS
m_mdnsDiscovery.stopAnnouncing();
m_mdnsDiscovery.stopDiscovering();
m_mdnsDiscovery.onStop();
#endif
m_udpSocket.close();
m_server->close();
@ -141,15 +138,15 @@ void LanLinkProvider::onStop()
void LanLinkProvider::onNetworkChange()
{
if (m_combineBroadcastsTimer.isActive()) {
qCDebug(KDECONNECT_CORE) << "Preventing duplicate broadcasts";
if (m_combineNetworkChangeTimer.isActive()) {
qCDebug(KDECONNECT_CORE) << "Device discovery triggered too fast, ignoring";
return;
}
m_combineBroadcastsTimer.start();
m_combineNetworkChangeTimer.start();
}
// I'm in a new network, let's be polite and introduce myself
void LanLinkProvider::broadcastToNetwork()
void LanLinkProvider::combinedOnNetworkChange()
{
if (!m_server->isListening()) {
qWarning() << "TCP server not listening, not broadcasting";
@ -160,8 +157,7 @@ void LanLinkProvider::broadcastToNetwork()
broadcastUdpIdentityPacket();
#ifdef KDECONNECT_MDNS
m_mdnsDiscovery.stopDiscovering();
m_mdnsDiscovery.startDiscovering();
m_mdnsDiscovery.onNetworkChange();
#endif
}

View file

@ -71,7 +71,7 @@ private Q_SLOTS:
void newConnection();
void dataReceived();
void sslErrors(const QList<QSslError> &errors);
void broadcastToNetwork();
void combinedOnNetworkChange();
private:
void addLink(QSslSocket *socket, const DeviceInfo &deviceInfo);
@ -95,14 +95,11 @@ private:
QMap<QSslSocket *, PendingConnect> m_receivedIdentityPackets;
QMap<QString, qint64> m_lastConnectionTime;
const bool m_testMode;
QTimer m_combineBroadcastsTimer;
QTimer m_combineNetworkChangeTimer;
#ifdef KDECONNECT_MDNS
MdnsDiscovery m_mdnsDiscovery;
#endif
#if QT_VERSION_MAJOR < 6
QNetworkConfiguration m_lastConfig;
#endif
};
#endif

1618
core/backends/lan/mdns.h Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,641 @@
/**
* SPDX-FileCopyrightText: 2023 Albert Vaca Cintora <albertvaka@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include "mdns_wrapper.h"
#include "core_debug.h"
#include "mdns.h"
#include <errno.h>
#include <QHostInfo>
#include <QNetworkInterface>
#include <QSocketNotifier>
namespace MdnsWrapper
{
const char *recordTypeToStr(int rtype)
{
switch (rtype) {
case MDNS_RECORDTYPE_PTR:
return "PTR";
case MDNS_RECORDTYPE_SRV:
return "SRV";
case MDNS_RECORDTYPE_TXT:
return "TXT";
case MDNS_RECORDTYPE_A:
return "A";
case MDNS_RECORDTYPE_AAAA:
return "AAAA";
case MDNS_RECORDTYPE_ANY:
return "ANY";
default:
return "UNKNOWN";
}
}
const char *entryTypeToStr(int entry)
{
switch (entry) {
case MDNS_ENTRYTYPE_QUESTION:
return "QUESTION";
case MDNS_ENTRYTYPE_ANSWER:
return "ANSWER";
case MDNS_ENTRYTYPE_AUTHORITY:
return "AUTHORITY";
case MDNS_ENTRYTYPE_ADDITIONAL:
return "ADDITIONAL";
default:
return "UNKNOWN";
}
}
static sockaddr_in qHostAddressToSockaddr(QHostAddress hostAddress)
{
Q_ASSERT(hostAddress.protocol() == QAbstractSocket::IPv4Protocol);
sockaddr_in socketAddress;
memset(&socketAddress, 0, sizeof(socketAddress));
socketAddress.sin_family = AF_INET;
socketAddress.sin_addr.s_addr = htonl(hostAddress.toIPv4Address());
return socketAddress;
}
static sockaddr_in6 qHostAddressToSockaddr6(QHostAddress hostAddress)
{
Q_ASSERT(hostAddress.protocol() == QAbstractSocket::IPv6Protocol);
sockaddr_in6 socketAddress;
memset(&socketAddress, 0, sizeof(socketAddress));
socketAddress.sin6_family = AF_INET6;
Q_IPV6ADDR ipv6Address = hostAddress.toIPv6Address();
for (int i = 0; i < 16; ++i) {
socketAddress.sin6_addr.s6_addr[i] = ipv6Address[i];
}
return socketAddress;
}
// Callback that handles responses to a query
static int query_callback(int sock, const struct sockaddr* from, size_t addrlen, mdns_entry_type_t entry_type,
uint16_t query_id, uint16_t record_type, uint16_t rclass, uint32_t ttl, const void* data,
size_t size, size_t name_offset, size_t name_length, size_t record_offset,
size_t record_length, void* user_data) {
Q_UNUSED(sock);
Q_UNUSED(addrlen);
Q_UNUSED(query_id);
Q_UNUSED(entry_type);
Q_UNUSED(rclass);
Q_UNUSED(ttl);
Q_UNUSED(name_offset);
Q_UNUSED(name_length);
//qCDebug(KDECONNECT_CORE) << "Received DNS record of type" << recordTypeToStr(record_type) << "from socket" << sock << "with type" << entryTypeToStr(entry_type);
Discoverer::MdnsService *discoveredService = (Discoverer::MdnsService *)user_data;
switch (record_type) {
case MDNS_RECORDTYPE_PTR: {
// We don't use mdns_record_parse_ptr() because we want to extract just the service name instead of the full "<instance-name>._<service-type>._tcp.local." string
mdns_string_pair_t instanceNamePos = mdns_get_next_substring(data, size, record_offset);
discoveredService->name = QString::fromLatin1((char *)data + instanceNamePos.offset, instanceNamePos.length);
//static char instanceNameBuffer[256];
//mdns_string_t instanceName = mdns_record_parse_ptr(data, size, record_offset, record_length, instanceNameBuffer, sizeof(instanceNameBuffer));
//discoveredService->name = QString::fromLatin1(instanceName.str, instanceName.length);
if (discoveredService->address == QHostAddress::Null) {
discoveredService->address = QHostAddress(from); // In case we don't receive a A record, use from as address
}
} break;
case MDNS_RECORDTYPE_SRV: {
static char nameBuffer[256];
mdns_record_srv_t record = mdns_record_parse_srv(data, size, record_offset, record_length, nameBuffer, sizeof(nameBuffer));
// We can use the IP to connect so we don't need to store the "<hostname>.local." address.
//discoveredService->qualifiedHostname = QString::fromLatin1(record.name.str, record.name.length);
discoveredService->port = record.port;
} break;
case MDNS_RECORDTYPE_A: {
sockaddr_in addr;
mdns_record_parse_a(data, size, record_offset, record_length, &addr);
discoveredService->address = QHostAddress(ntohl(addr.sin_addr.s_addr));
} break;
case MDNS_RECORDTYPE_AAAA:
// Ignore IPv6 for now
//sockaddr_in6 addr6;
//mdns_record_parse_aaaa(data, size, record_offset, record_length, &addr6);
break;
case MDNS_RECORDTYPE_TXT: {
mdns_record_txt_t records[24];
size_t parsed = mdns_record_parse_txt(data, size, record_offset, record_length, records, sizeof(records) / sizeof(mdns_record_txt_t));
for (size_t itxt = 0; itxt < parsed; ++itxt) {
QString key = QString::fromLatin1(records[itxt].key.str, records[itxt].key.length);
QString value = QString::fromLatin1(records[itxt].value.str, records[itxt].value.length);
discoveredService->txtRecords[key] = value;
}
} break;
default:
// Ignore unknown record types
break;
}
return 0;
}
void Discoverer::startDiscovering(const QString &serviceType)
{
int num_sockets = listenForQueryResponses();
if (num_sockets <= 0) {
qCWarning(KDECONNECT_CORE) << "Failed to open any MDNS server sockets";
return;
}
sendQuery(serviceType);
}
void Discoverer::stopDiscovering()
{
stopListeningForQueryResponses();
}
void Discoverer::stopListeningForQueryResponses()
{
qCDebug(KDECONNECT_CORE) << "Closing" << responseSocketNotifiers.size() << "sockets";
for (QSocketNotifier *socketNotifier : qAsConst(responseSocketNotifiers)) {
mdns_socket_close(socketNotifier->socket());
delete socketNotifier;
}
responseSocketNotifiers.clear();
}
int Discoverer::listenForQueryResponses()
{
// Open a socket for each interface
QVector<int> sockets;
for (const QNetworkInterface &iface : QNetworkInterface::allInterfaces()) {
int flags = iface.flags();
if (!(flags & QNetworkInterface::IsUp) || !(flags & QNetworkInterface::CanMulticast) || (flags & QNetworkInterface::IsLoopBack)) {
continue;
}
for (const QNetworkAddressEntry &ifaceAddress : iface.addressEntries()) {
QHostAddress sourceAddress = ifaceAddress.ip();
if (sourceAddress.protocol() == QAbstractSocket::IPv4Protocol && sourceAddress != QHostAddress::LocalHost) {
qCDebug(KDECONNECT_CORE) << "Opening socket for address" << sourceAddress;
struct sockaddr_in saddr = qHostAddressToSockaddr(sourceAddress);
int socket = mdns_socket_open_ipv4(&saddr);
sockets.append(socket);
} else if (sourceAddress.protocol() == QAbstractSocket::IPv6Protocol && sourceAddress != QHostAddress::LocalHostIPv6) {
qCDebug(KDECONNECT_CORE) << "Opening socket for address6" << sourceAddress;
struct sockaddr_in6 saddr = qHostAddressToSockaddr6(sourceAddress);
int socket = mdns_socket_open_ipv6(&saddr);
sockets.append(socket);
}
}
}
// Start listening on all sockets
for (int socket : sockets) {
QSocketNotifier *socketNotifier = new QSocketNotifier(socket, QSocketNotifier::Read);
QObject::connect(socketNotifier, &QSocketNotifier::activated, [this](QSocketDescriptor socket) {
MdnsService discoveredService;
static char buffer[2048];
size_t num_records = mdns_query_recv(socket, buffer, sizeof(buffer), query_callback, (void *)&discoveredService, 0);
Q_UNUSED(num_records);
// qCDebug(KDECONNECT_CORE) << "Discovered service" << discoveredService.name << "at" << discoveredService.address << "in" << num_records <<
// "records via socket" << socket;
Q_EMIT serviceFound(discoveredService);
});
responseSocketNotifiers.append(socketNotifier);
}
qCDebug(KDECONNECT_CORE) << "Opened" << sockets.size() << "sockets to listen for MDNS query responses";
return sockets.size();
}
void Discoverer::sendQuery(const QString &serviceType)
{
qCDebug(KDECONNECT_CORE) << "Sending MDNS query for service" << serviceType;
mdns_query_t query;
QByteArray serviceTypeBytes = serviceType.toLatin1();
query.name = serviceTypeBytes.constData();
query.length = serviceTypeBytes.length();
query.type = MDNS_RECORDTYPE_PTR;
static char buffer[2048];
for (QSocketNotifier *socketNotifier : qAsConst(responseSocketNotifiers)) {
int socket = socketNotifier->socket();
qCDebug(KDECONNECT_CORE) << "Sending mDNS query via socket" << socket;
int ret = mdns_multiquery_send(socket, &query, 1, buffer, sizeof(buffer), 0);
if (ret < 0) {
qCWarning(KDECONNECT_CORE) << "Failed to send mDNS query:" << strerror(errno);
}
}
}
const QByteArray dnsSdName = QByteArray("_services._dns-sd._udp.local.");
static mdns_string_t createMdnsString(const QByteArray &str)
{
return mdns_string_t{str.constData(), (size_t)str.length()};
}
int countCommonLeadingBits(quint32 int1, quint32 int2) {
int count = 0;
while (int1 != 0 && int2 != 0) {
if ((int1 & 0x80000000) == (int2 & 0x80000000)) {
count++;
int1 <<= 1;
int2 <<= 1;
} else {
break;
}
}
return count;
}
static QHostAddress findBestAddressMatchV4(QVector<QHostAddress> hostAddresses, const struct sockaddr *fromAddress)
{
Q_ASSERT(!hostAddresses.empty());
if (hostAddresses.size() == 1 || fromAddress == nullptr) {
return hostAddresses[0];
}
QHostAddress otherIp = QHostAddress(fromAddress);
if (otherIp.protocol() != QAbstractSocket::IPv4Protocol) {
return hostAddresses[0];
}
// qDebug() << "I have more than one IP address:" << hostAddresses << "- Finding best match for source IP:" << otherIp;
QHostAddress matchingIp = hostAddresses[0];
int matchingBits = -1;
quint32 rawOtherIp = otherIp.toIPv4Address();
for (const QHostAddress& ip : hostAddresses) {
Q_ASSERT(ip.protocol() == QAbstractSocket::IPv4Protocol);
quint32 rawMyIp = ip.toIPv4Address();
// Since we don't have the network mask, we just compare the prefixes of the IPs to find the longest match
int matchingBitsCount = countCommonLeadingBits(rawMyIp, rawOtherIp);
if (matchingBitsCount > matchingBits) {
matchingIp = ip;
matchingBits = matchingBitsCount;
}
}
// qDebug() << "Found match:" << matchingIp;
return matchingIp;
}
static QHostAddress findBestAddressMatchV6(QVector<QHostAddress> hostAddresses, const struct sockaddr *fromAddress)
{
Q_ASSERT(!hostAddresses.empty());
// We could do the same logic for v6 that we do for V4, but we don't care that much about IPv6
return hostAddresses[0];
Q_UNUSED(fromAddress);
}
static mdns_record_t createMdnsRecord(const Announcer::AnnouncedInfo &self,
mdns_record_type_t record_type,
const struct sockaddr *fromAddress = nullptr, // used to determine IP to set in A and AAAA records
QHash<QByteArray, QByteArray>::const_iterator txtIterator = {})
{
mdns_record_t answer;
answer.type = record_type;
answer.rclass = 0;
answer.ttl = 0;
switch (record_type) {
case MDNS_RECORDTYPE_PTR: // maps "_<service-type>._tcp.local." to "<instance-name>._<service-type>._tcp.local."
answer.name = createMdnsString(self.serviceType);
answer.data.ptr.name = createMdnsString(self.serviceInstance);
break;
case MDNS_RECORDTYPE_SRV: // maps "<instance-name>._<service-type>._tcp.local." to "<hostname>.local." and port
answer.name = createMdnsString(self.serviceInstance);
answer.data.srv.name = createMdnsString(self.hostname);
answer.data.srv.port = self.port;
answer.data.srv.priority = 0;
answer.data.srv.weight = 0;
break;
case MDNS_RECORDTYPE_A: // maps "<hostname>.local." to IPv4
answer.name = createMdnsString(self.hostname);
answer.data.a.addr = qHostAddressToSockaddr(findBestAddressMatchV4(self.addressesV4, fromAddress));
break;
case MDNS_RECORDTYPE_AAAA: // maps "<hostname>.local." to IPv6
answer.name = createMdnsString(self.hostname);
answer.data.aaaa.addr = qHostAddressToSockaddr6(findBestAddressMatchV6(self.addressesV6, fromAddress));
break;
case MDNS_RECORDTYPE_TXT:
answer.name = createMdnsString(self.serviceInstance);
answer.type = MDNS_RECORDTYPE_TXT;
answer.data.txt.key = createMdnsString(txtIterator.key());
answer.data.txt.value = createMdnsString(txtIterator.value());
break;
default:
Q_ASSERT(false);
}
return answer;
}
// Callback handling questions incoming on service sockets
static int service_callback(int sock, const struct sockaddr* from, size_t addrlen, mdns_entry_type_t entry_type,
uint16_t query_id, uint16_t record_type, uint16_t rclass, uint32_t ttl, const void* data,
size_t size, size_t name_offset, size_t name_length, size_t record_offset,
size_t record_length, void* user_data) {
Q_UNUSED(ttl);
Q_UNUSED(name_length);
Q_UNUSED(record_offset);
Q_UNUSED(record_length);
static char sendbuffer[2024];
const Announcer::AnnouncedInfo &self = *((Announcer::AnnouncedInfo *)user_data);
if (entry_type != MDNS_ENTRYTYPE_QUESTION) {
return 0;
}
static char nameBuffer[256];
mdns_string_t nameMdnsString = mdns_string_extract(data, size, &name_offset, nameBuffer, sizeof(nameBuffer));
QByteArray name = QByteArray(nameMdnsString.str, nameMdnsString.length);
if (name == dnsSdName) {
if ((record_type == MDNS_RECORDTYPE_PTR) || (record_type == MDNS_RECORDTYPE_ANY)) {
// The PTR query was for the DNS-SD domain, send answer with a PTR record for the service name we advertise.
mdns_record_t answer = createMdnsRecord(self, MDNS_RECORDTYPE_PTR);
uint16_t unicast = (rclass & MDNS_UNICAST_RESPONSE);
if (unicast) {
mdns_query_answer_unicast(sock, from, addrlen, sendbuffer, sizeof(sendbuffer), query_id,
(mdns_record_type_t)record_type, nameMdnsString.str, nameMdnsString.length,
answer, nullptr, 0, nullptr, 0);
} else {
mdns_query_answer_multicast(sock, sendbuffer, sizeof(sendbuffer), answer, nullptr, 0, nullptr, 0);
}
}
} else if (name == self.serviceType) {
if ((record_type == MDNS_RECORDTYPE_PTR) || (record_type == MDNS_RECORDTYPE_ANY)) {
// The PTR query was for our service, answer a PTR record reverse mapping the queried service name
// to our service instance name and add additional records containing the SRV record mapping the
// service instance name to our qualified hostname and port, as well as any IPv4/IPv6 and TXT records
mdns_record_t answer = createMdnsRecord(self, MDNS_RECORDTYPE_PTR);
QVector<mdns_record_t> additional;
additional.append(createMdnsRecord(self, MDNS_RECORDTYPE_SRV));
if (!self.addressesV4.empty()) {
additional.append(createMdnsRecord(self, MDNS_RECORDTYPE_A, from));
}
if (!self.addressesV6.empty()) {
additional.append(createMdnsRecord(self, MDNS_RECORDTYPE_AAAA, from));
}
for (auto txtIterator = self.txtRecords.cbegin(); txtIterator != self.txtRecords.cend(); txtIterator++) {
additional.append(createMdnsRecord(self, MDNS_RECORDTYPE_TXT, nullptr, txtIterator));
}
uint16_t unicast = (rclass & MDNS_UNICAST_RESPONSE);
if (unicast) {
mdns_query_answer_unicast(sock, from, addrlen, sendbuffer, sizeof(sendbuffer), query_id,
(mdns_record_type_t)record_type, nameMdnsString.str, nameMdnsString.length,
answer, nullptr, 0, additional.constData(), additional.length());
} else {
mdns_query_answer_multicast(sock, sendbuffer, sizeof(sendbuffer), answer, nullptr, 0,
additional.constData(), additional.length());
}
}
} else if (name == self.serviceInstance) {
if ((record_type == MDNS_RECORDTYPE_SRV) || (record_type == MDNS_RECORDTYPE_ANY)) {
// The SRV query was for our service instance, answer a SRV record mapping the service
// instance name to our qualified hostname (typically "<hostname>.local.") and port, as
// well as any IPv4/IPv6 address for the hostname as A/AAAA records and TXT records
mdns_record_t answer = createMdnsRecord(self, MDNS_RECORDTYPE_SRV);
QVector<mdns_record_t> additional;
if (!self.addressesV4.empty()) {
additional.append(createMdnsRecord(self, MDNS_RECORDTYPE_A, from));
}
if (!self.addressesV6.empty()) {
additional.append(createMdnsRecord(self, MDNS_RECORDTYPE_AAAA, from));
}
for (auto txtIterator = self.txtRecords.cbegin(); txtIterator != self.txtRecords.cend(); txtIterator++) {
additional.append(createMdnsRecord(self, MDNS_RECORDTYPE_TXT, nullptr, txtIterator));
}
uint16_t unicast = (rclass & MDNS_UNICAST_RESPONSE);
if (unicast) {
mdns_query_answer_unicast(sock, from, addrlen, sendbuffer, sizeof(sendbuffer), query_id,
(mdns_record_type_t)record_type, nameMdnsString.str, nameMdnsString.length,
answer, nullptr, 0, additional.constData(), additional.length());
} else {
mdns_query_answer_multicast(sock, sendbuffer, sizeof(sendbuffer), answer, nullptr, 0,
additional.constData(), additional.length());
}
}
} else if (name == self.hostname) {
if (((record_type == MDNS_RECORDTYPE_A) || (record_type == MDNS_RECORDTYPE_ANY)) && !self.addressesV4.empty()) {
// The A query was for our qualified hostname and we have an IPv4 address, answer with an A
// record mapping the hostname to an IPv4 address, as well as an AAAA record and TXT records
mdns_record_t answer = createMdnsRecord(self, MDNS_RECORDTYPE_A, from);
QVector<mdns_record_t> additional;
if (!self.addressesV6.empty()) {
additional.append(createMdnsRecord(self, MDNS_RECORDTYPE_AAAA, from));
}
for (auto txtIterator = self.txtRecords.cbegin(); txtIterator != self.txtRecords.cend(); txtIterator++) {
additional.append(createMdnsRecord(self, MDNS_RECORDTYPE_TXT, nullptr, txtIterator));
}
uint16_t unicast = (rclass & MDNS_UNICAST_RESPONSE);
if (unicast) {
mdns_query_answer_unicast(sock, from, addrlen, sendbuffer, sizeof(sendbuffer), query_id,
(mdns_record_type_t)record_type, nameMdnsString.str, nameMdnsString.length,
answer, nullptr, 0, additional.constData(), additional.length());
} else {
mdns_query_answer_multicast(sock, sendbuffer, sizeof(sendbuffer), answer, nullptr, 0,
additional.constData(), additional.length());
}
} else if (((record_type == MDNS_RECORDTYPE_AAAA) || (record_type == MDNS_RECORDTYPE_ANY)) && !self.addressesV6.empty()) {
// The AAAA query was for our qualified hostname and we have an IPv6 address, answer with an AAAA
// record mapping the hostname to an IPv4 address, as well as an A record and TXT records
mdns_record_t answer = createMdnsRecord(self, MDNS_RECORDTYPE_AAAA, from);
QVector<mdns_record_t> additional;
if (!self.addressesV4.empty()) {
additional.append(createMdnsRecord(self, MDNS_RECORDTYPE_A, from));
}
for (auto txtIterator = self.txtRecords.cbegin(); txtIterator != self.txtRecords.cend(); txtIterator++) {
additional.append(createMdnsRecord(self, MDNS_RECORDTYPE_TXT, nullptr, txtIterator));
}
uint16_t unicast = (rclass & MDNS_UNICAST_RESPONSE);
if (unicast) {
mdns_query_answer_unicast(sock, from, addrlen, sendbuffer, sizeof(sendbuffer), query_id,
(mdns_record_type_t)record_type, nameMdnsString.str, nameMdnsString.length,
answer, nullptr, 0, additional.constData(), additional.length());
} else {
mdns_query_answer_multicast(sock, sendbuffer, sizeof(sendbuffer), answer, nullptr, 0,
additional.constData(), additional.length());
}
}
} // else request is not for me
return 0;
}
// Open sockets to listen to incoming mDNS queries on port 5353
// When recieving, each socket can recieve data from all network interfaces
// Thus we only need to open one socket for each address family
int Announcer::listenForQueries()
{
auto callback = [this](QSocketDescriptor socket) {
static char buffer[2048];
mdns_socket_listen(socket, buffer, sizeof(buffer), service_callback, &self);
};
int numSockets = 0;
{
struct sockaddr_in sock_addr;
memset(&sock_addr, 0, sizeof(struct sockaddr_in));
sock_addr.sin_family = AF_INET;
#ifdef _WIN32
sock_addr.sin_addr = in4addr_any;
#else
sock_addr.sin_addr.s_addr = INADDR_ANY;
#endif
sock_addr.sin_port = htons(MDNS_PORT);
#ifdef __APPLE__
sock_addr.sin_len = sizeof(struct sockaddr_in);
#endif
int socket = mdns_socket_open_ipv4(&sock_addr);
if (socket >= 0) {
socketNotifier = new QSocketNotifier(socket, QSocketNotifier::Read);
QObject::connect(socketNotifier, &QSocketNotifier::activated, callback);
numSockets++;
}
}
{
struct sockaddr_in6 sock_addr;
memset(&sock_addr, 0, sizeof(struct sockaddr_in6));
sock_addr.sin6_family = AF_INET6;
sock_addr.sin6_addr = in6addr_any;
sock_addr.sin6_port = htons(MDNS_PORT);
#ifdef __APPLE__
sock_addr.sin6_len = sizeof(struct sockaddr_in6);
#endif
int socket = mdns_socket_open_ipv6(&sock_addr);
if (socket >= 0) {
socketNotifierV6 = new QSocketNotifier(socket, QSocketNotifier::Read);
QObject::connect(socketNotifierV6, &QSocketNotifier::activated, callback);
numSockets++;
}
}
return numSockets;
}
Announcer::Announcer(const QString &instanceName, const QString &serviceType, uint16_t port)
{
self.serviceType = serviceType.toLatin1();
if (!self.serviceType.endsWith('.')) {
// mdns.h needs all the qualified names to end with dot for some reason
self.serviceType.append('.');
}
self.serviceInstance = instanceName.toLatin1() + '.' + self.serviceType;
self.hostname = QHostInfo::localHostName().toLatin1() + ".local.";
self.port = port;
detectHostAddresses();
}
void Announcer::detectHostAddresses()
{
self.addressesV4.clear();
self.addressesV6.clear();
for (const QNetworkInterface &iface : QNetworkInterface::allInterfaces()) {
int flags = iface.flags();
if (!(flags & QNetworkInterface::IsUp) || !(flags & QNetworkInterface::CanMulticast) || (flags & QNetworkInterface::IsLoopBack)) {
continue;
}
for (const QNetworkAddressEntry &ifaceAddress : iface.addressEntries()) {
QHostAddress sourceAddress = ifaceAddress.ip();
if (sourceAddress.protocol() == QAbstractSocket::IPv4Protocol && sourceAddress != QHostAddress::LocalHost) {
self.addressesV4.append(sourceAddress);
} else if (sourceAddress.protocol() == QAbstractSocket::IPv6Protocol && sourceAddress != QHostAddress::LocalHostIPv6) {
self.addressesV6.append(sourceAddress);
}
}
}
}
void Announcer::startAnnouncing()
{
int num_sockets = listenForQueries();
if (num_sockets <= 0) {
qCWarning(KDECONNECT_CORE) << "Failed to open any MDNS client sockets";
return;
}
sendMulticastAnnounce(false);
}
void Announcer::stopAnnouncing()
{
sendMulticastAnnounce(true);
stopListeningForQueries();
}
void Announcer::stopListeningForQueries()
{
if (socketNotifier != nullptr) {
delete socketNotifier;
socketNotifier = nullptr;
}
if (socketNotifierV6 != nullptr) {
delete socketNotifierV6;
socketNotifierV6 = nullptr;
}
}
void Announcer::sendMulticastAnnounce(bool isGoodbye)
{
mdns_record_t ptr_record = createMdnsRecord(self, MDNS_RECORDTYPE_PTR);
QVector<mdns_record_t> additional;
additional.append(createMdnsRecord(self, MDNS_RECORDTYPE_SRV));
if (!self.addressesV4.empty()) {
additional.append(createMdnsRecord(self, MDNS_RECORDTYPE_A));
}
if (!self.addressesV6.empty()) {
additional.append(createMdnsRecord(self, MDNS_RECORDTYPE_AAAA));
}
for (auto txtIterator = self.txtRecords.cbegin(); txtIterator != self.txtRecords.cend(); txtIterator++) {
additional.append(createMdnsRecord(self, MDNS_RECORDTYPE_TXT, nullptr, txtIterator));
}
static char buffer[2048];
if (isGoodbye) {
qCDebug(KDECONNECT_CORE) << "Sending goodbye";
if (socketNotifier)
mdns_goodbye_multicast(socketNotifier->socket(), buffer, sizeof(buffer), ptr_record, nullptr, 0, additional.constData(), additional.length());
if (socketNotifierV6)
mdns_goodbye_multicast(socketNotifierV6->socket(), buffer, sizeof(buffer), ptr_record, nullptr, 0, additional.constData(), additional.length());
} else {
qCDebug(KDECONNECT_CORE) << "Sending announce";
if (socketNotifier)
mdns_announce_multicast(socketNotifier->socket(), buffer, sizeof(buffer), ptr_record, nullptr, 0, additional.constData(), additional.length());
if (socketNotifierV6)
mdns_announce_multicast(socketNotifierV6->socket(), buffer, sizeof(buffer), ptr_record, nullptr, 0, additional.constData(), additional.length());
}
}
} // namespace MdnsWrapper

View file

@ -0,0 +1,102 @@
/**
* SPDX-FileCopyrightText: 2023 Albert Vaca Cintora <albertvaka@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#ifndef KDECONNECT_MDNS_WRAPPER_H
#define KDECONNECT_MDNS_WRAPPER_H
#include <QHostAddress>
#include <QMap>
#include <QSocketNotifier>
#include <QString>
#include <QVector>
#include "kdeconnectcore_export.h"
/**
* A Qt wrapper for the mdns.h header-only library
* from https://github.com/mjansson/mdns
*/
namespace MdnsWrapper
{
class KDECONNECTCORE_EXPORT Discoverer : public QObject
{
Q_OBJECT
public:
struct MdnsService {
QString name; // The instance-name part in "<instance-name>._<service-type>._tcp.local."
uint16_t port;
QHostAddress address; // An IPv4 address (IPv6 addresses are ignored)
QMap<QString, QString> txtRecords;
};
// serviceType must be of the form "_<service-type>._<tcp/udp>.local"
void startDiscovering(const QString &serviceType);
void stopDiscovering();
void sendQuery(const QString &serviceType);
Q_SIGNALS:
void serviceFound(const MdnsWrapper::Discoverer::MdnsService &service);
private:
int listenForQueryResponses();
void stopListeningForQueryResponses();
QVector<QSocketNotifier *> responseSocketNotifiers;
};
class KDECONNECTCORE_EXPORT Announcer : public QObject
{
Q_OBJECT
public:
struct AnnouncedInfo {
QByteArray serviceType; // ie: "_<service-type>._tcp.local."
QByteArray serviceInstance; // ie: "<instance-name>._<service-type>._tcp.local."
QByteArray hostname; // ie: "<hostname>.local."
QVector<QHostAddress> addressesV4;
QVector<QHostAddress> addressesV6;
uint16_t port;
QHash<QByteArray, QByteArray> txtRecords;
};
// serviceType must be of the form "_<service-type>._<tcp/udp>.local"
Announcer(const QString &instanceName, const QString &serviceType, uint16_t port);
void putTxtRecord(const QString &key, const QString &value)
{
self.txtRecords[key.toLatin1()] = value.toLatin1();
}
void startAnnouncing();
void stopAnnouncing();
void sendMulticastAnnounce(bool isGoodbye);
public Q_SLOTS:
// notify that network interfaces changed since the creation of this Announcer
void onNetworkChange()
{
detectHostAddresses();
}
private:
int listenForQueries();
void stopListeningForQueries();
void detectHostAddresses();
AnnouncedInfo self;
QSocketNotifier *socketNotifier = nullptr;
QSocketNotifier *socketNotifierV6 = nullptr;
};
} // namespace MdnsWrapper
#endif

View file

@ -10,113 +10,51 @@
#include "kdeconnectconfig.h"
#include "lanlinkprovider.h"
#include <KDNSSD/PublicService>
#include <KDNSSD/RemoteService>
#include <KDNSSD/ServiceBrowser>
#include "mdns_wrapper.h"
const QString kServiceName = QStringLiteral("_kdeconnect._udp");
const QString kServiceType = QStringLiteral("_kdeconnect._udp.local");
MdnsDiscovery::MdnsDiscovery(LanLinkProvider *lanLinkProvider)
: lanLinkProvider(lanLinkProvider)
: mdnsAnnouncer(KdeConnectConfig::instance().deviceId(), kServiceType, LanLinkProvider::UDP_PORT)
{
switch (KDNSSD::ServiceBrowser::isAvailable()) {
case KDNSSD::ServiceBrowser::Stopped:
qCWarning(KDECONNECT_CORE) << "mDNS or Avahi daemons are not running, mDNS discovery not available";
break;
case KDNSSD::ServiceBrowser::Working:
qCDebug(KDECONNECT_CORE) << "mDNS discovery is available";
break;
case KDNSSD::ServiceBrowser::Unsupported:
qCWarning(KDECONNECT_CORE) << "mDNS discovery not available (library built without DNS-SD support)";
break;
KdeConnectConfig &config = KdeConnectConfig::instance();
mdnsAnnouncer.putTxtRecord(QStringLiteral("id"), config.deviceId());
mdnsAnnouncer.putTxtRecord(QStringLiteral("name"), config.name());
mdnsAnnouncer.putTxtRecord(QStringLiteral("type"), config.deviceType().toString());
mdnsAnnouncer.putTxtRecord(QStringLiteral("protocol"), QString::number(NetworkPacket::s_protocolVersion));
connect(&mdnsDiscoverer, &MdnsWrapper::Discoverer::serviceFound, this, [lanLinkProvider](const MdnsWrapper::Discoverer::MdnsService &service) {
if (KdeConnectConfig::instance().deviceId() == service.name) {
qCDebug(KDECONNECT_CORE) << "Discovered myself, ignoring";
return;
}
lanLinkProvider->sendUdpIdentityPacket(QList<QHostAddress>{service.address});
qCDebug(KDECONNECT_CORE) << "Discovered" << service.name << "at" << service.address;
});
}
MdnsDiscovery::~MdnsDiscovery()
{
stopAnnouncing();
stopDiscovering();
onStop();
}
void MdnsDiscovery::startAnnouncing()
void MdnsDiscovery::onStart()
{
if (m_publisher != nullptr) {
qCDebug(KDECONNECT_CORE) << "MDNS already announcing";
return;
}
qCDebug(KDECONNECT_CORE) << "MDNS start announcing";
KdeConnectConfig &config = KdeConnectConfig::instance();
m_publisher = new KDNSSD::PublicService(config.deviceId(), kServiceName, LanLinkProvider::UDP_PORT, QStringLiteral("local"));
m_publisher->setParent(this);
// We can't fit the device certificate in this field, so this is not enough info to create a Device and won't be used.
QMap<QString, QByteArray> data;
data[QStringLiteral("id")] = config.deviceId().toUtf8();
data[QStringLiteral("name")] = config.name().toUtf8();
data[QStringLiteral("type")] = config.deviceType().toString().toUtf8();
data[QStringLiteral("protocol")] = QString::number(NetworkPacket::s_protocolVersion).toUtf8();
m_publisher->setTextData(data);
connect(m_publisher, &KDNSSD::PublicService::published, [](bool successful) {
if (successful) {
qCDebug(KDECONNECT_CORE) << "MDNS published successfully";
} else {
qCWarning(KDECONNECT_CORE) << "MDNS failed to publish";
}
});
m_publisher->publishAsync();
mdnsAnnouncer.startAnnouncing();
mdnsDiscoverer.startDiscovering(kServiceType);
}
void MdnsDiscovery::stopAnnouncing()
void MdnsDiscovery::onStop()
{
if (m_publisher != nullptr) {
qCDebug(KDECONNECT_CORE) << "MDNS stop announcing";
delete m_publisher;
m_publisher = nullptr;
}
mdnsAnnouncer.stopAnnouncing();
mdnsDiscoverer.stopDiscovering();
}
void MdnsDiscovery::startDiscovering()
void MdnsDiscovery::onNetworkChange()
{
if (m_serviceBrowser != nullptr) {
qCDebug(KDECONNECT_CORE) << "MDNS already discovering";
return;
}
qCDebug(KDECONNECT_CORE) << "MDNS start discovering";
m_serviceBrowser = new KDNSSD::ServiceBrowser(kServiceName, true);
connect(m_serviceBrowser, &KDNSSD::ServiceBrowser::serviceAdded, [this](KDNSSD::RemoteService::Ptr service) {
if (KdeConnectConfig::instance().deviceId() == service->serviceName()) {
qCDebug(KDECONNECT_CORE) << "Discovered myself, ignoring";
return;
}
qCDebug(KDECONNECT_CORE) << "Discovered " << service->serviceName() << " at " << service->hostName();
QHostAddress address(service->hostName());
lanLinkProvider->sendUdpIdentityPacket(QList<QHostAddress>{address});
});
connect(m_serviceBrowser, &KDNSSD::ServiceBrowser::serviceRemoved, [](KDNSSD::RemoteService::Ptr service) {
qCDebug(KDECONNECT_CORE) << "Lost " << service->serviceName();
});
connect(m_serviceBrowser, &KDNSSD::ServiceBrowser::finished, []() {
qCDebug(KDECONNECT_CORE) << "Finished discovery";
});
m_serviceBrowser->startBrowse();
}
void MdnsDiscovery::stopDiscovering()
{
if (m_serviceBrowser != nullptr) {
qCDebug(KDECONNECT_CORE) << "MDNS stop discovering";
delete m_serviceBrowser;
m_serviceBrowser = nullptr;
}
mdnsAnnouncer.onNetworkChange();
mdnsDiscoverer.stopDiscovering();
mdnsDiscoverer.startDiscovering(kServiceType);
}
#include "moc_mdnsdiscovery.cpp"

View file

@ -11,12 +11,9 @@
#include "kdeconnectcore_export.h"
#include "mdns_wrapper.h"
class LanLinkProvider;
namespace KDNSSD
{
class PublicService;
class ServiceBrowser;
};
class KDECONNECTCORE_EXPORT MdnsDiscovery : public QObject
{
@ -26,16 +23,15 @@ public:
explicit MdnsDiscovery(LanLinkProvider *parent);
~MdnsDiscovery();
void startDiscovering();
void stopDiscovering();
void onStart();
void onStop();
void stopAnnouncing();
void startAnnouncing();
public Q_SLOTS:
void onNetworkChange();
private:
LanLinkProvider *lanLinkProvider = nullptr;
KDNSSD::PublicService *m_publisher = nullptr;
KDNSSD::ServiceBrowser *m_serviceBrowser = nullptr;
MdnsWrapper::Discoverer mdnsDiscoverer;
MdnsWrapper::Announcer mdnsAnnouncer;
};
#endif // KDECONNECT_SERVER_H

View file

@ -63,14 +63,6 @@ ConversationMessage::ConversationMessage(const qint32 &eventField,
{
}
ConversationMessage ConversationMessage::fromDBus(const QDBusVariant &var)
{
QDBusArgument data = var.variant().value<QDBusArgument>();
ConversationMessage message;
data >> message;
return message;
}
ConversationAddress::ConversationAddress(QString address)
: m_address(address)
{

View file

@ -56,7 +56,6 @@ public:
const qint64 &subID,
const QList<Attachment> &attachments);
static ConversationMessage fromDBus(const QDBusVariant &);
static void registerDbusType();
qint32 eventField() const

View file

@ -26,7 +26,6 @@ class QDBusPendingCallWatcher;
class QDBusInterface;
class QDBusError;
class QDBusObjectPath;
class QDBusVariant;
class MMTelephonyPlugin : public KdeConnectPlugin
{

View file

@ -126,9 +126,9 @@ void ConversationsDbusInterface::addMessages(const QList<ConversationMessage> &m
// Tell the world about what just happened
if (newConversation) {
Q_EMIT conversationCreated(QDBusVariant(QVariant::fromValue(message)));
Q_EMIT conversationCreated(QVariant::fromValue(message));
} else if (latestMessage) {
Q_EMIT conversationUpdated(QDBusVariant(QVariant::fromValue(message)));
Q_EMIT conversationUpdated(QVariant::fromValue(message));
}
}

View file

@ -109,7 +109,7 @@ Q_SIGNALS:
* Emitted whenever a conversation with no cached messages is added, either because the cache
* is being populated or because a new conversation has been created
*/
Q_SCRIPTABLE void conversationCreated(const QDBusVariant &msg);
Q_SCRIPTABLE void conversationCreated(const QVariant &msg);
/**
* Emitted whenever a conversation is being deleted
@ -120,7 +120,7 @@ Q_SIGNALS:
* Emitted whenever a message is added to a conversation and it is the newest message in the
* conversation
*/
Q_SCRIPTABLE void conversationUpdated(const QDBusVariant &msg);
Q_SCRIPTABLE void conversationUpdated(const QVariant &msg);
/**
* Emitted whenever we have handled a response from the phone indicating the total number of

View file

@ -74,7 +74,7 @@ size_t RequestConversationWorker::replyForConversation(const QList<ConversationM
if (i >= howMany) {
break;
}
Q_EMIT conversationMessageRead(QDBusVariant(QVariant::fromValue(*it)));
Q_EMIT conversationMessageRead(QVariant::fromValue(*it));
i++;
}

View file

@ -36,7 +36,7 @@ public Q_SLOTS:
void work();
Q_SIGNALS:
void conversationMessageRead(const QDBusVariant &msg);
void conversationMessageRead(const QVariant &msg);
void finished();
private:

View file

@ -110,15 +110,15 @@ void ConversationListModel::prepareConversationsList()
this);
}
void ConversationListModel::handleCreatedConversation(const QDBusVariant &msg)
void ConversationListModel::handleCreatedConversation(const QVariant &msg)
{
const ConversationMessage message = ConversationMessage::fromDBus(msg);
const ConversationMessage message = msg.value<ConversationMessage>();
createRowFromMessage(message);
}
void ConversationListModel::handleConversationUpdated(const QDBusVariant &msg)
void ConversationListModel::handleConversationUpdated(const QVariant &msg)
{
const ConversationMessage message = ConversationMessage::fromDBus(msg);
const ConversationMessage message = msg.value<ConversationMessage>();
createRowFromMessage(message);
}

View file

@ -49,8 +49,8 @@ public:
Q_INVOKABLE void createConversationForAddress(const QString &address);
public Q_SLOTS:
void handleCreatedConversation(const QDBusVariant &msg);
void handleConversationUpdated(const QDBusVariant &msg);
void handleCreatedConversation(const QVariant &msg);
void handleConversationUpdated(const QVariant &msg);
void createRowFromMessage(const ConversationMessage &message);
void printDBusError(const QDBusError &error);
void displayContacts();

View file

@ -182,10 +182,9 @@ void ConversationModel::createRowFromMessage(const ConversationMessage &message,
knownMessageIDs.insert(message.uID());
}
void ConversationModel::handleConversationUpdate(const QDBusVariant &msg)
void ConversationModel::handleConversationUpdate(const QVariant &msg)
{
ConversationMessage message = ConversationMessage::fromDBus(msg);
ConversationMessage message = msg.value<ConversationMessage>();
if (message.threadID() != m_threadId) {
// If a conversation which we are not currently viewing was updated, discard the information
qCDebug(KDECONNECT_SMS_CONVERSATION_MODEL) << "Saw update for thread" << message.threadID() << "but we are currently viewing" << m_threadId;
@ -194,10 +193,9 @@ void ConversationModel::handleConversationUpdate(const QDBusVariant &msg)
createRowFromMessage(message, 0);
}
void ConversationModel::handleConversationCreated(const QDBusVariant &msg)
void ConversationModel::handleConversationCreated(const QVariant &msg)
{
ConversationMessage message = ConversationMessage::fromDBus(msg);
ConversationMessage message = msg.value<ConversationMessage>();
if (m_threadId == INVALID_THREAD_ID && SmsHelper::isPhoneNumberMatch(m_addressList[0].address(), message.addresses().first().address())
&& !message.isMultitarget()) {
m_threadId = message.threadID();
@ -237,4 +235,18 @@ void ConversationModel::requestAttachmentPath(const qint64 &partID, const QStrin
m_conversationsInterface->requestAttachmentFile(partID, uniqueIdentifier);
}
QDBusArgument &operator<<(QDBusArgument &argument, const Message& message)
{
argument.beginStructure();
argument.endStructure();
return argument;
}
const QDBusArgument &operator>>(const QDBusArgument &argument, Message &message)
{
argument.beginStructure();
argument.endStructure();
return argument;
}
#include "moc_conversationmodel.cpp"

View file

@ -67,9 +67,9 @@ Q_SIGNALS:
void deviceIdChanged(const QString &value);
private Q_SLOTS:
void handleConversationUpdate(const QDBusVariant &message);
void handleConversationLoaded(qint64 threadID);
void handleConversationCreated(const QDBusVariant &message);
Q_SCRIPTABLE void handleConversationUpdate(const QVariant &message);
Q_SCRIPTABLE void handleConversationLoaded(qint64 threadID);
Q_SCRIPTABLE void handleConversationCreated(const QVariant &message);
private:
void createRowFromMessage(const ConversationMessage &message, int pos);

View file

@ -12,3 +12,4 @@ set(kdeconnect_libraries
ecm_add_test(pluginloadtest.cpp LINK_LIBRARIES ${kdeconnect_libraries})
ecm_add_test(sendfiletest.cpp LINK_LIBRARIES ${kdeconnect_libraries})
ecm_add_test(mdnstest.cpp LINK_LIBRARIES ${kdeconnect_libraries})

62
tests/mdnstest.cpp Normal file
View file

@ -0,0 +1,62 @@
/**
* SPDX-FileCopyrightText: 2023 Albert Vaca Cintora <albertvaka@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include <QCoreApplication>
#include <QObject>
#include <QSignalSpy>
#include <QSocketNotifier>
#include <QStandardPaths>
#include <QTest>
#include "core/backends/lan/mdns_wrapper.h"
#include "kdeconnect-version.h"
class TestMdns : public QObject
{
Q_OBJECT
public:
TestMdns()
{
}
private Q_SLOTS:
void testAnounceAndDiscover()
{
QString instanceName = QStringLiteral("myinstance");
uint16_t instancePort = 1716;
QString serviceType = QStringLiteral("_test._udp.local");
QString txtKey = QStringLiteral("keyerino");
QString txtValue = QStringLiteral("valuerino");
MdnsWrapper::Announcer announcer(instanceName, serviceType, instancePort);
announcer.putTxtRecord(txtKey, txtValue);
MdnsWrapper::Discoverer discoverer;
QSignalSpy spy(&discoverer, &MdnsWrapper::Discoverer::serviceFound);
connect(&discoverer, &MdnsWrapper::Discoverer::serviceFound, this, [instanceName, instancePort, txtKey, txtValue](const MdnsWrapper::Discoverer::MdnsService &service) {
QCOMPARE(instanceName, service.name);
QCOMPARE(instancePort, service.port);
QVERIFY(service.txtRecords.size() == 1);
QVERIFY(service.txtRecords.contains(txtKey));
QCOMPARE(txtValue, service.txtRecords.value(txtKey));
});
announcer.startAnnouncing();
discoverer.startDiscovering(serviceType);
QVERIFY(spy.wait(2000));
QVERIFY(spy.count() > 0);
discoverer.stopDiscovering();
announcer.stopAnnouncing();
}
};
QTEST_MAIN(TestMdns);
#include "mdnstest.moc"