diff options
Diffstat (limited to 'tests/manual/gds/tst_gds.cpp')
-rw-r--r-- | tests/manual/gds/tst_gds.cpp | 329 |
1 files changed, 329 insertions, 0 deletions
diff --git a/tests/manual/gds/tst_gds.cpp b/tests/manual/gds/tst_gds.cpp new file mode 100644 index 0000000..9e57238 --- /dev/null +++ b/tests/manual/gds/tst_gds.cpp @@ -0,0 +1,329 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt OPC UA module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtOpcUa/QOpcUaProvider> +#include <QtOpcUa/QOpcUaGdsClient> +#include <QtOpcUa/QOpcUaApplicationIdentity> +#include <QtOpcUa/QOpcUaPkiConfiguration> +#include <QtOpcUa/QOpcUaAuthenticationInformation> +#include <QtOpcUa/QOpcUaEndpointDescription> +#include <QtOpcUa/QOpcUaApplicationRecordDataType> +#include <QtOpcUa/QOpcUaClient> +#include <QtOpcUa/QOpcUaX509DistinguishedName> + +#include <QtCore/QCoreApplication> +#include <QtCore/QScopedPointer> +#include <QHostInfo> + +#include <QtTest/QSignalSpy> +#include <QtTest/QtTest> + +#define defineDataMethod(name) void name()\ +{\ + QTest::addColumn<QString>("backend");\ + for (auto backend : m_backends) {\ + const QString rowName = QString("%1").arg(backend); \ + QTest::newRow(rowName.toLatin1().constData()) << backend ; \ + QVERIFY(!backend.isEmpty()); \ + }\ +} + +static QOpcUaPkiConfiguration getPkiConfig() +{ + QOpcUaPkiConfiguration pkiConfig; + const QString pkiDir = QCoreApplication::applicationDirPath() + "/pki"; + + pkiConfig.setClientCertificateFile(pkiDir + "/own/certs/application.der"); + pkiConfig.setPrivateKeyFile(pkiDir + "/own/private/application.pem"); + pkiConfig.setTrustListDirectory(pkiDir + "/trusted/certs"); + pkiConfig.setRevocationListDirectory(pkiDir + "/trusted/crl"); + pkiConfig.setIssuerListDirectory(pkiDir + "/issuers/certs"); + pkiConfig.setIssuerRevocationListDirectory(pkiDir + "/issuers/crl"); + return pkiConfig; +} + +static QOpcUaApplicationIdentity getAppIdentity() +{ + QOpcUaApplicationIdentity identity; + + const QString applicationUri = QStringLiteral("urn:%1:%2:%3") + .arg(QHostInfo::localHostName()) + .arg(QCoreApplication::organizationName()) + .arg(QCoreApplication::applicationName()); + const QString productUri = QStringLiteral("urn:%1:%2") + .arg(QCoreApplication::organizationName()) + .arg(QCoreApplication::applicationName()); + + identity.setProductUri(productUri); + identity.setApplicationUri(applicationUri); + identity.setApplicationName(QCoreApplication::applicationName()); + identity.setApplicationType(QOpcUaApplicationDescription::Client); + return identity; +} + +static bool removeSettingsFile() +{ + QSettings settings; + QFile file(settings.fileName()); + if (file.exists()) + return file.remove(); + return true; +} + +static void provideCredentials(QOpcUaAuthenticationInformation &authInfo) +{ + qDebug() << "Authentication credentials provided"; + authInfo.setUsernameAuthentication("root", "secret"); +} + +static void commonGdsClientSetup(QOpcUaGdsClient &gc, const QString &backend, const QOpcUaEndpointDescription endpoint) +{ + QObject::connect(&gc, &QOpcUaGdsClient::authenticationRequired, provideCredentials); + + gc.setBackend(backend); + gc.setEndpoint(endpoint); + gc.setApplicationIdentity(getAppIdentity()); + gc.setPkiConfiguration(getPkiConfig()); + + QOpcUaApplicationRecordDataType ar = gc.applicationRecord(); + ar.setApplicationNames(QVector<QOpcUaLocalizedText>{QOpcUaLocalizedText("en", gc.applicationIdentity().applicationName())}); + ar.setApplicationType(gc.applicationIdentity().applicationType()); + ar.setApplicationUri(gc.applicationIdentity().applicationUri()); + ar.setProductUri(gc.applicationIdentity().productUri()); + ar.setDiscoveryUrls(QVector<QString>{QLatin1String("opc.tcp://localhost")}); + gc.setApplicationRecord(ar); + + QOpcUaX509DistinguishedName dn; + dn.setEntry(QOpcUaX509DistinguishedName::Type::CountryName, QLatin1String("DE")); + dn.setEntry(QOpcUaX509DistinguishedName::Type::LocalityName, QLatin1String("Berlin")); + dn.setEntry(QOpcUaX509DistinguishedName::Type::StateOrProvinceName, QLatin1String("Berlin")); + dn.setEntry(QOpcUaX509DistinguishedName::Type::OrganizationName, QLatin1String("The Qt Company")); + gc.setCertificateSigningRequestPresets(dn, QLatin1String("foo.com")); +} + +// The tests have are depending on each other and have to be executed in order. + +class Tst_QOpcUaGds: public QObject +{ + Q_OBJECT + +public: + Tst_QOpcUaGds(); + +private slots: + void initTestCase(); + void cleanupTestCase(); + + defineDataMethod(registerApplication_data) + void registerApplication(); + defineDataMethod(reuseApplicationId_data) + void reuseApplicationId(); + defineDataMethod(reuseRegisteredUri_data) + void reuseRegisteredUri(); + defineDataMethod(serverForgotRegistration_data) + void serverForgotRegistration(); +private: + QStringList m_backends; + QOpcUaEndpointDescription m_endpoint; +}; + +Tst_QOpcUaGds::Tst_QOpcUaGds() +{ + m_backends = QOpcUaProvider::availableBackends(); +} + +void Tst_QOpcUaGds::initTestCase() +{ + QOpcUaEndpointDescription endpoint; + QOpcUaProvider provider; + + QScopedPointer<QOpcUaClient> client(provider.createClient("open62541")); + QVERIFY(!client.isNull()); + QSignalSpy endpointSpy(client.data(), &QOpcUaClient::endpointsRequestFinished); + client->requestEndpoints(QUrl("opc.tcp://172.17.0.1:48060")); + endpointSpy.wait(2000); + QCOMPARE(endpointSpy.size(), 1); + + const QVector<QOpcUaEndpointDescription> desc = endpointSpy.at(0).at(0).value<QVector<QOpcUaEndpointDescription>>(); + QVERIFY(desc.size() > 0); + + for (const auto &i : qAsConst(desc)) { + if (i.securityPolicy().endsWith("#Basic256Sha256") + && i.securityMode() == QOpcUaEndpointDescription::MessageSecurityMode::SignAndEncrypt) + m_endpoint = i; + } + + QVERIFY(m_endpoint.securityPolicy().endsWith("#Basic256Sha256")); +} + +void Tst_QOpcUaGds::registerApplication() +{ + QFETCH(QString, backend); + if (backend == "open62541") + QSKIP("Skipping open62541"); + + QVERIFY(removeSettingsFile()); + + QOpcUaGdsClient gc; + commonGdsClientSetup(gc, backend, m_endpoint); + + QSignalSpy registeredSpy(&gc, &QOpcUaGdsClient::applicationRegistered); + QSignalSpy certificateGroupsSpy(&gc, &QOpcUaGdsClient::certificateGroupsReceived); + QSignalSpy certificateStatusSpy(&gc, &QOpcUaGdsClient::certificateUpdateRequired); + QSignalSpy certificateUpdatedSpy(&gc, &QOpcUaGdsClient::certificateUpdated); + QSignalSpy trustListUpdatedSpy(&gc, &QOpcUaGdsClient::trustListUpdated); + + gc.start(); + + QVERIFY(certificateGroupsSpy.wait()); + QVERIFY(!certificateGroupsSpy.at(0).at(0).value<QStringList>().isEmpty()); + QVERIFY(registeredSpy.wait()); + + if (certificateStatusSpy.isEmpty()) + certificateStatusSpy.wait(); + + if (certificateUpdatedSpy.isEmpty()) + certificateUpdatedSpy.wait(); + + if (trustListUpdatedSpy.isEmpty()) + trustListUpdatedSpy.wait(); + + QVERIFY(!certificateStatusSpy.isEmpty()); + QVERIFY(!certificateUpdatedSpy.isEmpty()); + QVERIFY(!trustListUpdatedSpy.isEmpty()); +} + +void Tst_QOpcUaGds::reuseApplicationId() +{ + QFETCH(QString, backend); + if (backend == "open62541") + QSKIP("Skipping open62541"); + + // Keep settings file in order to reuse the existing registration + + QOpcUaGdsClient gc; + commonGdsClientSetup(gc, backend, m_endpoint); + + QSignalSpy registeredSpy(&gc, &QOpcUaGdsClient::applicationRegistered); + QSignalSpy certificateGroupsSpy(&gc, &QOpcUaGdsClient::certificateGroupsReceived); + + gc.start(); + + QVERIFY(certificateGroupsSpy.wait()); + QVERIFY(!certificateGroupsSpy.at(0).at(0).value<QStringList>().isEmpty()); + QVERIFY(registeredSpy.wait()); +} + +void Tst_QOpcUaGds::reuseRegisteredUri() +{ + // This test assumes that a registratin at the server is still present. + // It will remove the information locally and has to reuse the + // registration from the server. + + QFETCH(QString, backend); + if (backend == "open62541") + QSKIP("Skipping open62541"); + + // Remove the registration data locally; the server will keep it. + QSettings settings("Unknown Organization", "tst_gds"); + + QString applicationId = settings.value(QLatin1String("gds/applicationId")).toString(); + settings.remove(QLatin1String("gds/applicationId")); + settings.sync(); + + QVERIFY(removeSettingsFile()); + + QOpcUaGdsClient gc; + commonGdsClientSetup(gc, backend, m_endpoint); + + QSignalSpy registeredSpy(&gc, &QOpcUaGdsClient::applicationRegistered); + QSignalSpy certificateGroupsSpy(&gc, &QOpcUaGdsClient::certificateGroupsReceived); + + gc.start(); + + QVERIFY(certificateGroupsSpy.wait()); + QVERIFY(!certificateGroupsSpy.at(0).at(0).value<QStringList>().isEmpty()); + QVERIFY(registeredSpy.wait()); + QVERIFY(gc.applicationId().size() > 10); + if (!applicationId.isEmpty()) + QVERIFY(gc.applicationId() == applicationId); +} + +void Tst_QOpcUaGds::serverForgotRegistration() +{ + QFETCH(QString, backend); + if (backend == "open62541") + QSKIP("Skipping open62541"); + + // Setting an invalid application ID simulates a previous registration which has been + // forgotten by the server + QSettings settings("Unknown Organization", "tst_gds"); + settings.setValue(QLatin1String("gds/applicationId"), "ns=42;i=1111111"); + settings.sync(); + + QOpcUaGdsClient gc; + commonGdsClientSetup(gc, backend, m_endpoint); + + QSignalSpy registeredSpy(&gc, &QOpcUaGdsClient::applicationRegistered); + QSignalSpy certificateGroupsSpy(&gc, &QOpcUaGdsClient::certificateGroupsReceived); + + gc.start(); + + QVERIFY(certificateGroupsSpy.wait()); + QVERIFY(!certificateGroupsSpy.at(0).at(0).value<QStringList>().isEmpty()); + QVERIFY(registeredSpy.wait()); +} + +void Tst_QOpcUaGds::cleanupTestCase() +{ +} + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + + QTEST_SET_MAIN_SOURCE_PATH + + if (QOpcUaProvider::availableBackends().empty()) { + qDebug("No OPCUA backends found, skipping tests."); + return EXIT_SUCCESS; + } + + Tst_QOpcUaGds tc; + return QTest::qExec(&tc, argc, argv); +} + +#include "tst_gds.moc" |