aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.qmake.conf4
-rw-r--r--tests/auto/auto.pro6
-rw-r--r--tests/auto/coapnetworksettings.h142
-rw-r--r--tests/auto/coaptestserver.pri8
-rw-r--r--tests/auto/qcoapclient/qcoapclient.pro3
-rw-r--r--tests/auto/qcoapclient/testdata/ca_cert.pem12
-rw-r--r--tests/auto/qcoapclient/testdata/local_cert.pem13
-rw-r--r--tests/auto/qcoapclient/testdata/privkey.pem40
-rw-r--r--tests/auto/qcoapclient/tst_qcoapclient.cpp390
-rw-r--r--tests/auto/qcoapqudpconnection/qcoapqudpconnection.pro3
-rw-r--r--tests/auto/qcoapqudpconnection/tst_qcoapqudpconnection.cpp10
-rwxr-xr-xtests/testserver/californium/californium.sh34
-rw-r--r--tests/testserver/docker-compose.yml44
-rwxr-xr-xtests/testserver/freecoap/freecoap.sh34
14 files changed, 627 insertions, 116 deletions
diff --git a/.qmake.conf b/.qmake.conf
index 1ed62c5..f6895bb 100644
--- a/.qmake.conf
+++ b/.qmake.conf
@@ -1,3 +1,5 @@
load(qt_build_config)
-MODULE_VERSION = 5.13.1
+DEFINES += QT_NO_FOREACH QT_NO_JAVA_STYLE_ITERATORS QT_NO_LINKED_LIST
+
+MODULE_VERSION = 5.14.0
diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro
index 5bfc66e..32e4ac4 100644
--- a/tests/auto/auto.pro
+++ b/tests/auto/auto.pro
@@ -1,17 +1,15 @@
TEMPLATE = subdirs
-# TODO: enable disabled tests, when CI is configured properly
-
SUBDIRS += \
cmake \
-# qcoapclient \
+ qcoapclient \
qcoapmessage \
qcoapoption \
qcoaprequest \
qcoapresource
qtConfig(private_tests): SUBDIRS += \
-# qcoapqudpconnection \
+ qcoapqudpconnection \
qcoapinternalrequest \
qcoapinternalreply \
qcoapreply
diff --git a/tests/auto/coapnetworksettings.h b/tests/auto/coapnetworksettings.h
index 0e1ee04..7f425ff 100644
--- a/tests/auto/coapnetworksettings.h
+++ b/tests/auto/coapnetworksettings.h
@@ -31,6 +31,8 @@
#include <QtTest>
#include <QtCore/qstring.h>
#include <QtNetwork/qhostinfo.h>
+#include <QtCoap/qcoapclient.h>
+#include <QtCoap/qcoapsecurityconfiguration.h>
/*!
\internal
@@ -49,23 +51,139 @@
*/
namespace QtCoapNetworkSettings
{
- QString testServerHost()
- {
+
+#if defined(COAP_TEST_SERVER_IP) || defined(QT_TEST_SERVER)
+#define CHECK_FOR_COAP_SERVER
+#else
+#define CHECK_FOR_COAP_SERVER \
+ QSKIP("CoAP server is not setup, skipping the test..."); \
+ return;
+#endif
+
+#if defined(QT_TEST_SERVER) && !defined(COAP_TEST_SERVER_IP)
+static QString tryToResolveHostName(const QString &hostName)
+{
+ const auto hostInfo = QHostInfo::fromName(hostName);
+ if (!hostInfo.addresses().empty())
+ return hostInfo.addresses().first().toString();
+
+ qWarning() << "Could not resolve the hostname"<< hostName;
+ return hostName;
+}
+#endif
+
+static QString getHostAddress(const QString &serverName)
+{
#if defined(COAP_TEST_SERVER_IP)
- return QStringLiteral(COAP_TEST_SERVER_IP);
+ Q_UNUSED(serverName);
+ return QStringLiteral(COAP_TEST_SERVER_IP);
+#elif defined(QT_TEST_SERVER_NAME)
+ QString hostname = serverName % "." % QString(QT_TEST_SERVER_DOMAIN);
+ return tryToResolveHostName(hostname);
+#elif defined(QT_TEST_SERVER)
+ Q_UNUSED(serverName);
+ QString hostname = "qt-test-server." % QString(QT_TEST_SERVER_DOMAIN);
+ return tryToResolveHostName(hostname);
#else
- static_assert(false, "COAP_TEST_SERVER_IP variable must be set");
+ Q_UNUSED(serverName);
+ qWarning("This test will fail, "
+ "please set the COAP_TEST_SERVER_IP variable to specify the CoAP server.");
+ return "";
#endif
- }
+}
+
+QString testServerHost()
+{
+ static QString testServerHostAddress = getHostAddress("californium");
+ return testServerHostAddress;
+}
- QString testServerUrl()
- {
- return QStringLiteral("coap://") + testServerHost() + QStringLiteral(":")
- + QString::number(QtCoap::DefaultPort);
+QString timeServerUrl()
+{
+ static QString timeServerHostAddress = getHostAddress("freecoap");
+ return QStringLiteral("coaps://") + timeServerHostAddress + QStringLiteral(":5685/time");
+}
+
+QString testServerUrl()
+{
+ return QStringLiteral("coap://") + testServerHost() + QStringLiteral(":")
+ + QString::number(QtCoap::DefaultPort);
+}
+
+QString testServerResource()
+{
+ return testServerHost() + QStringLiteral("/test");
+}
+
+QCoapSecurityConfiguration createConfiguration(QtCoap::SecurityMode securityMode)
+{
+ QCoapSecurityConfiguration configuration;
+
+ if (securityMode == QtCoap::SecurityMode::PreSharedKey) {
+ configuration.setPreSharedKeyIdentity("Client_identity");
+ configuration.setPreSharedKey("secretPSK");
+ } else if (securityMode == QtCoap::SecurityMode::Certificate) {
+ const QString directory = QFINDTESTDATA("testdata");
+ if (directory.isEmpty()) {
+ qWarning() << "Found no testdata/, cannot load certificates.";
+ return configuration;
+ }
+
+ const auto localCertPath = directory + QDir::separator() +"local_cert.pem";
+ const auto localCerts = QSslCertificate::fromPath(localCertPath);
+ if (localCerts.isEmpty()) {
+ qWarning() << "Failed to load local certificates, the"
+ << localCertPath
+ << "file was not found or it is not valid.";
+ } else {
+ configuration.setLocalCertificateChain(localCerts.toVector());
+ }
+
+ const auto caCertPath = directory + QDir::separator() + "ca_cert.pem";
+ const auto caCerts = QSslCertificate::fromPath(caCertPath);
+ if (caCerts.isEmpty()) {
+ qWarning() << "Failed to load CA certificates, the"
+ << caCertPath
+ << "file was not found or it is not valid.";
+ } else {
+ configuration.setCaCertificates(caCerts.toVector());
+ }
+
+ const auto privateKeyPath = directory + QDir::separator() + "privkey.pem";
+ QFile privateKey(privateKeyPath);
+ if (privateKey.open(QIODevice::ReadOnly)) {
+ QCoapPrivateKey key(privateKey.readAll(), QSsl::Ec);
+ if (key.isNull()) {
+ qWarning() << "Failed to set a private key, the key" << privateKeyPath
+ << "is not valid.";
+ } else {
+ configuration.setPrivateKey(key);
+ }
+ } else {
+ qWarning() << "Failed to read the private key" << privateKeyPath;
+ }
}
- QString testServerResource()
- {
- return testServerUrl() + QStringLiteral("/test");
+ return configuration;
+}
+
+bool waitForHost(const QUrl &url, QtCoap::SecurityMode security = QtCoap::SecurityMode::NoSecurity,
+ quint8 retries = 10)
+{
+ while (retries-- > 0) {
+ QCoapClient client(security);
+ if (security != QtCoap::SecurityMode::NoSecurity)
+ client.setSecurityConfiguration(createConfiguration(security));
+
+ QSignalSpy spyClientFinished(&client, SIGNAL(finished(QCoapReply *)));
+ client.get(url);
+
+ spyClientFinished.wait(1000);
+
+ if (spyClientFinished.count() == 1)
+ return true;
}
+ return false;
+}
+
}
diff --git a/tests/auto/coaptestserver.pri b/tests/auto/coaptestserver.pri
index 420aa89..2399826 100644
--- a/tests/auto/coaptestserver.pri
+++ b/tests/auto/coaptestserver.pri
@@ -1,7 +1,5 @@
COAP_TEST_SERVER_IP = $$(COAP_TEST_SERVER_IP)
-isEmpty( COAP_TEST_SERVER_IP ) {
- error(No IP set for CoAP plugtest server. Please set COAP_TEST_SERVER_IP environment variable to run this test.)
+!isEmpty(COAP_TEST_SERVER_IP) {
+ DEFINES += COAP_TEST_SERVER_IP=\\\"$${COAP_TEST_SERVER_IP}\\\"
+ message(CoAP plugtest server IP set to $$COAP_TEST_SERVER_IP)
}
-
-DEFINES += COAP_TEST_SERVER_IP=\\\"$${COAP_TEST_SERVER_IP}\\\"
-message(CoAP plugtest server IP set to $$COAP_TEST_SERVER_IP)
diff --git a/tests/auto/qcoapclient/qcoapclient.pro b/tests/auto/qcoapclient/qcoapclient.pro
index 5c7fe1e..59cfc4f 100644
--- a/tests/auto/qcoapclient/qcoapclient.pro
+++ b/tests/auto/qcoapclient/qcoapclient.pro
@@ -6,3 +6,6 @@ include(../coaptestserver.pri)
HEADERS += ../coapnetworksettings.h
SOURCES += tst_qcoapclient.cpp
+
+CONFIG += unsupported/testserver
+QT_TEST_SERVER_LIST = californium freecoap
diff --git a/tests/auto/qcoapclient/testdata/ca_cert.pem b/tests/auto/qcoapclient/testdata/ca_cert.pem
new file mode 100644
index 0000000..4f08468
--- /dev/null
+++ b/tests/auto/qcoapclient/testdata/ca_cert.pem
@@ -0,0 +1,12 @@
+-----BEGIN CERTIFICATE-----
+MIIBrTCCAVOgAwIBAgIMWBtPBCPy5ZOTqJ69MAoGCCqGSM49BAMCMDgxEzARBgNV
+BAMTCmR1bW15L3Jvb3QxETAPBgNVBAsTCFNvZnR3YXJlMQ4wDAYDVQQKEwVEdW1t
+eTAeFw0xNjExMDMxNDUxNDhaFw0yNjExMDExNDUxNDhaMDgxEzARBgNVBAMTCmR1
+bW15L3Jvb3QxETAPBgNVBAsTCFNvZnR3YXJlMQ4wDAYDVQQKEwVEdW1teTBZMBMG
+ByqGSM49AgEGCCqGSM49AwEHA0IABMJ6XvBwMC3sZRyBatX95w+/AieUhN1cNfWI
+Uc4HA0IXp+WwDN7QXd7mm1FDV/wQmNx/y2MJ7OQMJUvSozRPpfKjQzBBMA8GA1Ud
+EwEB/wQFMAMBAf8wDwYDVR0PAQH/BAUDAwcEADAdBgNVHQ4EFgQUsib+R29JtRKp
+G6duvLrDjM8PySUwCgYIKoZIzj0EAwIDSAAwRQIgQC6ZVZi6sXnYypt1CKlDSS2Q
+W+CV62TyOdE9j9phNfECIQDno7uEc8sXHnwkCcfuZhFVAkEfE8KBPhEF7ZmqJz5c
+BQ==
+-----END CERTIFICATE-----
diff --git a/tests/auto/qcoapclient/testdata/local_cert.pem b/tests/auto/qcoapclient/testdata/local_cert.pem
new file mode 100644
index 0000000..0f51a36
--- /dev/null
+++ b/tests/auto/qcoapclient/testdata/local_cert.pem
@@ -0,0 +1,13 @@
+-----BEGIN CERTIFICATE-----
+MIIB4jCCAYigAwIBAgIMWBtPBCa+RXwaSLCOMAoGCCqGSM49BAMCMDgxEzARBgNV
+BAMTCmR1bW15L3Jvb3QxETAPBgNVBAsTCFNvZnR3YXJlMQ4wDAYDVQQKEwVEdW1t
+eTAeFw0xNjExMDMxNDUxNDhaFw0yNjExMDExNDUxNDhaMDoxFTATBgNVBAMTDGR1
+bW15L2NsaWVudDERMA8GA1UECxMIU29mdHdhcmUxDjAMBgNVBAoTBUR1bW15MFkw
+EwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEcrIeFXrfmAu/fiT49ToNubG6u/w5GAQb
+xI0V7vmFCxFSn4ttoPfEUMlUKCTFYc6HbzmFgUjct1KFGBuAfZ13M6N2MHQwDAYD
+VR0TAQH/BAIwADATBgNVHSUEDDAKBggrBgEFBQcDAjAPBgNVHQ8BAf8EBQMDB4AA
+MB0GA1UdDgQWBBSc2q9vjtlLW1IUSzDa9X2qnJYU1DAfBgNVHSMEGDAWgBQEbS8W
+F1883YRUvUnCBnkSs/NhmjAKBggqhkjOPQQDAgNIADBFAiEA9fZYVTMn9My9erGO
+j5tFYsGj4A7CkWHFRj50KirZN4ECIFLEmt5SbmipqFdkiRBxkjkJEMYC+iLBeMqI
+Cgi5tYDF
+-----END CERTIFICATE-----
diff --git a/tests/auto/qcoapclient/testdata/privkey.pem b/tests/auto/qcoapclient/testdata/privkey.pem
new file mode 100644
index 0000000..75dee45
--- /dev/null
+++ b/tests/auto/qcoapclient/testdata/privkey.pem
@@ -0,0 +1,40 @@
+Public Key Info:
+ Public Key Algorithm: EC/ECDSA
+ Key Security Level: High (256 bits)
+
+curve: SECP256R1
+private key:
+ 00:f7:ea:23:23:78:12:38:e3:8f:e0:2b:05:72:2f:6a
+ 24:91:a0:54:c6:3d:ed:68:ce:6e:60:8d:0f:fd:9f:39
+ c6:
+
+x:
+ 72:b2:1e:15:7a:df:98:0b:bf:7e:24:f8:f5:3a:0d:b9
+ b1:ba:bb:fc:39:18:04:1b:c4:8d:15:ee:f9:85:0b:11
+
+
+y:
+ 52:9f:8b:6d:a0:f7:c4:50:c9:54:28:24:c5:61:ce:87
+ 6f:39:85:81:48:dc:b7:52:85:18:1b:80:7d:9d:77:33
+
+
+
+Public Key ID: 9C:DA:AF:6F:8E:D9:4B:5B:52:14:4B:30:DA:F5:7D:AA:9C:96:14:D4
+Public key's random art:
++--[SECP256R1]----+
+| o.=. |
+| o = +E. |
+| . . + . o|
+| . . . . ..|
+| S o . |
+| o + + |
+| . . o B |
+| *.= |
+| +=B. |
++-----------------+
+
+-----BEGIN EC PRIVATE KEY-----
+MHgCAQEEIQD36iMjeBI444/gKwVyL2okkaBUxj3taM5uYI0P/Z85xqAKBggqhkjO
+PQMBB6FEA0IABHKyHhV635gLv34k+PU6Dbmxurv8ORgEG8SNFe75hQsRUp+LbaD3
+xFDJVCgkxWHOh285hYFI3LdShRgbgH2ddzM=
+-----END EC PRIVATE KEY-----
diff --git a/tests/auto/qcoapclient/tst_qcoapclient.cpp b/tests/auto/qcoapclient/tst_qcoapclient.cpp
index 5ca695c..b7844b7 100644
--- a/tests/auto/qcoapclient/tst_qcoapclient.cpp
+++ b/tests/auto/qcoapclient/tst_qcoapclient.cpp
@@ -37,6 +37,7 @@
#include <QtCoap/qcoapresourcediscoveryreply.h>
#include <QtCore/qbuffer.h>
#include <QtNetwork/qnetworkdatagram.h>
+#include <QtNetwork/qsslcipher.h>
#include <private/qcoapclient_p.h>
#include <private/qcoapqudpconnection_p.h>
#include <private/qcoapprotocol_p.h>
@@ -51,6 +52,7 @@ class tst_QCoapClient : public QObject
Q_OBJECT
private Q_SLOTS:
+ void initTestCase();
void incorrectUrls_data();
void incorrectUrls();
void methods_data();
@@ -65,6 +67,7 @@ private Q_SLOTS:
void setBlockSize();
void requestWithQIODevice_data();
void requestWithQIODevice();
+ void multipleRequests_data();
void multipleRequests();
void blockwiseReply_data();
void blockwiseReply();
@@ -81,8 +84,51 @@ private Q_SLOTS:
void setMinimumTokenSize();
};
-#ifdef QT_BUILD_INTERNAL
+class QCoapClientForSecurityTests : public QCoapClient
+{
+public:
+ QCoapClientForSecurityTests(QtCoap::SecurityMode security)
+ : QCoapClient(security)
+ , securityMode(security)
+ {
+ if (security != QtCoap::SecurityMode::NoSecurity)
+ setSecurityConfiguration(createConfiguration(security));
+ }
+ bool securitySetupMissing() const
+ {
+#if QT_CONFIG(dtls)
+ if (securityMode == QtCoap::SecurityMode::PreSharedKey) {
+ // The Californium test server accepts only PSK-AES128-CCM8 and PSK-AES128-CBC-SHA256
+ // ciphers. Make sure that the required ciphers are present, otherwise the test
+ // should be skipped.
+ const auto ciphers = QSslConfiguration::defaultDtlsConfiguration().ciphers();
+ const auto it = std::find_if(ciphers.cbegin(), ciphers.cend(),
+ [](const QSslCipher &cipher) {
+ return cipher.name() == "PSK-AES128-CCM8"
+ || cipher.name() == "PSK-AES128-CBC-SHA256";
+ });
+ return it == ciphers.cend();
+ }
+ // For all other modes the setup should be OK, return false.
+ return false;
+#else
+ // If dtls is not configured, the setup for the secure modes is missing,
+ // but it is OK if security is not used.
+ return isSecure();
+#endif
+ }
+
+ bool isSecure() const
+ {
+ return securityMode != QtCoap::SecurityMode::NoSecurity;
+ }
+
+private:
+ QtCoap::SecurityMode securityMode;
+};
+
+#ifdef QT_BUILD_INTERNAL
class QCoapQUdpConnectionSocketTestsPrivate : public QCoapQUdpConnectionPrivate
{
bool bind() override
@@ -194,6 +240,17 @@ public slots:
}
};
+void tst_QCoapClient::initTestCase()
+{
+#if defined(COAP_TEST_SERVER_IP) || defined(QT_TEST_SERVER)
+ QVERIFY2(waitForHost(testServerHost()), "Failed to connect to Californium plugtest server.");
+#if QT_CONFIG(dtls)
+ QVERIFY2(waitForHost(timeServerUrl(), QtCoap::SecurityMode::Certificate),
+ "Failed to connect to FreeCoAP sample time server.");
+#endif
+#endif
+}
+
void tst_QCoapClient::incorrectUrls_data()
{
QWARN("Expect warnings here...");
@@ -235,32 +292,73 @@ void tst_QCoapClient::methods_data()
{
QTest::addColumn<QUrl>("url");
QTest::addColumn<QtCoap::Method>("method");
-
- QTest::newRow("get") << QUrl(testServerResource()) << QtCoap::Method::Get;
- QTest::newRow("get_no_port") << QUrl("coap://" + testServerHost() + "/test")
- << QtCoap::Method::Get;
- QTest::newRow("get_no_scheme_no_port") << QUrl(testServerHost() + "/test")
- << QtCoap::Method::Get;
- QTest::newRow("post") << QUrl(testServerResource())
- << QtCoap::Method::Post;
- QTest::newRow("post_no_scheme_no_port") << QUrl(testServerHost() + "/test")
- << QtCoap::Method::Post;
- QTest::newRow("put") << QUrl(testServerResource())
- << QtCoap::Method::Put;
- QTest::newRow("put_no_scheme_no_port") << QUrl(testServerHost() + "/test")
- << QtCoap::Method::Put;
- QTest::newRow("delete") << QUrl(testServerResource())
- << QtCoap::Method::Delete;
- QTest::newRow("delete_no_scheme_no_port") << QUrl(testServerHost() + "/test")
- << QtCoap::Method::Delete;
+ QTest::addColumn<QtCoap::SecurityMode>("security");
+
+ QTest::newRow("get")
+ << QUrl(testServerResource())
+ << QtCoap::Method::Get
+ << QtCoap::SecurityMode::NoSecurity;
+ QTest::newRow("get_no_port")
+ << QUrl("coap://" + testServerHost() + "/test")
+ << QtCoap::Method::Get
+ << QtCoap::SecurityMode::NoSecurity;
+ QTest::newRow("get_no_scheme_no_port")
+ << QUrl(testServerHost() + "/test")
+ << QtCoap::Method::Get
+ << QtCoap::SecurityMode::NoSecurity;
+ QTest::newRow("get_psk")
+ << QUrl(testServerResource()) << QtCoap::Method::Get
+ << QtCoap::SecurityMode::PreSharedKey;
+ QTest::newRow("get_cert")
+ << QUrl(timeServerUrl()) << QtCoap::Method::Get
+ << QtCoap::SecurityMode::Certificate;
+ QTest::newRow("post")
+ << QUrl(testServerResource())
+ << QtCoap::Method::Post
+ << QtCoap::SecurityMode::NoSecurity;
+ QTest::newRow("post_no_scheme_no_port")
+ << QUrl(testServerHost() + "/test")
+ << QtCoap::Method::Post
+ << QtCoap::SecurityMode::NoSecurity;
+ QTest::newRow("post_psk")
+ << QUrl(testServerResource()) << QtCoap::Method::Post
+ << QtCoap::SecurityMode::PreSharedKey;
+ QTest::newRow("put")
+ << QUrl(testServerResource())
+ << QtCoap::Method::Put
+ << QtCoap::SecurityMode::NoSecurity;
+ QTest::newRow("put_no_scheme_no_port")
+ << QUrl(testServerHost() + "/test")
+ << QtCoap::Method::Put
+ << QtCoap::SecurityMode::NoSecurity;
+ QTest::newRow("put_psk")
+ << QUrl(testServerResource()) << QtCoap::Method::Put
+ << QtCoap::SecurityMode::PreSharedKey;
+ QTest::newRow("delete")
+ << QUrl(testServerResource())
+ << QtCoap::Method::Delete
+ << QtCoap::SecurityMode::NoSecurity;
+ QTest::newRow("delete_no_scheme_no_port")
+ << QUrl(testServerHost() + "/test")
+ << QtCoap::Method::Delete
+ << QtCoap::SecurityMode::NoSecurity;
+ QTest::newRow("delete_psk")
+ << QUrl(testServerResource()) << QtCoap::Method::Delete
+ << QtCoap::SecurityMode::PreSharedKey;
}
void tst_QCoapClient::methods()
{
+ CHECK_FOR_COAP_SERVER;
+
QFETCH(QUrl, url);
QFETCH(QtCoap::Method, method);
+ QFETCH(QtCoap::SecurityMode, security);
+
+ QCoapClientForSecurityTests client(security);
+ if (client.securitySetupMissing())
+ QSKIP("Skipping this test, security is not configured properly");
- QCoapClient client;
QCoapRequest request(url);
QSignalSpy spyClientFinished(&client, SIGNAL(finished(QCoapReply *)));
@@ -281,7 +379,8 @@ void tst_QCoapClient::methods()
QVERIFY2(!reply.isNull(), "Request failed unexpectedly");
#ifdef QT_BUILD_INTERNAL
- QCOMPARE(reply->url(), QCoapRequestPrivate::adjustedUrl(url, false));
+ QCOMPARE(reply->url(),
+ QCoapRequestPrivate::adjustedUrl(url, client.isSecure()));
#endif
QSignalSpy spyReplyFinished(reply.data(), SIGNAL(finished(QCoapReply *)));
QTRY_COMPARE(spyReplyFinished.count(), 1);
@@ -312,6 +411,8 @@ void tst_QCoapClient::methods()
void tst_QCoapClient::separateMethod()
{
+ CHECK_FOR_COAP_SERVER;
+
QCoapClient client;
QScopedPointer<QCoapReply> reply(client.get(QUrl(testServerUrl() + "/separate")));
@@ -327,6 +428,8 @@ void tst_QCoapClient::separateMethod()
void tst_QCoapClient::removeReply()
{
+ CHECK_FOR_COAP_SERVER;
+
QCoapClient client;
QCoapReply *reply = client.get(QUrl(testServerResource()));
QVERIFY2(reply != nullptr, "Request failed unexpectedly");
@@ -384,6 +487,8 @@ void tst_QCoapClient::requestWithQIODevice_data()
void tst_QCoapClient::requestWithQIODevice()
{
+ CHECK_FOR_COAP_SERVER;
+
QFETCH(QUrl, url);
QCoapClient client;
@@ -414,54 +519,63 @@ void tst_QCoapClient::requestWithQIODevice()
}
}
+void tst_QCoapClient::multipleRequests_data()
+{
+ QTest::addColumn<QtCoap::SecurityMode>("security");
+
+ QTest::newRow("multiple_requests") << QtCoap::SecurityMode::NoSecurity;
+ QTest::newRow("multiple_requests_secure") << QtCoap::SecurityMode::PreSharedKey;
+}
+
void tst_QCoapClient::multipleRequests()
{
- QCoapClient client;
+ CHECK_FOR_COAP_SERVER;
+
+ QFETCH(QtCoap::SecurityMode, security);
+
+ QCoapClientForSecurityTests client(security);
+ if (client.securitySetupMissing())
+ QSKIP("Skipping this test, security is not configured properly");
+
QUrl url = QUrl(testServerResource());
QSignalSpy spyClientFinished(&client, SIGNAL(finished(QCoapReply *)));
- QScopedPointer<QCoapReply> replyGet1(client.get(url));
- QScopedPointer<QCoapReply> replyGet2(client.get(url));
- QScopedPointer<QCoapReply> replyGet3(client.get(url));
- QScopedPointer<QCoapReply> replyGet4(client.get(url));
-
- QVERIFY2(!replyGet1.isNull(), "Request failed unexpectedly");
- QVERIFY2(!replyGet2.isNull(), "Request failed unexpectedly");
- QVERIFY2(!replyGet3.isNull(), "Request failed unexpectedly");
- QVERIFY2(!replyGet4.isNull(), "Request failed unexpectedly");
-
- QSignalSpy spyReplyGet1Finished(replyGet1.data(), SIGNAL(finished(QCoapReply *)));
- QSignalSpy spyReplyGet2Finished(replyGet2.data(), SIGNAL(finished(QCoapReply *)));
- QSignalSpy spyReplyGet3Finished(replyGet3.data(), SIGNAL(finished(QCoapReply *)));
- QSignalSpy spyReplyGet4Finished(replyGet4.data(), SIGNAL(finished(QCoapReply *)));
-
- QTRY_COMPARE(spyReplyGet1Finished.count(), 1);
- QTRY_COMPARE(spyReplyGet2Finished.count(), 1);
- QTRY_COMPARE(spyReplyGet3Finished.count(), 1);
- QTRY_COMPARE(spyReplyGet4Finished.count(), 1);
+ const uint8_t requestCount = 4;
+ QVector<QSharedPointer<QCoapReply>> replies;
+ QVector<QSharedPointer<QSignalSpy>> signalSpies;
+ for (uint8_t i = 0; i < requestCount; ++i) {
+ QCoapRequest request;
+ const auto token = "token" + QByteArray::number(i);
+ request.setToken(token);
+ request.setUrl(url);
+
+ QSharedPointer<QCoapReply> reply(client.get(request));
+ const auto errorMsg = QStringLiteral("Request number %1 failed unexpectedly").arg(i);
+ QVERIFY2(!reply.isNull(), qPrintable(errorMsg));
+ replies.push_back(reply);
+
+ QSharedPointer<QSignalSpy> signalSpy(
+ new QSignalSpy(reply.data(), SIGNAL(finished(QCoapReply *))));
+ signalSpies.push_back(signalSpy);
+ }
+
+ for (const auto &signalSpy : signalSpies)
+ QTRY_COMPARE(signalSpy->count(), 1);
QTRY_COMPARE(spyClientFinished.count(), 4);
- QByteArray replyData1 = replyGet1->readAll();
- QByteArray replyData2 = replyGet2->readAll();
- QByteArray replyData3 = replyGet3->readAll();
- QByteArray replyData4 = replyGet4->readAll();
-
- QCOMPARE(replyGet1->responseCode(), QtCoap::ResponseCode::Content);
- QCOMPARE(replyGet2->responseCode(), QtCoap::ResponseCode::Content);
- QCOMPARE(replyGet3->responseCode(), QtCoap::ResponseCode::Content);
- QCOMPARE(replyGet4->responseCode(), QtCoap::ResponseCode::Content);
-
- QVERIFY(replyData1 != replyData2);
- QVERIFY(replyData1 != replyData3);
- QVERIFY(replyData1 != replyData4);
- QVERIFY(replyData2 != replyData3);
- QVERIFY(replyData2 != replyData4);
- QVERIFY(replyData3 != replyData4);
+ for (uint8_t i = 0; i < requestCount; ++i) {
+ QCOMPARE(replies[i]->responseCode(), QtCoap::ResponseCode::Content);
+ QByteArray replyData = replies[i]->readAll();
+ const auto token = "token" + QByteArray::number(i);
+ QVERIFY(replyData.contains(token.toHex()));
+ }
}
void tst_QCoapClient::socketError()
{
#ifdef QT_BUILD_INTERNAL
+ CHECK_FOR_COAP_SERVER;
+
QCoapClientForSocketErrorTests client;
QUrl url = QUrl(testServerResource());
@@ -494,6 +608,8 @@ void tst_QCoapClient::timeout_data()
void tst_QCoapClient::timeout()
{
#ifdef QT_BUILD_INTERNAL
+ CHECK_FOR_COAP_SERVER;
+
QFETCH(uint, timeout);
QFETCH(uint, maximumRetransmitCount);
@@ -542,10 +658,13 @@ void tst_QCoapClient::timeout()
void tst_QCoapClient::abort()
{
+ CHECK_FOR_COAP_SERVER;
+
QCoapClient client;
QUrl url = QUrl(testServerUrl() + "/large");
QScopedPointer<QCoapReply> reply(client.get(url));
+ QVERIFY(!reply.isNull());
QSignalSpy spyReplyFinished(reply.data(), &QCoapReply::finished);
QSignalSpy spyReplyAborted(reply.data(), &QCoapReply::aborted);
QSignalSpy spyReplyError(reply.data(), &QCoapReply::error);
@@ -566,6 +685,7 @@ void tst_QCoapClient::blockwiseReply_data()
QTest::addColumn<QUrl>("url");
QTest::addColumn<QCoapMessage::Type>("type");
QTest::addColumn<QByteArray>("replyData");
+ QTest::addColumn<QtCoap::SecurityMode>("security");
QByteArray data;
data.append("/-------------------------------------------------------------\\\n");
@@ -592,36 +712,58 @@ void tst_QCoapClient::blockwiseReply_data()
QTest::newRow("get_large")
<< QUrl(testServerUrl() + "/large")
<< QCoapMessage::Type::NonConfirmable
- << data;
+ << data
+ << QtCoap::SecurityMode::NoSecurity;
+ QTest::newRow("get_large_secure")
+ << QUrl(testServerHost() + "/large")
+ << QCoapMessage::Type::NonConfirmable
+ << data
+ << QtCoap::SecurityMode::PreSharedKey;
QTest::newRow("get_large_separate")
- << QUrl(testServerUrl() + "/large-separate")
+ << QUrl(testServerHost() + "/large-separate")
<< QCoapMessage::Type::NonConfirmable
- << data;
+ << data
+ << QtCoap::SecurityMode::NoSecurity;
QTest::newRow("get_large_confirmable")
<< QUrl(testServerUrl() + "/large")
<< QCoapMessage::Type::Confirmable
- << data;
+ << data
+ << QtCoap::SecurityMode::NoSecurity;
+ QTest::newRow("get_large_confirmable_secure")
+ << QUrl(testServerHost() + "/large")
+ << QCoapMessage::Type::Confirmable
+ << data
+ << QtCoap::SecurityMode::PreSharedKey;
QTest::newRow("get_large_separate_confirmable")
<< QUrl(testServerUrl() + "/large-separate")
<< QCoapMessage::Type::Confirmable
- << data;
+ << data
+ << QtCoap::SecurityMode::NoSecurity;
QTest::newRow("get_large_16bits")
<< QUrl(testServerUrl() + "/large")
<< QCoapMessage::Type::NonConfirmable
- << data;
+ << data
+ << QtCoap::SecurityMode::NoSecurity;
QTest::newRow("get_large_16bits_confirmable")
<< QUrl(testServerUrl() + "/large")
<< QCoapMessage::Type::Confirmable
- << data;
+ << data
+ << QtCoap::SecurityMode::NoSecurity;
}
void tst_QCoapClient::blockwiseReply()
{
+ CHECK_FOR_COAP_SERVER;
+
QFETCH(QUrl, url);
QFETCH(QCoapMessage::Type, type);
QFETCH(QByteArray, replyData);
+ QFETCH(QtCoap::SecurityMode, security);
+
+ QCoapClientForSecurityTests client(security);
+ if (client.securitySetupMissing())
+ QSKIP("Skipping this test, security is not configured properly");
- QCoapClient client;
QCoapRequest request(url);
if (qstrncmp(QTest::currentDataTag(), "get_large_16bits", 16) == 0)
@@ -629,6 +771,7 @@ void tst_QCoapClient::blockwiseReply()
request.setType(type);
QScopedPointer<QCoapReply> reply(client.get(request));
+ QVERIFY(!reply.isNull());
QSignalSpy spyReplyFinished(reply.data(), &QCoapReply::finished);
QSignalSpy spyReplyError(reply.data(), &QCoapReply::error);
Helper helper;
@@ -646,33 +789,51 @@ void tst_QCoapClient::blockwiseRequest_data()
QTest::addColumn<QByteArray>("requestData");
QTest::addColumn<QtCoap::ResponseCode>("responseCode");
QTest::addColumn<QByteArray>("replyData");
+ QTest::addColumn<QtCoap::SecurityMode>("security");
QByteArray data;
const char alphabet[] = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz";
for (int i = 3; i-- > 0; )
data.append(alphabet);
- QTest::newRow("large_post_empty_reply") << QUrl(testServerUrl() + "/query")
- << QCoapMessage::Type::NonConfirmable
- << data
- << QtCoap::ResponseCode::MethodNotAllowed
- << QByteArray();
- QTest::newRow("large_post_large_reply") << QUrl(testServerUrl() + "/large-post")
- << QCoapMessage::Type::NonConfirmable
- << data
- << QtCoap::ResponseCode::Changed
- << data.toUpper();
+ QTest::newRow("large_post_empty_reply")
+ << QUrl(testServerUrl() + "/query")
+ << QCoapMessage::Type::NonConfirmable
+ << data
+ << QtCoap::ResponseCode::MethodNotAllowed
+ << QByteArray()
+ << QtCoap::SecurityMode::NoSecurity;
+ QTest::newRow("large_post_large_reply")
+ << QUrl(testServerUrl() + "/large-post")
+ << QCoapMessage::Type::NonConfirmable
+ << data
+ << QtCoap::ResponseCode::Changed
+ << data.toUpper()
+ << QtCoap::SecurityMode::NoSecurity;
+ QTest::newRow("large_post_large_reply_secure")
+ << QUrl(testServerHost() + "/large-post")
+ << QCoapMessage::Type::NonConfirmable
+ << data
+ << QtCoap::ResponseCode::Changed
+ << data.toUpper()
+ << QtCoap::SecurityMode::PreSharedKey;
}
void tst_QCoapClient::blockwiseRequest()
{
+ CHECK_FOR_COAP_SERVER;
+
QFETCH(QUrl, url);
QFETCH(QCoapMessage::Type, type);
QFETCH(QByteArray, requestData);
QFETCH(QtCoap::ResponseCode, responseCode);
QFETCH(QByteArray, replyData);
+ QFETCH(QtCoap::SecurityMode, security);
+
+ QCoapClientForSecurityTests client(security);
+ if (client.securitySetupMissing())
+ QSKIP("Skipping this test, security is not configured properly");
- QCoapClient client;
client.setBlockSize(16);
QCoapRequest request(url);
@@ -680,6 +841,7 @@ void tst_QCoapClient::blockwiseRequest()
request.addOption(QCoapOption::ContentFormat);
QScopedPointer<QCoapReply> reply(client.post(request, requestData));
+ QVERIFY(!reply.isNull());
QSignalSpy spyReplyFinished(reply.data(), SIGNAL(finished(QCoapReply *)));
QTRY_COMPARE_WITH_TIMEOUT(spyReplyFinished.count(), 1, 30000);
@@ -693,29 +855,41 @@ void tst_QCoapClient::discover_data()
{
QTest::addColumn<QUrl>("url");
QTest::addColumn<int>("resourceNumber");
+ QTest::addColumn<QtCoap::SecurityMode>("security");
// Californium test server exposes 29 resources
- QTest::newRow("discover") << QUrl(testServerUrl())
- << 29;
- QTest::newRow("discover_no_scheme_no_port") << QUrl(testServerHost())
- << 29;
+ QTest::newRow("discover")
+ << QUrl(testServerHost())
+ << 29
+ << QtCoap::SecurityMode::NoSecurity;
+ QTest::newRow("discover_secure")
+ << QUrl(testServerHost())
+ << 29
+ << QtCoap::SecurityMode::PreSharedKey;
}
void tst_QCoapClient::discover()
{
+ CHECK_FOR_COAP_SERVER;
+
QFETCH(QUrl, url);
QFETCH(int, resourceNumber);
+ QFETCH(QtCoap::SecurityMode, security);
- QCoapClient client;
+ QCoapClientForSecurityTests client(security);
+ if (client.securitySetupMissing())
+ QSKIP("Skipping this test, security is not configured properly");
QScopedPointer<QCoapResourceDiscoveryReply> resourcesReply(client.discover(url)); // /.well-known/core
+ QVERIFY(!resourcesReply.isNull());
QSignalSpy spyReplyFinished(resourcesReply.data(), SIGNAL(finished(QCoapReply *)));
QTRY_COMPARE_WITH_TIMEOUT(spyReplyFinished.count(), 1, 30000);
const auto discoverUrl = QUrl(url.toString() + "/.well-known/core");
#ifdef QT_BUILD_INTERNAL
- QCOMPARE(resourcesReply->url(), QCoapRequestPrivate::adjustedUrl(discoverUrl, false));
+ QCOMPARE(resourcesReply->url(),
+ QCoapRequestPrivate::adjustedUrl(discoverUrl, client.isSecure()));
#endif
QCOMPARE(resourcesReply->resources().length(), resourceNumber);
QCOMPARE(resourcesReply->request().method(), QtCoap::Method::Get);
@@ -728,62 +902,90 @@ void tst_QCoapClient::observe_data()
QWARN("Observe tests may take some time, don't forget to raise Tests timeout in settings.");
QTest::addColumn<QUrl>("url");
QTest::addColumn<QCoapMessage::Type>("type");
+ QTest::addColumn<QtCoap::SecurityMode>("security");
QTest::newRow("observe")
<< QUrl(testServerUrl() + "/obs")
- << QCoapMessage::Type::NonConfirmable;
+ << QCoapMessage::Type::NonConfirmable
+ << QtCoap::SecurityMode::NoSecurity;
+
+ QTest::newRow("observe_secure")
+ << QUrl(testServerHost() + "/obs")
+ << QCoapMessage::Type::NonConfirmable
+ << QtCoap::SecurityMode::PreSharedKey;
QTest::newRow("observe_no_scheme_no_port")
<< QUrl(testServerHost() + "/obs")
- << QCoapMessage::Type::NonConfirmable;
+ << QCoapMessage::Type::NonConfirmable
+ << QtCoap::SecurityMode::NoSecurity;
QTest::newRow("observe_confirmable")
<< QUrl(testServerUrl() + "/obs")
- << QCoapMessage::Type::Confirmable;
+ << QCoapMessage::Type::Confirmable
+ << QtCoap::SecurityMode::NoSecurity;
QTest::newRow("observe_receive")
<< QUrl(testServerUrl() + "/obs-non")
- << QCoapMessage::Type::NonConfirmable;
+ << QCoapMessage::Type::NonConfirmable
+ << QtCoap::SecurityMode::NoSecurity;
QTest::newRow("observe_receive_confirmable")
<< QUrl(testServerUrl() + "/obs-non")
- << QCoapMessage::Type::Confirmable;
+ << QCoapMessage::Type::Confirmable
+ << QtCoap::SecurityMode::NoSecurity;
QTest::newRow("observe_large")
<< QUrl(testServerUrl() + "/obs-large")
- << QCoapMessage::Type::NonConfirmable;
+ << QCoapMessage::Type::NonConfirmable
+ << QtCoap::SecurityMode::NoSecurity;
+
+ QTest::newRow("observe_large_secure")
+ << QUrl(testServerHost() + "/obs-large")
+ << QCoapMessage::Type::NonConfirmable
+ << QtCoap::SecurityMode::PreSharedKey;
QTest::newRow("observe_large_confirmable")
<< QUrl(testServerUrl() + "/obs-large")
- << QCoapMessage::Type::Confirmable;
+ << QCoapMessage::Type::Confirmable
+ << QtCoap::SecurityMode::NoSecurity;
QTest::newRow("observe_pumping")
<< QUrl(testServerUrl() + "/obs-pumping")
- << QCoapMessage::Type::NonConfirmable;
+ << QCoapMessage::Type::NonConfirmable
+ << QtCoap::SecurityMode::NoSecurity;
QTest::newRow("observe_pumping_confirmable")
<< QUrl(testServerUrl() + "/obs-pumping")
- << QCoapMessage::Type::Confirmable;
+ << QCoapMessage::Type::Confirmable
+ << QtCoap::SecurityMode::NoSecurity;
}
void tst_QCoapClient::observe()
{
+ CHECK_FOR_COAP_SERVER;
+
QFETCH(QUrl, url);
QFETCH(QCoapMessage::Type, type);
+ QFETCH(QtCoap::SecurityMode, security);
+
+ QCoapClientForSecurityTests client(security);
+ if (client.securitySetupMissing())
+ QSKIP("Skipping this test, security is not configured properly");
- QCoapClient client;
QCoapRequest request(url);
request.setType(type);
QSharedPointer<QCoapReply> reply(client.observe(request),
&QObject::deleteLater);
+ QVERIFY(!reply.isNull());
QSignalSpy spyReplyNotified(reply.data(), &QCoapReply::notified);
QSignalSpy spyReplyFinished(reply.data(), &QCoapReply::finished);
QTRY_COMPARE_WITH_TIMEOUT(spyReplyNotified.count(), 3, 30000);
client.cancelObserve(reply.data());
#ifdef QT_BUILD_INTERNAL
- QCOMPARE(reply->url(), QCoapRequestPrivate::adjustedUrl(url, false));
+ QCOMPARE(reply->url(),
+ QCoapRequestPrivate::adjustedUrl(url, client.isSecure()));
#endif
QCOMPARE(reply->request().method(), QtCoap::Method::Get);
@@ -907,7 +1109,7 @@ void tst_QCoapClient::setMinimumTokenSize()
QScopedPointer<QCoapReply> reply;
reply.reset(client.get(QCoapRequest("127.0.0.1")));
- QTRY_COMPARE_WITH_TIMEOUT(spyClientError.count(), 1, 10);
+ QTRY_COMPARE_WITH_TIMEOUT(spyClientError.count(), 1, 100);
QVERIFY(reply->request().tokenLength() >= expectedMinSize);
QVERIFY(reply->request().tokenLength() <= maxSize);
}
diff --git a/tests/auto/qcoapqudpconnection/qcoapqudpconnection.pro b/tests/auto/qcoapqudpconnection/qcoapqudpconnection.pro
index c10d6e6..984d62d 100644
--- a/tests/auto/qcoapqudpconnection/qcoapqudpconnection.pro
+++ b/tests/auto/qcoapqudpconnection/qcoapqudpconnection.pro
@@ -7,3 +7,6 @@ HEADERS += ../coapnetworksettings.h
SOURCES += \
tst_qcoapqudpconnection.cpp
+
+CONFIG += unsupported/testserver
+QT_TEST_SERVER_LIST = californium
diff --git a/tests/auto/qcoapqudpconnection/tst_qcoapqudpconnection.cpp b/tests/auto/qcoapqudpconnection/tst_qcoapqudpconnection.cpp
index 8876214..fb30392 100644
--- a/tests/auto/qcoapqudpconnection/tst_qcoapqudpconnection.cpp
+++ b/tests/auto/qcoapqudpconnection/tst_qcoapqudpconnection.cpp
@@ -50,6 +50,7 @@ class tst_QCoapQUdpConnection : public QObject
Q_OBJECT
private Q_SLOTS:
+ void initTestCase();
void ctor();
void connectToHost();
void reconnect();
@@ -72,6 +73,13 @@ public:
}
};
+void tst_QCoapQUdpConnection::initTestCase()
+{
+#if defined(COAP_TEST_SERVER_IP) || defined(QT_TEST_SERVER)
+ QVERIFY2(waitForHost(testServerHost()), "Failed to connect to Californium plugtest server.");
+#endif
+}
+
void tst_QCoapQUdpConnection::ctor()
{
QCoapQUdpConnection connection;
@@ -166,6 +174,8 @@ void tst_QCoapQUdpConnection::sendRequest_data()
void tst_QCoapQUdpConnection::sendRequest()
{
+ CHECK_FOR_COAP_SERVER;
+
QFETCH(QString, protocol);
QFETCH(QString, host);
QFETCH(QString, path);
diff --git a/tests/testserver/californium/californium.sh b/tests/testserver/californium/californium.sh
new file mode 100755
index 0000000..82072e4
--- /dev/null
+++ b/tests/testserver/californium/californium.sh
@@ -0,0 +1,34 @@
+#!/usr/bin/env bash
+
+#############################################################################
+##
+## Copyright (C) 2019 The Qt Company Ltd.
+## Contact: https://www.qt.io/licensing/
+##
+## This file is part of the test suite of the Qt Toolkit.
+##
+## $QT_BEGIN_LICENSE:GPL$
+## 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 https://www.qt.io/terms-conditions. For further
+## information use the contact form at https://www.qt.io/contact-us.
+##
+## GNU General Public License Usage
+## Alternatively, this file may be used under the terms of the GNU
+## General Public License version 3 or (at your option) any later version
+## approved by the KDE Free Qt Foundation. The licenses are as published by
+## the Free Software Foundation and appearing in the file LICENSE.GPL3
+## included in the packaging of this file. Please review the following
+## information to ensure the GNU General Public License requirements will
+## be met: https://www.gnu.org/licenses/gpl-3.0.html.
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+set -ex
+
+java -jar /root/src/californium/demo-apps/run/cf-plugtest-server-1.1.0-SNAPSHOT.jar &
diff --git a/tests/testserver/docker-compose.yml b/tests/testserver/docker-compose.yml
new file mode 100644
index 0000000..44ca4ae
--- /dev/null
+++ b/tests/testserver/docker-compose.yml
@@ -0,0 +1,44 @@
+version: '2.1'
+
+# The tag of images is used by docker compose file to launch the corresponding
+# docker containers. The value of tag comes from the provisioning script
+# (coin/provisioning/.../testserver/docker_testserver.sh). The script gets SHA-1
+# of each server context as the tag of docker images. If one of the server
+# contexts gets changes, please make sure to update this compose file as well.
+# You can run command 'docker images' to list all the tag of test server images.
+# For example:
+# REPOSITORY TAG
+# qt-test-server-apache2 537fe302f61851d1663f41495230d8e3554a4a13
+
+services:
+ californium:
+ extends:
+ file: ${SHARED_DATA}/docker-compose-common.yml
+ service: ${SHARED_SERVICE}
+ container_name: qt-test-server-californium
+ hostname: ${HOST_NAME:-californium}
+ build:
+ context: .
+ args:
+ provisioningImage: qt-test-server-californium:dd4a2ab0b113fbd45c5d6e92b43341f5eb521926
+ serviceDir: ./californium
+ ports:
+ - "5683:5683/udp"
+ - "5684:5684/udp"
+ entrypoint: ./startup.sh
+ command: service/californium.sh
+ freecoap:
+ extends:
+ file: ${SHARED_DATA}/docker-compose-common.yml
+ service: ${SHARED_SERVICE}
+ container_name: qt-test-server-freecoap
+ hostname: ${HOST_NAME:-freecoap}
+ build:
+ context: .
+ args:
+ provisioningImage: qt-test-server-freecoap:e2d7208ea82e623e2769d9ee1f052b58537d2f35
+ serviceDir: ./freecoap
+ ports:
+ - "5685:5685/udp"
+ entrypoint: ./startup.sh
+ command: service/freecoap.sh
diff --git a/tests/testserver/freecoap/freecoap.sh b/tests/testserver/freecoap/freecoap.sh
new file mode 100755
index 0000000..4b15df6
--- /dev/null
+++ b/tests/testserver/freecoap/freecoap.sh
@@ -0,0 +1,34 @@
+#!/usr/bin/env bash
+
+#############################################################################
+##
+## Copyright (C) 2019 The Qt Company Ltd.
+## Contact: https://www.qt.io/licensing/
+##
+## This file is part of the test suite of the Qt Toolkit.
+##
+## $QT_BEGIN_LICENSE:GPL$
+## 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 https://www.qt.io/terms-conditions. For further
+## information use the contact form at https://www.qt.io/contact-us.
+##
+## GNU General Public License Usage
+## Alternatively, this file may be used under the terms of the GNU
+## General Public License version 3 or (at your option) any later version
+## approved by the KDE Free Qt Foundation. The licenses are as published by
+## the Free Software Foundation and appearing in the file LICENSE.GPL3
+## included in the packaging of this file. Please review the following
+## information to ensure the GNU General Public License requirements will
+## be met: https://www.gnu.org/licenses/gpl-3.0.html.
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+set -ex
+
+(cd /root/src/FreeCoAP/sample/time_server && ./time_server 0.0.0.0 5685 &)