File indexing completed on 2024-12-01 07:25:01

0001 
0002 // SPDX-License-Identifier: GPL-3.0-or-later
0003 // SPDX-FileCopyrightText: 2019-2020 Casper Meijn <casper@meijn.net>
0004 // SPDX-FileCopyrightText: 2023 Harald Sitter <sitter@kde.org>
0005 
0006 #include <KDSoapClient/KDQName>
0007 #include <QNetworkDatagram>
0008 #include <QNetworkInterface>
0009 #include <QRegularExpression>
0010 #include <QSignalSpy>
0011 #include <QTest>
0012 #include <QUdpSocket>
0013 #include <QXmlStreamReader>
0014 
0015 #include "wsdiscoveryclient.h"
0016 #include "wsdiscoverytargetservice.h"
0017 
0018 Q_DECLARE_METATYPE(WSDiscoveryTargetService)
0019 
0020 static constexpr auto clientPort = 15744;
0021 
0022 class testWSDiscoveryClient : public QObject
0023 {
0024     Q_OBJECT
0025 public:
0026     explicit testWSDiscoveryClient(QObject *parent = nullptr)
0027         : QObject(parent)
0028     {
0029         ;
0030     }
0031 
0032 private Q_SLOTS:
0033     static void testSendProbe();
0034     static void testSendResolve();
0035     static void testReceiveProbeMatch();
0036     static void testReceiveResolveMatch();
0037 
0038 private:
0039     static QByteArray zeroOutUuid(const QByteArray &original);
0040     static QByteArray expectedSendProbeData();
0041     static QByteArray expectedSendResolveData();
0042     static QByteArray toBeSendProbeMatchData();
0043     static QByteArray toBeSendResolveMatchData();
0044     static QByteArray formatXml(const QByteArray &original);
0045 };
0046 
0047 void testWSDiscoveryClient::testSendProbe()
0048 {
0049     QUdpSocket testSocket;
0050     QVERIFY(testSocket.bind(QHostAddress::Any, 3702, QAbstractSocket::ShareAddress));
0051     const auto ifaces = QNetworkInterface::allInterfaces();
0052     for (const auto &iface : ifaces) {
0053         QVERIFY(testSocket.joinMulticastGroup(QHostAddress(QStringLiteral("FF02::C")), iface));
0054     }
0055 
0056     KDQName type(QStringLiteral("tdn:NetworkVideoTransmitter"));
0057     type.setNameSpace(QStringLiteral("http://www.onvif.org/ver10/network/wsdl"));
0058     auto typeList = QList<KDQName>() << type;
0059 
0060     auto scopeList = QList<QUrl>() << QUrl(QStringLiteral("onvif://www.onvif.org/Profile/Streaming"));
0061 
0062     WSDiscoveryClient discoveryClient;
0063     discoveryClient.start();
0064     discoveryClient.sendProbe(typeList, scopeList);
0065 
0066     QVERIFY(testSocket.hasPendingDatagrams());
0067     auto datagram = testSocket.receiveDatagram();
0068     auto zeroedDatagram = zeroOutUuid(datagram.data());
0069     QCOMPARE(formatXml(zeroedDatagram), formatXml(expectedSendProbeData()));
0070 }
0071 
0072 QByteArray testWSDiscoveryClient::expectedSendProbeData()
0073 {
0074     return QByteArray(
0075         "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
0076         "<soap:Envelope"
0077         "  xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\""
0078         "  xmlns:soap-enc=\"http://www.w3.org/2003/05/soap-encoding\""
0079         "  xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\""
0080         "  xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
0081         "  xmlns:wsa=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\""
0082         "  xmlns:n1=\"http://schemas.xmlsoap.org/ws/2005/04/discovery\">"
0083         "  <soap:Header>"
0084         "    <wsa:To>urn:schemas-xmlsoap-org:ws:2005:04:discovery</wsa:To>"
0085         "    <wsa:ReplyTo><wsa:Address>http://www.w3.org/2005/08/addressing/anonymous</wsa:Address></wsa:ReplyTo>"
0086         "    <wsa:Action>http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</wsa:Action>"
0087         "    <wsa:MessageID>urn:uuid:00000000-0000-0000-0000-000000000000</wsa:MessageID>"
0088         "  </soap:Header>"
0089         "  <soap:Body>"
0090         "    <n1:Probe>"
0091         "      <n1:Types xmlns:tdn=\"http://www.onvif.org/ver10/network/wsdl\">tdn:NetworkVideoTransmitter</n1:Types>"
0092         "      <n1:Scopes>onvif://www.onvif.org/Profile/Streaming</n1:Scopes>"
0093         "    </n1:Probe>"
0094         "  </soap:Body>"
0095         "</soap:Envelope>");
0096 }
0097 
0098 void testWSDiscoveryClient::testSendResolve()
0099 {
0100     QUdpSocket testSocket;
0101     QVERIFY(testSocket.bind(QHostAddress::Any, 3702, QAbstractSocket::ShareAddress));
0102     const auto ifaces = QNetworkInterface::allInterfaces();
0103     for (const auto &iface : ifaces) {
0104         QVERIFY(testSocket.joinMulticastGroup(QHostAddress(QStringLiteral("FF02::C")), iface));
0105     }
0106 
0107     WSDiscoveryClient discoveryClient;
0108     discoveryClient.start();
0109     discoveryClient.sendResolve(QStringLiteral("A_Unique_Reference"));
0110 
0111     QVERIFY(testSocket.hasPendingDatagrams());
0112     auto datagram = testSocket.receiveDatagram();
0113     auto zeroedDatagram = zeroOutUuid(datagram.data());
0114     QCOMPARE(formatXml(zeroedDatagram), formatXml(expectedSendResolveData()));
0115 }
0116 
0117 QByteArray testWSDiscoveryClient::expectedSendResolveData()
0118 {
0119     return QByteArray(
0120         "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
0121         "<soap:Envelope"
0122         "  xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\""
0123         "  xmlns:soap-enc=\"http://www.w3.org/2003/05/soap-encoding\""
0124         "  xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\""
0125         "  xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
0126         "  xmlns:wsa=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\""
0127         "  xmlns:n1=\"http://schemas.xmlsoap.org/ws/2005/04/discovery\">"
0128         "  <soap:Header>"
0129         "    <wsa:To>urn:schemas-xmlsoap-org:ws:2005:04:discovery</wsa:To>"
0130         "    <wsa:ReplyTo><wsa:Address>http://www.w3.org/2005/08/addressing/anonymous</wsa:Address></wsa:ReplyTo>"
0131         "    <wsa:Action>http://schemas.xmlsoap.org/ws/2005/04/discovery/Resolve</wsa:Action>"
0132         "    <wsa:MessageID>urn:uuid:00000000-0000-0000-0000-000000000000</wsa:MessageID>"
0133         "  </soap:Header>"
0134         "  <soap:Body>"
0135         "    <n1:Resolve>"
0136         "      <wsa:EndpointReference>"
0137         "        <wsa:Address>A_Unique_Reference</wsa:Address>"
0138         "      </wsa:EndpointReference>"
0139         "    </n1:Resolve>"
0140         "  </soap:Body>"
0141         "</soap:Envelope>");
0142 }
0143 
0144 void testWSDiscoveryClient::testReceiveProbeMatch()
0145 {
0146     WSDiscoveryClient discoveryClient;
0147     discoveryClient.start(clientPort);
0148 
0149     qRegisterMetaType<WSDiscoveryTargetService>();
0150     QSignalSpy spy(&discoveryClient, &WSDiscoveryClient::probeMatchReceived);
0151     QVERIFY(spy.isValid());
0152 
0153     QUdpSocket testSocket;
0154     QVERIFY(testSocket.bind(QHostAddress::Any, 3702, QAbstractSocket::ShareAddress));
0155     const auto ifaces = QNetworkInterface::allInterfaces();
0156     for (const auto &iface : ifaces) {
0157         QVERIFY(testSocket.joinMulticastGroup(QHostAddress(QStringLiteral("FF02::C")), iface));
0158     }
0159     testSocket.writeDatagram(toBeSendProbeMatchData(), QHostAddress::LocalHost, clientPort);
0160 
0161     QVERIFY(spy.wait(1000));
0162 
0163     QCOMPARE(spy.count(), 1); // make sure the signal was emitted exactly one time
0164     QList<QVariant> arguments = spy.takeFirst(); // take the first signal
0165 
0166     const WSDiscoveryTargetService &probeMatchService = qvariant_cast<WSDiscoveryTargetService>(arguments.at(0));
0167 
0168     QCOMPARE(probeMatchService.endpointReference(), "Incomming_unique_reference");
0169     QCOMPARE(probeMatchService.scopeList().size(), 1);
0170     QCOMPARE(probeMatchService.scopeList().at(0), QUrl(QStringLiteral("ldap:///ou=engineering,o=examplecom,c=us")));
0171     QCOMPARE(probeMatchService.typeList().size(), 1);
0172     QCOMPARE(probeMatchService.typeList().at(0), KDQName(QStringLiteral("http://printer.example.org/2003/imaging"), QStringLiteral("PrintBasic")));
0173     QCOMPARE(probeMatchService.xAddrList().size(), 1);
0174     QCOMPARE(probeMatchService.xAddrList().at(0), QUrl(QStringLiteral("http://prn-example/PRN42/b42-1668-a")));
0175     QVERIFY(probeMatchService.lastSeen().msecsTo(QDateTime::currentDateTime()) < 500);
0176 }
0177 
0178 QByteArray testWSDiscoveryClient::toBeSendProbeMatchData()
0179 {
0180     return QByteArray(
0181         "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
0182         "<soap:Envelope"
0183         "  xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\""
0184         "  xmlns:soap-enc=\"http://www.w3.org/2003/05/soap-encoding\""
0185         "  xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\""
0186         "  xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
0187         "  xmlns:wsa=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\""
0188         "  xmlns:n1=\"http://schemas.xmlsoap.org/ws/2005/04/discovery\">"
0189         "  <soap:Header>"
0190         "    <wsa:To>urn:schemas-xmlsoap-org:ws:2005:04:discovery</wsa:To>"
0191         "    <wsa:Action>http://schemas.xmlsoap.org/ws/2005/04/discovery/ProbeMatches</wsa:Action>"
0192         "    <wsa:MessageID>urn:uuid:00000000-0000-0000-0000-000000000000</wsa:MessageID>"
0193         "    <wsa:RelatesTo>xs:anyURI</wsa:RelatesTo>"
0194         "    <n1:AppSequence InstanceId=\"12\" MessageNumber=\"12\"/>"
0195         "  </soap:Header>"
0196         "  <soap:Body>"
0197         "    <n1:ProbeMatches>"
0198         "      <n1:ProbeMatch>"
0199         "        <wsa:EndpointReference><wsa:Address>Incomming_unique_reference</wsa:Address></wsa:EndpointReference>"
0200         "        <n1:Types xmlns:i=\"http://printer.example.org/2003/imaging\">i:PrintBasic</n1:Types>"
0201         "        <n1:Scopes>ldap:///ou=engineering,o=examplecom,c=us</n1:Scopes>"
0202         "        <n1:XAddrs>http://prn-example/PRN42/b42-1668-a</n1:XAddrs>"
0203         "        <n1:MetadataVersion>12</n1:MetadataVersion>"
0204         "      </n1:ProbeMatch>"
0205         "    </n1:ProbeMatches>"
0206         "  </soap:Body>"
0207         "</soap:Envelope>");
0208 }
0209 
0210 void testWSDiscoveryClient::testReceiveResolveMatch()
0211 {
0212     WSDiscoveryClient discoveryClient;
0213     discoveryClient.start(clientPort);
0214 
0215     qRegisterMetaType<WSDiscoveryTargetService>();
0216     QSignalSpy spy(&discoveryClient, &WSDiscoveryClient::resolveMatchReceived);
0217     QVERIFY(spy.isValid());
0218 
0219     QUdpSocket testSocket;
0220     QVERIFY(testSocket.bind(QHostAddress::Any, 3702, QAbstractSocket::ShareAddress));
0221     const auto ifaces = QNetworkInterface::allInterfaces();
0222     for (const auto &iface : ifaces) {
0223         QVERIFY(testSocket.joinMulticastGroup(QHostAddress(QStringLiteral("FF02::C")), iface));
0224     }
0225     testSocket.writeDatagram(toBeSendResolveMatchData(), QHostAddress::LocalHost, clientPort);
0226 
0227     QVERIFY(spy.wait(1000));
0228 
0229     QCOMPARE(spy.count(), 1); // make sure the signal was emitted exactly one time
0230     QList<QVariant> arguments = spy.takeFirst(); // take the first signal
0231 
0232     const WSDiscoveryTargetService &probeMatchService = qvariant_cast<WSDiscoveryTargetService>(arguments.at(0));
0233 
0234     QCOMPARE(probeMatchService.endpointReference(), "Incomming_resolve_reference");
0235     QCOMPARE(probeMatchService.scopeList().size(), 1);
0236     QCOMPARE(probeMatchService.scopeList().at(0), QUrl(QStringLiteral("ldap:///ou=floor1,ou=b42,ou=anytown,o=examplecom,c=us")));
0237     QCOMPARE(probeMatchService.typeList().size(), 1);
0238     QCOMPARE(probeMatchService.typeList().at(0), KDQName(QStringLiteral("http://printer.example.org/2003/imaging"), QStringLiteral("PrintAdvanced")));
0239     QCOMPARE(probeMatchService.xAddrList().size(), 1);
0240     QCOMPARE(probeMatchService.xAddrList().at(0), QUrl(QStringLiteral("http://printer.local:8080")));
0241     QVERIFY(probeMatchService.lastSeen().msecsTo(QDateTime::currentDateTime()) < 500);
0242 }
0243 
0244 QByteArray testWSDiscoveryClient::toBeSendResolveMatchData()
0245 {
0246     return QByteArray(
0247         "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
0248         "<soap:Envelope"
0249         "  xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\""
0250         "  xmlns:soap-enc=\"http://www.w3.org/2003/05/soap-encoding\""
0251         "  xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\""
0252         "  xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
0253         "  xmlns:wsa=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\""
0254         "  xmlns:n1=\"http://schemas.xmlsoap.org/ws/2005/04/discovery\">"
0255         "  <soap:Header>"
0256         "    <wsa:To>urn:schemas-xmlsoap-org:ws:2005:04:discovery</wsa:To>"
0257         "    <wsa:Action>http://schemas.xmlsoap.org/ws/2005/04/discovery/ResolveMatches</wsa:Action>"
0258         "    <wsa:MessageID>urn:uuid:00000000-0000-0000-0000-000000000000</wsa:MessageID>"
0259         "    <wsa:RelatesTo>xs:anyURI</wsa:RelatesTo>"
0260         "    <n1:AppSequence InstanceId=\"12\" MessageNumber=\"13\"/>"
0261         "  </soap:Header>"
0262         "  <soap:Body>"
0263         "    <n1:ResolveMatches>"
0264         "      <n1:ResolveMatch>"
0265         "        <wsa:EndpointReference><wsa:Address>Incomming_resolve_reference</wsa:Address></wsa:EndpointReference>"
0266         "        <n1:Types xmlns:i=\"http://printer.example.org/2003/imaging\">i:PrintAdvanced</n1:Types>"
0267         "        <n1:Scopes>ldap:///ou=floor1,ou=b42,ou=anytown,o=examplecom,c=us</n1:Scopes>"
0268         "        <n1:XAddrs>http://printer.local:8080</n1:XAddrs>"
0269         "        <n1:MetadataVersion>12</n1:MetadataVersion>"
0270         "      </n1:ResolveMatch>"
0271         "    </n1:ResolveMatches>"
0272         "  </soap:Body>"
0273         "</soap:Envelope>");
0274 }
0275 
0276 QByteArray testWSDiscoveryClient::formatXml(const QByteArray &original)
0277 {
0278     QByteArray xmlOut;
0279 
0280     QXmlStreamReader reader(original);
0281     QXmlStreamWriter writer(&xmlOut);
0282     writer.setAutoFormatting(true);
0283 
0284     while (!reader.atEnd()) {
0285         reader.readNext();
0286         if (!reader.isWhitespace()) {
0287             writer.writeCurrentToken(reader);
0288         }
0289     }
0290 
0291     return xmlOut;
0292 }
0293 
0294 QByteArray testWSDiscoveryClient::zeroOutUuid(const QByteArray &original)
0295 {
0296     QString originalString = original;
0297     static QRegularExpression regExp(QStringLiteral("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"));
0298     originalString.replace(regExp, QStringLiteral("00000000-0000-0000-0000-000000000000"));
0299     return originalString.toLatin1();
0300 }
0301 
0302 QTEST_MAIN(testWSDiscoveryClient)
0303 
0304 #include "test_wsdiscoveryclient.moc"