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"