// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2019-2020 Casper Meijn // SPDX-FileCopyrightText: 2023 Harald Sitter #include #include #include #include #include #include #include #include #include "wsdiscoveryclient.h" #include "wsdiscoverytargetservice.h" Q_DECLARE_METATYPE(WSDiscoveryTargetService) static constexpr auto clientPort = 15744; class testWSDiscoveryClient : public QObject { Q_OBJECT public: explicit testWSDiscoveryClient(QObject *parent = nullptr) : QObject(parent) { ; } private Q_SLOTS: static void testSendProbe(); static void testSendResolve(); static void testReceiveProbeMatch(); static void testReceiveResolveMatch(); private: static QByteArray zeroOutUuid(const QByteArray &original); static QByteArray expectedSendProbeData(); static QByteArray expectedSendResolveData(); static QByteArray toBeSendProbeMatchData(); static QByteArray toBeSendResolveMatchData(); static QByteArray formatXml(const QByteArray &original); }; void testWSDiscoveryClient::testSendProbe() { QUdpSocket testSocket; QVERIFY(testSocket.bind(QHostAddress::Any, 3702, QAbstractSocket::ShareAddress)); const auto ifaces = QNetworkInterface::allInterfaces(); for (const auto &iface : ifaces) { QVERIFY(testSocket.joinMulticastGroup(QHostAddress(QStringLiteral("FF02::C")), iface)); } KDQName type(QStringLiteral("tdn:NetworkVideoTransmitter")); type.setNameSpace(QStringLiteral("http://www.onvif.org/ver10/network/wsdl")); auto typeList = QList() << type; auto scopeList = QList() << QUrl(QStringLiteral("onvif://www.onvif.org/Profile/Streaming")); WSDiscoveryClient discoveryClient; discoveryClient.start(); discoveryClient.sendProbe(typeList, scopeList); QVERIFY(testSocket.hasPendingDatagrams()); auto datagram = testSocket.receiveDatagram(); auto zeroedDatagram = zeroOutUuid(datagram.data()); QCOMPARE(formatXml(zeroedDatagram), formatXml(expectedSendProbeData())); } QByteArray testWSDiscoveryClient::expectedSendProbeData() { return QByteArray( "" "" " " " urn:schemas-xmlsoap-org:ws:2005:04:discovery" " http://www.w3.org/2005/08/addressing/anonymous" " http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe" " urn:uuid:00000000-0000-0000-0000-000000000000" " " " " " " " tdn:NetworkVideoTransmitter" " onvif://www.onvif.org/Profile/Streaming" " " " " ""); } void testWSDiscoveryClient::testSendResolve() { QUdpSocket testSocket; QVERIFY(testSocket.bind(QHostAddress::Any, 3702, QAbstractSocket::ShareAddress)); const auto ifaces = QNetworkInterface::allInterfaces(); for (const auto &iface : ifaces) { QVERIFY(testSocket.joinMulticastGroup(QHostAddress(QStringLiteral("FF02::C")), iface)); } WSDiscoveryClient discoveryClient; discoveryClient.start(); discoveryClient.sendResolve(QStringLiteral("A_Unique_Reference")); QVERIFY(testSocket.hasPendingDatagrams()); auto datagram = testSocket.receiveDatagram(); auto zeroedDatagram = zeroOutUuid(datagram.data()); QCOMPARE(formatXml(zeroedDatagram), formatXml(expectedSendResolveData())); } QByteArray testWSDiscoveryClient::expectedSendResolveData() { return QByteArray( "" "" " " " urn:schemas-xmlsoap-org:ws:2005:04:discovery" " http://www.w3.org/2005/08/addressing/anonymous" " http://schemas.xmlsoap.org/ws/2005/04/discovery/Resolve" " urn:uuid:00000000-0000-0000-0000-000000000000" " " " " " " " " " A_Unique_Reference" " " " " " " ""); } void testWSDiscoveryClient::testReceiveProbeMatch() { WSDiscoveryClient discoveryClient; discoveryClient.start(clientPort); qRegisterMetaType(); QSignalSpy spy(&discoveryClient, &WSDiscoveryClient::probeMatchReceived); QVERIFY(spy.isValid()); QUdpSocket testSocket; QVERIFY(testSocket.bind(QHostAddress::Any, 3702, QAbstractSocket::ShareAddress)); const auto ifaces = QNetworkInterface::allInterfaces(); for (const auto &iface : ifaces) { QVERIFY(testSocket.joinMulticastGroup(QHostAddress(QStringLiteral("FF02::C")), iface)); } testSocket.writeDatagram(toBeSendProbeMatchData(), QHostAddress::LocalHost, clientPort); QVERIFY(spy.wait(1000)); QCOMPARE(spy.count(), 1); // make sure the signal was emitted exactly one time QList arguments = spy.takeFirst(); // take the first signal const WSDiscoveryTargetService &probeMatchService = qvariant_cast(arguments.at(0)); QCOMPARE(probeMatchService.endpointReference(), "Incomming_unique_reference"); QCOMPARE(probeMatchService.scopeList().size(), 1); QCOMPARE(probeMatchService.scopeList().at(0), QUrl(QStringLiteral("ldap:///ou=engineering,o=examplecom,c=us"))); QCOMPARE(probeMatchService.typeList().size(), 1); QCOMPARE(probeMatchService.typeList().at(0), KDQName(QStringLiteral("http://printer.example.org/2003/imaging"), QStringLiteral("PrintBasic"))); QCOMPARE(probeMatchService.xAddrList().size(), 1); QCOMPARE(probeMatchService.xAddrList().at(0), QUrl(QStringLiteral("http://prn-example/PRN42/b42-1668-a"))); QVERIFY(probeMatchService.lastSeen().msecsTo(QDateTime::currentDateTime()) < 500); } QByteArray testWSDiscoveryClient::toBeSendProbeMatchData() { return QByteArray( "" "" " " " urn:schemas-xmlsoap-org:ws:2005:04:discovery" " http://schemas.xmlsoap.org/ws/2005/04/discovery/ProbeMatches" " urn:uuid:00000000-0000-0000-0000-000000000000" " xs:anyURI" " " " " " " " " " " " Incomming_unique_reference" " i:PrintBasic" " ldap:///ou=engineering,o=examplecom,c=us" " http://prn-example/PRN42/b42-1668-a" " 12" " " " " " " ""); } void testWSDiscoveryClient::testReceiveResolveMatch() { WSDiscoveryClient discoveryClient; discoveryClient.start(clientPort); qRegisterMetaType(); QSignalSpy spy(&discoveryClient, &WSDiscoveryClient::resolveMatchReceived); QVERIFY(spy.isValid()); QUdpSocket testSocket; QVERIFY(testSocket.bind(QHostAddress::Any, 3702, QAbstractSocket::ShareAddress)); const auto ifaces = QNetworkInterface::allInterfaces(); for (const auto &iface : ifaces) { QVERIFY(testSocket.joinMulticastGroup(QHostAddress(QStringLiteral("FF02::C")), iface)); } testSocket.writeDatagram(toBeSendResolveMatchData(), QHostAddress::LocalHost, clientPort); QVERIFY(spy.wait(1000)); QCOMPARE(spy.count(), 1); // make sure the signal was emitted exactly one time QList arguments = spy.takeFirst(); // take the first signal const WSDiscoveryTargetService &probeMatchService = qvariant_cast(arguments.at(0)); QCOMPARE(probeMatchService.endpointReference(), "Incomming_resolve_reference"); QCOMPARE(probeMatchService.scopeList().size(), 1); QCOMPARE(probeMatchService.scopeList().at(0), QUrl(QStringLiteral("ldap:///ou=floor1,ou=b42,ou=anytown,o=examplecom,c=us"))); QCOMPARE(probeMatchService.typeList().size(), 1); QCOMPARE(probeMatchService.typeList().at(0), KDQName(QStringLiteral("http://printer.example.org/2003/imaging"), QStringLiteral("PrintAdvanced"))); QCOMPARE(probeMatchService.xAddrList().size(), 1); QCOMPARE(probeMatchService.xAddrList().at(0), QUrl(QStringLiteral("http://printer.local:8080"))); QVERIFY(probeMatchService.lastSeen().msecsTo(QDateTime::currentDateTime()) < 500); } QByteArray testWSDiscoveryClient::toBeSendResolveMatchData() { return QByteArray( "" "" " " " urn:schemas-xmlsoap-org:ws:2005:04:discovery" " http://schemas.xmlsoap.org/ws/2005/04/discovery/ResolveMatches" " urn:uuid:00000000-0000-0000-0000-000000000000" " xs:anyURI" " " " " " " " " " " " Incomming_resolve_reference" " i:PrintAdvanced" " ldap:///ou=floor1,ou=b42,ou=anytown,o=examplecom,c=us" " http://printer.local:8080" " 12" " " " " " " ""); } QByteArray testWSDiscoveryClient::formatXml(const QByteArray &original) { QByteArray xmlOut; QXmlStreamReader reader(original); QXmlStreamWriter writer(&xmlOut); writer.setAutoFormatting(true); while (!reader.atEnd()) { reader.readNext(); if (!reader.isWhitespace()) { writer.writeCurrentToken(reader); } } return xmlOut; } QByteArray testWSDiscoveryClient::zeroOutUuid(const QByteArray &original) { QString originalString = original; static QRegularExpression regExp(QStringLiteral("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}")); originalString.replace(regExp, QStringLiteral("00000000-0000-0000-0000-000000000000")); return originalString.toLatin1(); } QTEST_MAIN(testWSDiscoveryClient) #include "test_wsdiscoveryclient.moc"