diff --git a/libnymea/libnymea.pro b/libnymea/libnymea.pro
index 41752d75..2c9ea6c0 100644
--- a/libnymea/libnymea.pro
+++ b/libnymea/libnymea.pro
@@ -42,6 +42,8 @@ HEADERS += \
network/apikeys/apikey.h \
network/apikeys/apikeysprovider.h \
network/apikeys/apikeystorage.h \
+ network/ping.h \
+ network/pingreply.h \
platform/package.h \
platform/repository.h \
types/browseritem.h \
@@ -140,6 +142,8 @@ SOURCES += \
network/apikeys/apikey.cpp \
network/apikeys/apikeysprovider.cpp \
network/apikeys/apikeystorage.cpp \
+ network/ping.cpp \
+ network/pingreply.cpp \
nymeasettings.cpp \
platform/package.cpp \
platform/repository.cpp \
diff --git a/libnymea/network/ping.cpp b/libnymea/network/ping.cpp
new file mode 100644
index 00000000..35aa04f8
--- /dev/null
+++ b/libnymea/network/ping.cpp
@@ -0,0 +1,434 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2021, nymea GmbH
+* Contact: contact@nymea.io
+*
+* This file is part of nymea.
+* This project including source code and documentation is protected by
+* copyright law, and remains the property of nymea GmbH. All rights, including
+* reproduction, publication, editing and translation, are reserved. The use of
+* this project is subject to the terms of a license agreement to be concluded
+* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
+* under https://nymea.io/license
+*
+* GNU Lesser General Public License Usage
+* Alternatively, this project may be redistributed and/or modified under the
+* terms of the GNU Lesser General Public License as published by the Free
+* Software Foundation; version 3. This project is distributed in the hope that
+* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this project. If not, see .
+*
+* For any further details and any questions please contact us under
+* contact@nymea.io or see our FAQ/Licensing Information on
+* https://nymea.io/license/faq
+*
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#include "ping.h"
+#include "loggingcategories.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+NYMEA_LOGGING_CATEGORY(dcPing, "Ping")
+NYMEA_LOGGING_CATEGORY(dcPingTraffic, "PingTraffic")
+
+Ping::Ping(QObject *parent) : QObject(parent)
+{
+ // Build socket descriptor
+ m_socketDescriptor = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
+ if (m_socketDescriptor < 0) {
+ qCWarning(dcPing()) << "Failed to create the ICMP socket." << strerror(errno);
+ verifyErrno(errno);
+ return;
+ }
+
+ // Set time to live value
+ const int val = ICMP_TTL_VALUE;
+ if (setsockopt(m_socketDescriptor, SOL_IP, IP_TTL, &val, sizeof(val)) != 0) {
+ verifyErrno(errno);
+ qCWarning(dcPing()) << "Failed to set the ICMP socket TTL option:" << strerror(errno);
+ cleanUpSocket();
+ return;
+ }
+
+ // Configure non blocking
+ if (fcntl(m_socketDescriptor, F_SETFL, fcntl(m_socketDescriptor, F_GETFL, 0) | O_NONBLOCK) != 0) {
+ verifyErrno(errno);
+ qCWarning(dcPing()) << "Failed to set the ICMP socket function control to non-blocking" << strerror(errno);
+ cleanUpSocket();
+ return;
+ }
+
+ // Create the socket notifier for read notification
+ m_socketNotifier = new QSocketNotifier(m_socketDescriptor, QSocketNotifier::Read, this);
+ connect(m_socketNotifier, &QSocketNotifier::activated, this, &Ping::onSocketReadyRead);
+
+ m_queueTimer = new QTimer(this);
+ m_queueTimer->setInterval(20);
+ m_queueTimer->setSingleShot(true);
+ connect(m_queueTimer, &QTimer::timeout, this, [=](){
+ sendNextReply();
+ });
+
+ m_socketNotifier->setEnabled(true);
+ m_available = true;
+ qCDebug(dcPing()) << "ICMP socket set up successfully (Socket ID:" << m_socketDescriptor << ")";
+}
+
+QByteArray Ping::payload() const
+{
+ return m_payload;
+}
+
+void Ping::setPayload(const QByteArray &payload)
+{
+ Q_ASSERT_X(static_cast(payload.count()) <= ICMP_PAYLOAD_SIZE, "ping", QString("maximal payload size is %1").arg(ICMP_PAYLOAD_SIZE).toLocal8Bit());
+ m_payload = payload;
+}
+
+bool Ping::available() const
+{
+ return m_available;
+}
+
+PingReply::Error Ping::error() const
+{
+ return m_error;
+}
+
+PingReply *Ping::ping(const QHostAddress &hostAddress)
+{
+ PingReply *reply = new PingReply(this);
+ reply->m_targetHostAddress = hostAddress;
+ reply->m_networkInterface = getInterfaceForHostaddress(hostAddress);
+
+ // Perform the reply in the next event loop to give the user time to do the reply connects
+ m_replyQueue.enqueue(reply);
+ sendNextReply();
+
+ return reply;
+}
+
+void Ping::sendNextReply()
+{
+ if (m_queueTimer->isActive())
+ return;
+
+ if (m_replyQueue.isEmpty())
+ return;
+
+ PingReply *reply = m_replyQueue.dequeue();
+ //qCDebug(dcPing()) << "Send next reply," << m_replyQueue.count() << "left in queue";
+ m_queueTimer->start();
+ QTimer::singleShot(0, this, [=]() { performPing(reply); });
+}
+
+void Ping::performPing(PingReply *reply)
+{
+ if (!m_available) {
+ qCDebug(dcPing()) << "Cannot send ping request" << m_error;
+ finishReply(reply, m_error);
+ return;
+ }
+
+ // Get host ip address
+ struct hostent *hostname = gethostbyname(reply->targetHostAddress().toString().toLocal8Bit().constData());
+ struct sockaddr_in pingAddress;
+ memset(&pingAddress, 0, sizeof(pingAddress));
+ pingAddress.sin_family = hostname->h_addrtype;
+ pingAddress.sin_port = 0;
+ pingAddress.sin_addr.s_addr = *(long*)hostname->h_addr;
+
+ QHostAddress targetHostAddress = QHostAddress(qFromBigEndian(pingAddress.sin_addr.s_addr));
+
+ // Build the ICMP echo request packet
+ struct icmpPacket requestPacket;
+ memset(&requestPacket, 0, sizeof(requestPacket));
+ requestPacket.icmpHeadr.type = ICMP_ECHO;
+ if (reply->requestId() == 0) {
+ requestPacket.icmpHeadr.un.echo.id = calculateRequestId();
+ } else {
+ requestPacket.icmpHeadr.un.echo.id = reply->requestId();
+ }
+ requestPacket.icmpHeadr.un.echo.sequence = htons(reply->m_sequenceNumber++);
+
+ // Write the ICMP payload
+ memset(&requestPacket.icmpPayload, ' ', sizeof(requestPacket.icmpPayload));
+ for (int i = 0; i < m_payload.count(); i++)
+ requestPacket.icmpPayload[i] = m_payload.at(i);
+
+ // Calculate the ICMP packet checksum
+ requestPacket.icmpHeadr.checksum = calculateChecksum(reinterpret_cast(&requestPacket), sizeof(requestPacket));
+
+ // Get time for ping measurement and fill reply information
+ if (gettimeofday(&reply->m_startTime, nullptr) < 0 ) {
+ qCWarning(dcPing()) << "Failed to get start time for ping measurement" << strerror(errno);
+ }
+
+ reply->m_requestId = requestPacket.icmpHeadr.un.echo.id;
+ reply->m_targetHostAddress = targetHostAddress;
+ reply->m_sequenceNumber = requestPacket.icmpHeadr.un.echo.sequence;
+
+ qCDebug(dcPingTraffic()) << "Send ICMP echo request" << reply->targetHostAddress().toString() << ICMP_PACKET_SIZE << "[Bytes]"
+ << "ID:" << QString("0x%1").arg(requestPacket.icmpHeadr.un.echo.id, 4, 16, QChar('0'))
+ << "Sequence:" << htons(requestPacket.icmpHeadr.un.echo.sequence);
+
+ // Send packet to the target ip
+ int bytesSent = sendto(m_socketDescriptor, &requestPacket, sizeof(requestPacket), 0, (struct sockaddr *)&pingAddress, sizeof(pingAddress));
+ if (bytesSent < 0) {
+ verifyErrno(errno);
+ qCWarning(dcPing()) << "Failed to send data to" << reply->targetHostAddress().toString() << strerror(errno);
+ finishReply(reply, m_error);
+ return;
+ }
+
+ // Start reply timer and handle timeout
+ m_pendingReplies.insert(reply->requestId(), reply);
+}
+
+void Ping::verifyErrno(int error)
+{
+ switch (error) {
+ case ENETDOWN:
+ m_error = PingReply::ErrorNetworkDown;
+ break;
+ case ENETUNREACH:
+ m_error = PingReply::ErrorNetworkUnreachable;
+ break;
+ case EACCES:
+ case EPERM:
+ m_error = PingReply::ErrorPermissionDenied;
+ break;
+ default:
+ m_error = PingReply::ErrorSocketError;
+ }
+}
+
+unsigned short Ping::calculateChecksum(unsigned short *b, int len)
+{
+ unsigned short *buf = b;
+ unsigned int sum = 0;
+ unsigned short result;
+
+ for (sum = 0; len > 1; len -= 2)
+ sum += *buf++;
+
+ if (len == 1)
+ sum += *(unsigned char*)buf;
+
+ sum = (sum >> 16) + (sum & 0xFFFF);
+ sum += (sum >> 16);
+ result = ~sum;
+ return result;
+}
+
+void Ping::cleanUpSocket()
+{
+ m_available = false;
+
+ if (m_socketNotifier) {
+ m_socketNotifier->setEnabled(false);
+ delete m_socketNotifier;
+ m_socketNotifier = nullptr;
+ }
+
+ if (m_socketDescriptor >= 0) {
+ close(m_socketDescriptor);
+ m_socketDescriptor = -1;
+ }
+}
+
+void Ping::timeValueSubtract(timeval *start, timeval *stop)
+{
+ int sec = start->tv_sec - stop->tv_sec;
+ int usec = start->tv_usec - stop->tv_usec;
+ if (usec < 0) {
+ start->tv_sec = sec - 1;
+ start->tv_usec = 1000000 + usec;
+ } else {
+ start->tv_sec = sec;
+ start->tv_usec = usec;
+ }
+}
+
+quint16 Ping::calculateRequestId()
+{
+ quint16 requestId = 0;
+ while (requestId == 0 || m_pendingReplies.contains(requestId)) {
+ requestId = rand();
+ }
+
+ return requestId;
+}
+
+void Ping::finishReply(PingReply *reply, PingReply::Error error)
+{
+ reply->m_error = error;
+ m_pendingReplies.remove(reply->requestId());
+ emit reply->finished();
+ reply->deleteLater();
+}
+
+QNetworkInterface Ping::getInterfaceForHostaddress(const QHostAddress &address)
+{
+ foreach (const QNetworkInterface &networkInterface, QNetworkInterface::allInterfaces()) {
+ foreach (const QNetworkAddressEntry &entry, networkInterface.addressEntries()) {
+ // Only IPv4
+ if (entry.ip().protocol() != QAbstractSocket::IPv4Protocol)
+ continue;
+
+ if (address.isInSubnet(entry.ip(), entry.netmask().toIPv4Address())) {
+ return networkInterface;
+ }
+ }
+ }
+
+ return QNetworkInterface();
+}
+
+QNetworkInterface Ping::getInterfaceForMacAddress(const QString &macAddress)
+{
+ foreach (const QNetworkInterface &networkInterface, QNetworkInterface::allInterfaces()) {
+ if (networkInterface.hardwareAddress().toLower() == macAddress.toLower()) {
+ return networkInterface;
+ }
+ }
+
+ return QNetworkInterface();
+}
+
+void Ping::onSocketReadyRead(int socketDescriptor)
+{
+ // We must read all data otherwise the socket notifier does not work as expected
+ while (true) {
+ // Read the socket data...
+ int receiveBufferSize = 2 * ICMP_PACKET_SIZE + sizeof(struct iphdr);
+ char receiveBuffer[receiveBufferSize];
+ memset(&receiveBuffer, 0, sizeof(receiveBufferSize));
+
+ int bytesReceived = recv(socketDescriptor, &receiveBuffer, receiveBufferSize, 0);
+ if (bytesReceived < 0) {
+ return;
+ }
+
+ qCDebug(dcPingTraffic()) << "Received" << bytesReceived << "bytes" << "( Socket ID:" << m_socketDescriptor << ")";
+ struct iphdr *ipHeader = (struct iphdr *)receiveBuffer;
+ int ipHeaderLength = ipHeader->ihl << 2;
+ int icmpPacketSize = htons(ipHeader->tot_len) - ipHeaderLength;
+ QHostAddress senderAddress(qFromBigEndian(ipHeader->saddr));
+ QHostAddress destinationAddress(qFromBigEndian(ipHeader->daddr));
+
+ qCDebug(dcPingTraffic()) << "IP header: Lenght" << ipHeaderLength
+ << "Sender:" << senderAddress.toString()
+ << "Destination:" << destinationAddress.toString()
+ << "Size:" << htons(ipHeader->tot_len) << "B"
+ << "TTL" << ipHeader->ttl;
+
+ struct icmp *responsePacket = reinterpret_cast(receiveBuffer + ipHeaderLength);
+ qCDebug(dcPingTraffic()) << "ICMP packt (Size:" << icmpPacketSize << "Bytes):"
+ << "Type" << responsePacket->icmp_type
+ << "Code:" << responsePacket->icmp_code
+ << "ID:" << QString("0x%1").arg(responsePacket->icmp_id, 4, 16, QChar('0'))
+ << "Sequence:" << responsePacket->icmp_seq;
+
+ if (responsePacket->icmp_type == ICMP_ECHOREPLY) {
+ PingReply *reply = m_pendingReplies.take(responsePacket->icmp_id);
+ if (!reply) {
+ qCDebug(dcPing()) << "No pending reply for ping echo response with id" << QString("0x%1").arg(responsePacket->icmp_id, 4, 16, QChar('0')) << "Sequence:" << htons(responsePacket->icmp_seq) << "from" << senderAddress.toString();
+ return;
+ }
+
+ // Make sure the sender matches the target
+ if (reply->targetHostAddress() != senderAddress) {
+ qCWarning(dcPing()) << "Received id for different target reply" << reply->targetHostAddress().toString() << "!=" << senderAddress.toString();
+ finishReply(reply, PingReply::ErrorHostUnreachable);
+ return;
+ }
+
+ // Verify sequence number
+ if (responsePacket->icmp_seq != reply->sequenceNumber()) {
+ qCWarning(dcPing()) << "Received echo reply with different sequence number" << htons(responsePacket->icmp_seq);
+ finishReply(reply, PingReply::ErrorInvalidSequenceNumberResponse);
+ return;
+ }
+
+ // Calculate ping duration 2 digits accuracy
+ struct timeval receiveTimeValue;
+ gettimeofday(&receiveTimeValue, nullptr);
+ timeValueSubtract(&receiveTimeValue, &reply->m_startTime);
+ reply->m_duration = qRound((receiveTimeValue.tv_sec * 1000 + (double)receiveTimeValue.tv_usec / 1000) * 100) / 100.0;
+
+ QHostInfo::lookupHost(senderAddress.toString(), this, [=](const QHostInfo &info){
+ if (info.error() != QHostInfo::NoError) {
+ qCWarning(dcPing()) << "Failed to look up hostname after successfull ping" << senderAddress.toString() << info.error();
+ } else {
+ qCDebug(dcPing()) << "********Looked up hostname after successfull ping" << senderAddress.toString() << info.hostName();
+ if (info.hostName() != senderAddress.toString()) {
+ reply->m_hostName = info.hostName();
+ }
+ }
+
+ qCDebug(dcPingTraffic()) << "Received ICMP response" << reply->targetHostAddress().toString() << ICMP_PACKET_SIZE << "[Bytes]"
+ << "ID:" << QString("0x%1").arg(responsePacket->icmp_id, 4, 16, QChar('0'))
+ << "Sequence:" << htons(responsePacket->icmp_seq)
+ << "Time:" << reply->duration() << "[ms]" << info.hostName();
+
+ finishReply(reply, PingReply::ErrorNoError);
+ });
+ } else if (responsePacket->icmp_type == ICMP_DEST_UNREACH) {
+
+ // Get the sending package
+ int messageOffset = sizeof(struct iphdr) + 8;
+ struct iphdr *nestedIpHeader = (struct iphdr *)(receiveBuffer + messageOffset);
+ int nestedIpHeaderLength = nestedIpHeader->ihl << 2;
+ int nestedIcmpPacketSize = htons(nestedIpHeader->tot_len) - nestedIpHeaderLength;
+ QHostAddress nestedSenderAddress(qFromBigEndian(nestedIpHeader->saddr));
+ QHostAddress nestedDestinationAddress(qFromBigEndian(nestedIpHeader->daddr));
+
+ qCDebug(dcPingTraffic()) << "++ IP header: Lenght" << nestedIpHeaderLength
+ << "Sender:" << nestedSenderAddress.toString()
+ << "Destination:" << nestedDestinationAddress.toString()
+ << "Size:" << htons(nestedIpHeader->tot_len) << "B"
+ << "TTL" << ipHeader->ttl;
+
+ struct icmp *nestedResponsePacket = reinterpret_cast(receiveBuffer + messageOffset + nestedIpHeaderLength);
+ qCDebug(dcPingTraffic()) << "++ ICMP packt (Size:" << nestedIcmpPacketSize << "Bytes):"
+ << "Type" << nestedResponsePacket->icmp_type
+ << "Code:" << nestedResponsePacket->icmp_code
+ << "ID:" << QString("0x%1").arg(nestedResponsePacket->icmp_id, 4, 16, QChar('0'))
+ << "Sequence:" << nestedResponsePacket->icmp_seq;
+
+ qCDebug(dcPing()) << "ICMP destination unreachable" << nestedDestinationAddress.toString()
+ << "Code:" << nestedResponsePacket->icmp_code
+ << "ID:" << QString("0x%1").arg(nestedResponsePacket->icmp_id, 4, 16, QChar('0'))
+ << "Sequence:" << htons(nestedResponsePacket->icmp_seq);
+
+ PingReply *reply = m_pendingReplies.take(nestedResponsePacket->icmp_id);
+ if (!reply) {
+ qCDebug(dcPingTraffic()) << "No pending reply for ping echo response unreachable with ID"
+ << QString("0x%1").arg(nestedResponsePacket->icmp_id, 4, 16, QChar('0'))
+ << "Sequence:" << htons(nestedResponsePacket->icmp_seq)
+ << "from" << nestedSenderAddress.toString() << "to" << nestedDestinationAddress.toString();
+ return;
+ }
+
+ finishReply(reply, PingReply::ErrorHostUnreachable);
+ }
+ }
+}
+
diff --git a/libnymea/network/ping.h b/libnymea/network/ping.h
new file mode 100644
index 00000000..df673e9a
--- /dev/null
+++ b/libnymea/network/ping.h
@@ -0,0 +1,112 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2021, nymea GmbH
+* Contact: contact@nymea.io
+*
+* This file is part of nymea.
+* This project including source code and documentation is protected by
+* copyright law, and remains the property of nymea GmbH. All rights, including
+* reproduction, publication, editing and translation, are reserved. The use of
+* this project is subject to the terms of a license agreement to be concluded
+* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
+* under https://nymea.io/license
+*
+* GNU Lesser General Public License Usage
+* Alternatively, this project may be redistributed and/or modified under the
+* terms of the GNU Lesser General Public License as published by the Free
+* Software Foundation; version 3. This project is distributed in the hope that
+* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this project. If not, see .
+*
+* For any further details and any questions please contact us under
+* contact@nymea.io or see our FAQ/Licensing Information on
+* https://nymea.io/license/faq
+*
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#ifndef PING_H
+#define PING_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include "libnymea.h"
+
+#include "pingreply.h"
+
+#include
+
+#define ICMP_PACKET_SIZE 64
+#define ICMP_TTL_VALUE 64
+#define ICMP_PAYLOAD_SIZE (ICMP_PACKET_SIZE - sizeof(struct icmphdr))
+
+class LIBNYMEA_EXPORT Ping : public QObject
+{
+ Q_OBJECT
+public:
+ explicit Ping(QObject *parent = nullptr);
+
+ QByteArray payload() const;
+ void setPayload(const QByteArray &payload);
+
+ bool available() const;
+
+ PingReply::Error error() const;
+
+ PingReply *ping(const QHostAddress &hostAddress);
+
+signals:
+ void availableChanged(bool available);
+
+private:
+ struct icmpPacket {
+ struct icmphdr icmpHeadr;
+ char icmpPayload[ICMP_PAYLOAD_SIZE];
+ };
+
+ // Config
+ QByteArray m_payload = "ping from nymea";
+ PingReply::Error m_error = PingReply::ErrorNoError;
+
+ // Socket
+ QSocketNotifier *m_socketNotifier = nullptr;
+ int m_socketDescriptor = -1;
+ QHash m_pendingReplies;
+ bool m_available = false;
+
+ QQueue m_replyQueue;
+ QTimer *m_queueTimer = nullptr;
+ void sendNextReply();
+
+ //Error performPing(const QString &address);
+ void performPing(PingReply *reply);
+ void verifyErrno(int error);
+
+ // Helper
+ unsigned short calculateChecksum(unsigned short *b, int len);
+ void cleanUpSocket();
+ void timeValueSubtract(struct timeval *start, struct timeval *stop);
+ quint16 calculateRequestId();
+
+ void finishReply(PingReply *reply, PingReply::Error error);
+
+ QNetworkInterface getInterfaceForHostaddress(const QHostAddress &address);
+ QNetworkInterface getInterfaceForMacAddress(const QString &macAddress);
+
+private slots:
+ void onSocketReadyRead(int socketDescriptor);
+
+};
+
+#endif // PING_H
diff --git a/libnymea/network/pingreply.cpp b/libnymea/network/pingreply.cpp
new file mode 100644
index 00000000..e97034a1
--- /dev/null
+++ b/libnymea/network/pingreply.cpp
@@ -0,0 +1,71 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2021, nymea GmbH
+* Contact: contact@nymea.io
+*
+* This file is part of nymea.
+* This project including source code and documentation is protected by
+* copyright law, and remains the property of nymea GmbH. All rights, including
+* reproduction, publication, editing and translation, are reserved. The use of
+* this project is subject to the terms of a license agreement to be concluded
+* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
+* under https://nymea.io/license
+*
+* GNU Lesser General Public License Usage
+* Alternatively, this project may be redistributed and/or modified under the
+* terms of the GNU Lesser General Public License as published by the Free
+* Software Foundation; version 3. This project is distributed in the hope that
+* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this project. If not, see .
+*
+* For any further details and any questions please contact us under
+* contact@nymea.io or see our FAQ/Licensing Information on
+* https://nymea.io/license/faq
+*
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#include "pingreply.h"
+
+PingReply::PingReply(QObject *parent) : QObject(parent)
+{
+
+}
+
+QHostAddress PingReply::targetHostAddress() const
+{
+ return m_targetHostAddress;
+}
+
+quint16 PingReply::sequenceNumber() const
+{
+ return m_sequenceNumber;
+}
+
+quint16 PingReply::requestId() const
+{
+ return m_requestId;
+}
+
+QString PingReply::hostName() const
+{
+ return m_hostName;
+}
+
+QNetworkInterface PingReply::networkInterface() const
+{
+ return m_networkInterface;
+}
+
+double PingReply::duration() const
+{
+ return m_duration;
+}
+
+PingReply::Error PingReply::error() const
+{
+ return m_error;
+}
diff --git a/libnymea/network/pingreply.h b/libnymea/network/pingreply.h
new file mode 100644
index 00000000..da57b41b
--- /dev/null
+++ b/libnymea/network/pingreply.h
@@ -0,0 +1,91 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2021, nymea GmbH
+* Contact: contact@nymea.io
+*
+* This file is part of nymea.
+* This project including source code and documentation is protected by
+* copyright law, and remains the property of nymea GmbH. All rights, including
+* reproduction, publication, editing and translation, are reserved. The use of
+* this project is subject to the terms of a license agreement to be concluded
+* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
+* under https://nymea.io/license
+*
+* GNU Lesser General Public License Usage
+* Alternatively, this project may be redistributed and/or modified under the
+* terms of the GNU Lesser General Public License as published by the Free
+* Software Foundation; version 3. This project is distributed in the hope that
+* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this project. If not, see .
+*
+* For any further details and any questions please contact us under
+* contact@nymea.io or see our FAQ/Licensing Information on
+* https://nymea.io/license/faq
+*
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#ifndef PINGREPLY_H
+#define PINGREPLY_H
+
+#include
+#include
+#include
+
+#include
+
+#include
+#include
+
+class PingReply : public QObject
+{
+ Q_OBJECT
+
+ friend class Ping;
+
+public:
+ enum Error {
+ ErrorNoError,
+ ErrorInvalidSequenceNumberResponse,
+ ErrorNetworkDown,
+ ErrorNetworkUnreachable,
+ ErrorPermissionDenied,
+ ErrorSocketError,
+ ErrorHostUnreachable
+ };
+ Q_ENUM(Error)
+
+ explicit PingReply(QObject *parent = nullptr);
+
+ QHostAddress targetHostAddress() const;
+ quint16 sequenceNumber() const;
+ quint16 requestId() const;
+ QString hostName() const;
+ QNetworkInterface networkInterface() const;
+
+ double duration() const;
+
+ Error error() const;
+
+signals:
+ void finished();
+
+private:
+ QHostAddress m_targetHostAddress;
+ quint16 m_sequenceNumber = 0;
+ quint16 m_requestId = 0;
+ QString m_hostName;
+ QNetworkInterface m_networkInterface;
+
+ uint m_timeout = 3;
+ double m_duration = 0;
+ Error m_error = ErrorNoError;
+
+ struct timeval m_startTime;
+
+};
+
+#endif // PINGREPLY_H