summaryrefslogtreecommitdiffstats
path: root/src/knx/netip/qknxnetipendpointconnection.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/knx/netip/qknxnetipendpointconnection.cpp')
-rw-r--r--src/knx/netip/qknxnetipendpointconnection.cpp707
1 files changed, 537 insertions, 170 deletions
diff --git a/src/knx/netip/qknxnetipendpointconnection.cpp b/src/knx/netip/qknxnetipendpointconnection.cpp
index 18939a6..dfc6501 100644
--- a/src/knx/netip/qknxnetipendpointconnection.cpp
+++ b/src/knx/netip/qknxnetipendpointconnection.cpp
@@ -27,6 +27,7 @@
**
******************************************************************************/
+#include "qknxcryptographicengine.h"
#include "qknxnetipconnectrequest.h"
#include "qknxnetipconnectresponse.h"
#include "qknxnetipconnectionstaterequest.h"
@@ -37,6 +38,11 @@
#include "qknxnetipdisconnectresponse.h"
#include "qknxnetipendpointconnection.h"
#include "qknxnetipendpointconnection_p.h"
+#include "qknxnetipsessionauthenticate.h"
+#include "qknxnetipsecurewrapper.h"
+#include "qknxnetipsessionrequest.h"
+#include "qknxnetipsessionresponse.h"
+#include "qknxnetipsessionstatus.h"
#include "qknxnetiptunnelingacknowledge.h"
#include "qknxnetiptunnelingfeatureget.h"
#include "qknxnetiptunnelingfeatureset.h"
@@ -47,6 +53,8 @@
#include "qtcpsocket.h"
#include "qudpsocket.h"
+#include "private/qknxnetipsecureconfiguration_p.h"
+
QT_BEGIN_NAMESPACE
/*!
\class QKnxNetIpEndpointConnection
@@ -106,7 +114,18 @@ QT_BEGIN_NAMESPACE
State request timeout.
\value Cemi
No cEMI frame acknowledge in time.
+ \value SecureConfig
+ An invalid secure configuration was used to establish the connection.
+ \value SerialNumber
+ An invalid serial number was set for the device.
+ \value AuthFailed
+ The secure client was not successfully authenticated.
+ \value Timeout
+ A timeout occurred during secure session handshake.
+ \value Close
+ The server requested to close the secure session.
\value Unknown
+ An unknown error occurred.
*/
/*!
\enum QKnxNetIpEndpointConnection::SequenceType
@@ -177,17 +196,18 @@ void QKnxNetIpEndpointConnectionPrivate::setupTimer()
QKnxPrivate::clearTimer(&m_connectionStateTimer);
QKnxPrivate::clearTimer(&m_disconnectRequestTimer);
QKnxPrivate::clearTimer(&m_acknowledgeTimer);
+ QKnxPrivate::clearTimer(&m_secureTimer);
Q_Q(QKnxNetIpEndpointConnection);
m_heartbeatTimer = new QTimer(q);
m_heartbeatTimer->setSingleShot(true);
- QObject::connect(m_heartbeatTimer, &QTimer::timeout, [&]() {
+ QObject::connect(m_heartbeatTimer, &QTimer::timeout, q, [&]() {
sendStateRequest();
});
m_connectRequestTimer = new QTimer(q);
m_connectRequestTimer->setSingleShot(true);
- QObject::connect(m_connectRequestTimer, &QTimer::timeout, [&]() {
+ QObject::connect(m_connectRequestTimer, &QTimer::timeout, q, [&]() {
setAndEmitStateChanged(QKnxNetIpEndpointConnection::State::Bound);
setAndEmitErrorOccurred(QKnxNetIpEndpointConnection::Error::Acknowledge,
QKnxNetIpEndpointConnection::tr("Connect request timeout."));
@@ -198,7 +218,7 @@ void QKnxNetIpEndpointConnectionPrivate::setupTimer()
m_connectionStateTimer = new QTimer(q);
m_connectionStateTimer->setSingleShot(true);
- QObject::connect(m_connectionStateTimer, &QTimer::timeout, [&]() {
+ QObject::connect(m_connectionStateTimer, &QTimer::timeout, q, [&]() {
m_heartbeatTimer->stop();
m_connectionStateTimer->stop();
if (m_stateRequests > m_maxStateRequests) {
@@ -214,7 +234,7 @@ void QKnxNetIpEndpointConnectionPrivate::setupTimer()
m_disconnectRequestTimer = new QTimer(q);
m_disconnectRequestTimer->setSingleShot(true);
- QObject::connect(m_disconnectRequestTimer, &QTimer::timeout, [&] () {
+ QObject::connect(m_disconnectRequestTimer, &QTimer::timeout, q, [&] () {
setAndEmitErrorOccurred(QKnxNetIpEndpointConnection::Error::Acknowledge,
QKnxNetIpEndpointConnection::tr("Disconnect request timeout."));
processDisconnectResponse(QKnxNetIpDisconnectResponseProxy::builder()
@@ -223,7 +243,7 @@ void QKnxNetIpEndpointConnectionPrivate::setupTimer()
m_acknowledgeTimer = new QTimer(q);
m_acknowledgeTimer->setSingleShot(true);
- QObject::connect(m_acknowledgeTimer, &QTimer::timeout, [&]() {
+ QObject::connect(m_acknowledgeTimer, &QTimer::timeout, q, [&]() {
if (m_cemiRequests > m_maxCemiRequest) {
setAndEmitErrorOccurred(QKnxNetIpEndpointConnection::Error::Cemi,
QKnxNetIpEndpointConnection::tr("Did not receive acknowledge in time."));
@@ -235,43 +255,26 @@ void QKnxNetIpEndpointConnectionPrivate::setupTimer()
sendCemiRequest();
}
});
-}
-namespace QKnxPrivate
-{
- static bool isNullOrLocal(const QHostAddress &address)
- {
- return address.isNull() || address == QHostAddress::Any || address.isLoopback();
- }
+ m_secureTimer = new QTimer(q);
+ m_secureTimer->setSingleShot(true);
}
-void QKnxNetIpEndpointConnectionPrivate::processReceivedFrame(const QHostAddress &address, int port)
+QKnxNetIp::ServiceType
+ QKnxNetIpEndpointConnectionPrivate::processReceivedFrame(const QKnxNetIpFrame &frame)
{
- const auto frame = QKnxNetIpFrame::fromBytes(m_rxBuffer);
- if (!frame.isValid())
- return;
-
- // remove already processed KNX frame from buffer
- m_rxBuffer.remove(0, frame.size());
-
// TODO: fix the version and validity checks
- // if (!m_supportedVersions.contains(header.protocolVersion())) {
+ // if (!m_supportedVersions.contains(frame.header().protocolVersion())) {
// send E_VERSION_NOT_SUPPORTED confirmation frame
// send disconnect request
- // } else if (header.protocolVersion() != m_controlEndpointVersion) {
+ // } else if (frame.header().protocolVersion() != m_controlEndpointVersion) {
// send disconnect request
// } else {
// TODO: set the m_dataEndpointVersion once we receive or send the first frame
- switch (frame.serviceType()) {
+ auto serviceType = frame.serviceType();
+ switch (serviceType) {
case QKnxNetIp::ServiceType::ConnectResponse:
- if (m_nat) { // TODO: Review this for TCP connections
- if (QKnxPrivate::isNullOrLocal(m_remoteDataEndpoint.address)
- || m_remoteDataEndpoint.port == 0) {
- m_remoteDataEndpoint.port = port;
- m_remoteDataEndpoint.address = address;
- }
- }
processConnectResponse(frame);
break;
case QKnxNetIp::ServiceType::ConnectionStateResponse:
@@ -302,14 +305,241 @@ void QKnxNetIpEndpointConnectionPrivate::processReceivedFrame(const QHostAddress
case QKnxNetIp::ServiceType::TunnelingFeatureResponse:
processFeatureFrame(frame);
break;
+
+ case QKnxNetIp::ServiceType::SecureWrapper: {
+ qDebug() << "Received secure wrapper frame:" << frame;
+
+ const QKnxNetIpSecureWrapperProxy proxy(frame);
+ if (!proxy.isValid())
+ break;
+
+ const auto seqNumber = proxy.sequenceNumber();
+ const auto serialNumber = proxy.serialNumber();
+ const auto messageTag = proxy.messageTag();
+
+ const auto decData = QKnxCryptographicEngine::decryptSecureWrapperPayload(m_sessionKey,
+ proxy.encapsulatedFrame(), seqNumber, serialNumber, messageTag);
+
+ const auto mac = QKnxCryptographicEngine::computeMessageAuthenticationCode(m_sessionKey,
+ frame.header(), proxy.secureSessionId(), decData, seqNumber, serialNumber, messageTag);
+ const auto decMac = QKnxCryptographicEngine::decryptMessageAuthenticationCode(m_sessionKey,
+ proxy.messageAuthenticationCode(), seqNumber, serialNumber, messageTag);
+
+ if (decMac != mac)
+ break; // MAC could not be verified, bail out
+
+ return processReceivedFrame(QKnxNetIpFrame::fromBytes(decData));
+ } break;
+
+ case QKnxNetIp::ServiceType::SessionRequest:
+ qDebug() << "Unexpectedly received session request frame:" << frame;
+ break;
+
+ case QKnxNetIp::ServiceType::SessionResponse: {
+ qDebug() << "Received session response frame:" << frame;
+
+ QKnxNetIpSessionResponseProxy proxy(frame);
+ if (!proxy.isValid())
+ break;
+
+ const auto authHash = QKnxCryptographicEngine::deviceAuthenticationCodeHash(m_secureConfig.
+ d->deviceAuthenticationCode);
+ const auto xorX_Y = QKnxCryptographicEngine::XOR(m_secureConfig.d->publicKey.bytes(), proxy
+ .publicKey());
+
+ auto mac = QKnxCryptographicEngine::computeMessageAuthenticationCode(authHash,
+ frame.header(), proxy.secureSessionId(), xorX_Y);
+ auto decMac = QKnxCryptographicEngine::decryptMessageAuthenticationCode(authHash,
+ proxy.messageAuthenticationCode());
+
+ if (decMac != mac)
+ break; // MAC could not be verified, bail out
+
+ m_secureTimer->stop();
+ m_secureTimer->disconnect();
+ m_sessionId = proxy.secureSessionId();
+ m_sessionKey = QKnxCryptographicEngine::sessionKey(m_secureConfig.d->privateKey,
+ QKnxSecureKey::fromBytes(QKnxSecureKey::Type::Public, proxy.publicKey()));
+
+ auto secureWrapper = QKnxNetIpSecureWrapperProxy::secureBuilder()
+ .setSecureSessionId(m_sessionId)
+ .setSequenceNumber(m_sequenceNumber)
+ .setSerialNumber(m_serialNumber)
+ // .setMessageTag(0x0000) TODO: Do we need an API for this?
+ .setEncapsulatedFrame(QKnxNetIpSessionAuthenticateProxy::secureBuilder()
+ .setUserId(m_secureConfig.d->userId)
+ .create(m_secureConfig.d->userPassword, m_secureConfig.d->publicKey.bytes(), proxy
+ .publicKey()))
+ .create(m_sessionKey);
+
+ ++m_sequenceNumber;
+ m_waitForAuthentication = true;
+ if (m_tcpSocket)
+ m_tcpSocket->write(secureWrapper.bytes().toByteArray());
+
+ Q_Q(QKnxNetIpEndpointConnection);
+ QObject::connect(m_secureTimer, &QTimer::timeout, q, [&]() {
+ m_secureTimer->stop();
+ setAndEmitErrorOccurred(QKnxNetIpEndpointConnection::Error::AuthFailed,
+ QKnxNetIpEndpointConnection::tr("Did not receive session status frame."));
+
+ auto secureStatusWrapper = QKnxNetIpSecureWrapperProxy::secureBuilder()
+ .setSecureSessionId(m_sessionId)
+ .setSequenceNumber(m_sequenceNumber)
+ .setSerialNumber(m_serialNumber)
+ // .setMessageTag(0x0000) TODO: Do we need an API for this?
+ .setEncapsulatedFrame(QKnxNetIpSessionStatusProxy::builder()
+ .setStatus(QKnxNetIp::SecureSessionStatus::Close)
+ .create())
+ .create(m_sessionKey);
+ if (m_tcpSocket)
+ m_tcpSocket->write(secureStatusWrapper.bytes().toByteArray());
+
+ Q_Q(QKnxNetIpEndpointConnection);
+ q->disconnectFromHost();
+ });
+ m_secureTimer->start(QKnxNetIp::SecureSessionAuthenticateTimeout);
+ } break;
+
+ case QKnxNetIp::ServiceType::SessionAuthenticate:
+ qDebug() << "Unexpectedly received session authenticate frame:" << frame;
+ break;
+
+ case QKnxNetIp::ServiceType::SessionStatus: {
+ qDebug() << "Received session status frame:" << frame;
+
+ QKnxNetIpSessionStatusProxy ssp(frame);
+ if (!ssp.isValid())
+ break;
+
+ bool close { false };
+ switch (ssp.status()) {
+ case QKnxNetIp::SecureSessionStatus::AuthenticationSuccess: {
+ if (m_waitForAuthentication) {
+ m_secureTimer->stop();
+ m_secureTimer->disconnect();
+ m_waitForAuthentication = false;
+ auto ep = (m_tcpSocket ? m_routeBack : (m_nat ? m_routeBack : m_localEndpoint));
+ auto secureWrapper = QKnxNetIpSecureWrapperProxy::secureBuilder()
+ .setSecureSessionId(m_sessionId)
+ .setSequenceNumber(m_sequenceNumber)
+ .setSerialNumber(m_serialNumber)
+ // .setMessageTag(0x0000) TODO: Do we need an API for this?
+ .setEncapsulatedFrame(QKnxNetIpConnectRequestProxy::builder()
+ .setControlEndpoint(ep)
+ .setDataEndpoint(ep)
+ .setRequestInformation(m_cri)
+ .create())
+ .create(m_sessionKey);
+
+ ++m_sequenceNumber;
+ if (m_tcpSocket)
+ m_tcpSocket->write(secureWrapper.bytes().toByteArray());
+
+ if (!m_secureConfig.d->keepAlive)
+ break;
+
+ Q_Q(QKnxNetIpEndpointConnection);
+ QObject::connect(m_secureTimer, &QTimer::timeout, q, [&]() {
+ auto secureStatusWrapper = QKnxNetIpSecureWrapperProxy::secureBuilder()
+ .setSecureSessionId(m_sessionId)
+ .setSequenceNumber(m_sequenceNumber)
+ .setSerialNumber(m_serialNumber)
+ // .setMessageTag(0x0000) TODO: Do we need an API for this?
+ .setEncapsulatedFrame(QKnxNetIpSessionStatusProxy::builder()
+ .setStatus(QKnxNetIp::SecureSessionStatus::KeepAlive)
+ .create())
+ .create(m_sessionKey);
+ qDebug() << "Sending keep alive status frame:" << secureStatusWrapper;
+
+ ++m_sequenceNumber;
+ if (m_tcpSocket)
+ m_tcpSocket->write(secureStatusWrapper.bytes().toByteArray());
+ });
+ m_secureTimer->setSingleShot(false);
+ m_secureTimer->start(QKnxNetIp::Timeout::SecureSessionTimeout - 5000);
+ }
+ } break;
+
+ case QKnxNetIp::SecureSessionStatus::KeepAlive:
+ // TODO: implement in case we're the server
+ qDebug() << "Unexpectedly received keep alive status frame:" << frame;
+ break;
+
+ case QKnxNetIp::SecureSessionStatus::AuthenticationFailed:
+ setAndEmitErrorOccurred(QKnxNetIpEndpointConnection::Error::AuthFailed,
+ QKnxNetIpEndpointConnection::tr("Secure session authentication failed."));
+ close = true;
+ break;
+
+ case QKnxNetIp::SecureSessionStatus::Unauthenticated:
+ setAndEmitErrorOccurred(QKnxNetIpEndpointConnection::Error::AuthFailed,
+ QKnxNetIpEndpointConnection::tr("Secure session not authenticated."));
+ close = true;
+ break;
+
+ case QKnxNetIp::SecureSessionStatus::Timeout:
+ setAndEmitErrorOccurred(QKnxNetIpEndpointConnection::Error::Timeout,
+ QKnxNetIpEndpointConnection::tr("A timeout occurred during secure session handshake."));
+ close = true;
+ break;
+
+ case QKnxNetIp::SecureSessionStatus::Close:
+ setAndEmitErrorOccurred(QKnxNetIpEndpointConnection::Error::Timeout,
+ QKnxNetIpEndpointConnection::tr("The server requested to close the secure session."));
+ close = true;
+ break;
+
+ default:
+ qDebug() << "Received unknown status frame:" << frame;
+ break;
+ }
+
+ if (close) {
+ Q_Q(QKnxNetIpEndpointConnection);
+ q->disconnectFromHost();
+ }
+ } break;
+
+ case QKnxNetIp::ServiceType::TimerNotify:
+ qDebug() << "Unexpectedly received timer notify frame:" << frame;
+ break;
+
default:
break;
}
+ return serviceType;
}
-void QKnxNetIpEndpointConnectionPrivate::setup()
+bool QKnxNetIpEndpointConnectionPrivate::initConnection(const QHostAddress &a, quint16 p,
+ QKnxNetIp::HostProtocol hp)
{
- setupTimer();
+ if (!QKnxNetIp::isStructType(hp))
+ return false;
+
+ if (m_state != QKnxNetIpEndpointConnection::State::Disconnected)
+ return false;
+
+ auto isIPv4 = false;
+ a.toIPv4Address(&isIPv4);
+ if (!isIPv4) {
+ setAndEmitErrorOccurred(QKnxNetIpEndpointConnection::Error::NotIPv4,
+ QKnxNetIpEndpointConnection::tr("Only IPv4 addresses as remote control endpoint "
+ "supported."));
+ return false;
+ }
+ m_remoteControlEndpoint = { a, p, hp };
+
+ isIPv4 = false;
+ m_user.address.toIPv4Address(&isIPv4);
+ if (!isIPv4) {
+ setAndEmitErrorOccurred(QKnxNetIpEndpointConnection::Error::NotIPv4,
+ QKnxNetIpEndpointConnection::tr("Only IPv4 addresses as local control endpoint "
+ "supported."));
+ return false;
+ }
+
+ setAndEmitStateChanged(QKnxNetIpEndpointConnection::State::Starting);
m_channelId = -1;
@@ -327,49 +557,67 @@ void QKnxNetIpEndpointConnectionPrivate::setup()
m_errorString = QString();
m_error = QKnxNetIpEndpointConnection::Error::None;
- if (m_tcpSocket) {
- QObject::connect(m_tcpSocket, &QIODevice::readyRead, [&]() {
+ m_routeBack.hostProtocol = hp;
+
+ m_sessionId = 0;
+ m_sequenceNumber = 0;
+ m_waitForAuthentication = false;
+
+ setupTimer();
+
+ QKnxPrivate::clearSocket(&m_tcpSocket);
+ QKnxPrivate::clearSocket(&m_udpSocket);
+
+ Q_Q(QKnxNetIpEndpointConnection);
+ QAbstractSocket *socket = nullptr;
+ if (hp == QKnxNetIp::HostProtocol::TCP_IPv4) {
+ socket = m_tcpSocket = new QTcpSocket(q_func());
+ QObject::connect(m_tcpSocket, &QTcpSocket::readyRead, q, [&]() {
+ if (m_tcpSocket->bytesAvailable() < QKnxNetIpFrameHeader::HeaderSize10)
+ return;
m_rxBuffer += QKnxByteArray::fromByteArray(m_tcpSocket->readAll());
- int bufferSize = m_rxBuffer.size();
- bool continueProcessingRxBuffer = false;
- do {
- // TODO: AN184 v03 KNXnet-IP Core v2 AS, 2.2.3.2.3.2
- processReceivedFrame(m_remoteControlEndpoint.address,
- m_remoteControlEndpoint.port);
- continueProcessingRxBuffer = (m_rxBuffer.size() < bufferSize);
- bufferSize = m_rxBuffer.size();
- } while (continueProcessingRxBuffer);
- });
- using overload = void (QTcpSocket::*)(QTcpSocket::SocketError);
- QObject::connect(m_tcpSocket,
- static_cast<overload>(&QTcpSocket::error), [&](QTcpSocket::SocketError) {
- setAndEmitErrorOccurred(QKnxNetIpEndpointConnection::Error::Network,
- m_tcpSocket->errorString());
+ // TODO: AN184 v03 KNXnet-IP Core v2 AS, 2.2.3.2.3.2
+ auto header = QKnxNetIpFrameHeader::fromBytes(m_rxBuffer);
+ if (header.isValid() && m_rxBuffer.size() < header.totalSize())
+ return;
- Q_Q(QKnxNetIpEndpointConnection);
- q->disconnectFromHost();
+ auto frame = QKnxNetIpFrame::fromBytes(m_rxBuffer);
+ m_rxBuffer.remove(0, header.totalSize());
+ processReceivedFrame(frame);
});
- } else if (m_udpSocket) {
- QObject::connect(m_udpSocket, &QUdpSocket::readyRead, [&]() {
+ } else if (hp == QKnxNetIp::HostProtocol::UDP_IPv4) {
+ socket = m_udpSocket = new QUdpSocket(q_func());
+ QObject::connect(m_udpSocket, &QUdpSocket::readyRead, q, [&]() {
while (m_udpSocket && m_udpSocket->state() == QUdpSocket::BoundState
&& m_udpSocket->hasPendingDatagrams()) {
- auto datagram = m_udpSocket->receiveDatagram();
- m_rxBuffer += QKnxByteArray::fromByteArray(datagram.data());
- processReceivedFrame(datagram.senderAddress(), datagram.senderPort());
+ auto tmp = m_udpSocket->receiveDatagram();
+ auto frame = QKnxNetIpFrame::fromBytes(QKnxByteArray::fromByteArray(tmp.data()));
+ if (processReceivedFrame(frame) != QKnxNetIp::ServiceType::ConnectResponse)
+ continue;
+
+ if (m_nat && m_remoteDataEndpoint.isNullOrLocal())
+ m_remoteDataEndpoint = { tmp.senderAddress(), quint16(tmp.senderPort())};
}
});
+ } else {
+ return false;
+ }
- using overload = void (QUdpSocket::*)(QUdpSocket::SocketError);
- QObject::connect(m_udpSocket,
- static_cast<overload>(&QUdpSocket::error), [&](QUdpSocket::SocketError) {
- setAndEmitErrorOccurred(QKnxNetIpEndpointConnection::Error::Network,
- m_udpSocket->errorString());
-
- Q_Q(QKnxNetIpEndpointConnection);
- q->disconnectFromHost();
+ if (socket) {
+ QObject::connect(socket, QOverload<QAbstractSocket::SocketError>::of(&QAbstractSocket::error),
+ q, [socket, this](QAbstractSocket::SocketError) {
+ setAndEmitErrorOccurred(QKnxNetIpEndpointConnection::Error::Network,
+ socket->errorString());
+ Q_Q(QKnxNetIpEndpointConnection);
+ q->disconnectFromHost();
});
+ } else {
+ return false;
}
+
+ setAndEmitStateChanged(QKnxNetIpEndpointConnection::State::Connecting);
+ return true;
}
void QKnxNetIpEndpointConnectionPrivate::cleanup()
@@ -379,11 +627,26 @@ void QKnxNetIpEndpointConnectionPrivate::cleanup()
QKnxPrivate::clearTimer(&m_connectionStateTimer);
QKnxPrivate::clearTimer(&m_disconnectRequestTimer);
QKnxPrivate::clearTimer(&m_acknowledgeTimer);
+ QKnxPrivate::clearTimer(&m_secureTimer);
if (m_udpSocket) {
m_udpSocket->close();
QKnxPrivate::clearSocket(&m_udpSocket);
- } else {
+ } else if (m_tcpSocket) {
+ if (m_secureConfig.isValid()) {
+ auto secureStatusWrapper = QKnxNetIpSecureWrapperProxy::secureBuilder()
+ .setSecureSessionId(m_sessionId)
+ .setSequenceNumber(m_sequenceNumber)
+ .setSerialNumber(m_serialNumber)
+ // .setMessageTag(0x0000) TODO: Do we need an API for this?
+ .setEncapsulatedFrame(QKnxNetIpSessionStatusProxy::builder()
+ .setStatus(QKnxNetIp::SecureSessionStatus::Close)
+ .create())
+ .create(m_sessionKey);
+ ++m_sequenceNumber;
+ m_tcpSocket->write(secureStatusWrapper.bytes().toByteArray());
+ m_tcpSocket->waitForBytesWritten();
+ }
m_tcpSocket->close();
QKnxPrivate::clearSocket(&m_tcpSocket);
}
@@ -393,19 +656,37 @@ void QKnxNetIpEndpointConnectionPrivate::cleanup()
bool QKnxNetIpEndpointConnectionPrivate::sendCemiRequest()
{
- if (!m_waitForAcknowledgement && !m_tcpSocket) {
+ if (m_udpSocket) {
+ if (m_waitForAcknowledgement)
+ return false; // still waiting for an ACK from an previous request
+
m_waitForAcknowledgement = true;
m_udpSocket->writeDatagram(m_lastSendCemiRequest.bytes().toByteArray(),
- m_remoteDataEndpoint.address,
- m_remoteDataEndpoint.port);
+ m_remoteDataEndpoint.address,
+ m_remoteDataEndpoint.port);
m_cemiRequests++;
m_acknowledgeTimer->start(m_acknowledgeTimeout);
- } else if (m_tcpSocket) {
- m_tcpSocket->write(m_lastSendCemiRequest.bytes().toByteArray());
- m_waitForAcknowledgement = false;
+ return true;
}
- return !m_waitForAcknowledgement;
+
+ if (m_tcpSocket) {
+ if (m_secureConfig.isValid()) {
+ auto secureFrame = QKnxNetIpSecureWrapperProxy::secureBuilder()
+ .setSecureSessionId(m_sessionId)
+ .setSequenceNumber(m_sequenceNumber)
+ .setSerialNumber(m_serialNumber)
+ // .setMessageTag(0x0000) TODO: Do we need an API for this?
+ .setEncapsulatedFrame(m_lastSendCemiRequest)
+ .create(m_sessionKey);
+ ++m_sequenceNumber;
+ m_tcpSocket->write(secureFrame.bytes().toByteArray());
+ } else {
+ m_tcpSocket->write(m_lastSendCemiRequest.bytes().toByteArray());
+ }
+ return true; // TCP connections do not send an ACK
+ }
+ return false;
}
void QKnxNetIpEndpointConnectionPrivate::sendStateRequest()
@@ -414,7 +695,19 @@ void QKnxNetIpEndpointConnectionPrivate::sendStateRequest()
.bytes().toHex();
if (m_tcpSocket) {
- m_tcpSocket->write(m_lastStateRequest.bytes().toByteArray());
+ if (m_secureConfig.isValid()) {
+ auto secureFrame = QKnxNetIpSecureWrapperProxy::secureBuilder()
+ .setSecureSessionId(m_sessionId)
+ .setSequenceNumber(m_sequenceNumber)
+ .setSerialNumber(m_serialNumber)
+ // .setMessageTag(0x0000) TODO: Do we need an API for this?
+ .setEncapsulatedFrame(m_lastStateRequest)
+ .create(m_sessionKey);
+ ++m_sequenceNumber;
+ m_tcpSocket->write(secureFrame.bytes().toByteArray());
+ } else {
+ m_tcpSocket->write(m_lastStateRequest.bytes().toByteArray());
+ }
} else {
m_udpSocket->writeDatagram(m_lastStateRequest.bytes().toByteArray(),
m_remoteControlEndpoint.address, m_remoteControlEndpoint.port);
@@ -653,10 +946,13 @@ void QKnxNetIpEndpointConnectionPrivate::processConnectResponse(const QKnxNetIpF
m_remoteDataEndpoint = response.dataEndpoint();
m_lastStateRequest = QKnxNetIpConnectionStateRequestProxy::builder()
.setChannelId(m_channelId)
- .setControlEndpoint(m_nat ? m_natEndpoint : m_localEndpoint)
+ .setControlEndpoint(
+ m_tcpSocket ? m_routeBack : (m_nat ? m_routeBack : m_localEndpoint)
+ )
.create();
- QTimer::singleShot(0, [&]() { sendStateRequest(); });
+ Q_Q(QKnxNetIpEndpointConnection);
+ QTimer::singleShot(0, q, [&]() { sendStateRequest(); });
setAndEmitStateChanged(QKnxNetIpEndpointConnection::State::Connected);
} else {
auto metaEnum = QMetaEnum::fromType<QKnxNetIp::Error>();
@@ -699,16 +995,26 @@ void QKnxNetIpEndpointConnectionPrivate::processDisconnectRequest(const QKnxNetI
QKnxNetIpDisconnectRequestProxy request(frame);
if (request.channelId() == m_channelId) {
- auto frame = QKnxNetIpDisconnectResponseProxy::builder()
+ auto responseFrame = QKnxNetIpDisconnectResponseProxy::builder()
.setChannelId(m_channelId)
.setStatus(QKnxNetIp::Error::None)
.create();
- qDebug() << "Sending disconnect response:" << frame;
+ qDebug() << "Sending disconnect response:" << responseFrame;
if (m_tcpSocket) {
- m_tcpSocket->write(frame.bytes().toByteArray());
+ if (m_secureConfig.isValid()) {
+ responseFrame = QKnxNetIpSecureWrapperProxy::secureBuilder()
+ .setSecureSessionId(m_sessionId)
+ .setSequenceNumber(m_sequenceNumber)
+ .setSerialNumber(m_serialNumber)
+ // .setMessageTag(0x0000) TODO: Do we need an API for this?
+ .setEncapsulatedFrame(responseFrame)
+ .create(m_sessionKey);
+ ++m_sequenceNumber;
+ }
+ m_tcpSocket->write(responseFrame.bytes().toByteArray());
} else {
- m_udpSocket->writeDatagram(frame.bytes().toByteArray(),
- m_remoteControlEndpoint.address, m_remoteControlEndpoint.port);
+ m_udpSocket->writeDatagram(responseFrame.bytes().toByteArray(),
+ m_remoteControlEndpoint.address, m_remoteControlEndpoint.port);
}
Q_Q(QKnxNetIpEndpointConnection);
@@ -757,7 +1063,8 @@ void QKnxNetIpEndpointConnectionPrivate::setAndEmitErrorOccurred(
}
-// -- QKnxNetIpClient
+// -- QKnxNetIpEndpointConnection
+
/*!
Destroys a connection and disconnects from the server.
*/
@@ -879,8 +1186,8 @@ quint32 QKnxNetIpEndpointConnection::heartbeatTimeout() const
}
/*!
- Sets the value of the heartbeat timeout. Every \a msec a state request is sent over the
- connection to keep it alive.
+ Sets the value of the heartbeat timeout. Every \a msec a state request is
+ sent over the connection to keep it alive.
*/
void QKnxNetIpEndpointConnection::setHeartbeatTimeout(quint32 msec)
{
@@ -923,139 +1230,184 @@ void QKnxNetIpEndpointConnection::connectToHost(const QKnxNetIpHpai &controlEndp
}
/*!
- Establishes a connection to the host with \a address and \a port.
+ Establishes a connection to the host with \a address and \a port via UDP.
*/
void QKnxNetIpEndpointConnection::connectToHost(const QHostAddress &address, quint16 port)
{
Q_D(QKnxNetIpEndpointConnection);
- if (d->m_state != State::Disconnected)
- return;
-
- auto isIPv4 = false;
- address.toIPv4Address(&isIPv4);
- if (!isIPv4) {
- d->setAndEmitErrorOccurred(Error::NotIPv4, tr("Only IPv4 addresses as remote control "
- "endpoint supported."));
- return;
- }
- d->m_remoteControlEndpoint = Endpoint(address, port);
-
- isIPv4 = false;
- d->m_user.address.toIPv4Address(&isIPv4);
- if (!isIPv4) {
- d->setAndEmitErrorOccurred(Error::NotIPv4, tr("Only IPv4 addresses as local control "
- "endpoint supported."));
+ if (!d->initConnection(address, port, QKnxNetIp::HostProtocol::UDP_IPv4))
return;
- }
-
- d->setAndEmitStateChanged(QKnxNetIpEndpointConnection::State::Starting);
-
- QKnxPrivate::clearSocket(&(d->m_udpSocket));
- d->m_udpSocket = new QUdpSocket(this);
- if (!d->m_udpSocket->bind(d->m_user.address, d->m_user.port)) {
- d->setAndEmitErrorOccurred(QKnxNetIpEndpointConnection::Error::Network,
- QKnxNetIpEndpointConnection::tr("Could not bind endpoint: %1")
- .arg(d->m_udpSocket->errorString()));
- QKnxPrivate::clearSocket(&d->m_udpSocket);
- d->setAndEmitStateChanged(QKnxNetIpEndpointConnection::State::Disconnected);
+ if (!d->m_udpSocket->bind(d->m_user.address, d->m_user.port))
return;
- }
- d->m_localEndpoint = Endpoint(d->m_udpSocket->localAddress(), d->m_udpSocket->localPort());
+ d->m_localEndpoint = { d->m_udpSocket->localAddress(), d->m_udpSocket->localPort() };
d->setAndEmitStateChanged(QKnxNetIpEndpointConnection::State::Bound);
- d->setup();
-
- d->setAndEmitStateChanged(QKnxNetIpEndpointConnection::State::Connecting);
-
auto request = QKnxNetIpConnectRequestProxy::builder()
- .setControlEndpoint(d->m_nat ? d->m_natEndpoint : d->m_localEndpoint)
- .setDataEndpoint(d->m_nat ? d->m_natEndpoint : d->m_localEndpoint)
+ .setControlEndpoint(d->m_nat ? d->m_routeBack : d->m_localEndpoint)
+ .setDataEndpoint(d->m_nat ? d->m_routeBack : d->m_localEndpoint)
.setRequestInformation(d->m_cri)
.create();
d->m_controlEndpointVersion = request.header().protocolVersion();
- qDebug() << "Sending connect request:" << request;
-
- d->m_connectRequestTimer->start(QKnxNetIp::ConnectRequestTimeout);
+ d->setAndEmitStateChanged(QKnxNetIpEndpointConnection::State::Connecting);
+ qDebug() << "Sending connect request:" << request;
d->m_udpSocket->writeDatagram(request.bytes().toByteArray(),
d->m_remoteControlEndpoint.address, d->m_remoteControlEndpoint.port);
+
+ if (d->m_connectRequestTimer)
+ d->m_connectRequestTimer->start(QKnxNetIp::ConnectRequestTimeout);
}
/*!
\since 5.12
- Establishes a connection to the host with \a address, \a port and \a proto.
+ Establishes a connection to the host with \a address, \a port and \a protocol.
*/
void QKnxNetIpEndpointConnection::connectToHost(const QHostAddress &address, quint16 port,
- QKnxNetIp::HostProtocol proto)
+ QKnxNetIp::HostProtocol protocol)
{
- if (proto == QKnxNetIp::HostProtocol::Unknown)
- return;
+ if (protocol == QKnxNetIp::HostProtocol::UDP_IPv4)
+ return connectToHost(address, port);
- if (proto == QKnxNetIp::HostProtocol::UDP_IPv4) {
- connectToHost(address, port);
+ Q_D(QKnxNetIpEndpointConnection);
+ if (!d->initConnection(address, port, protocol))
return;
- }
- // establishing TCP connection
+ connect(d->m_tcpSocket, &QTcpSocket::connected, this, [&]() {
+ Q_D(QKnxNetIpEndpointConnection);
+ d->m_localEndpoint = { d->m_tcpSocket->localAddress(), d->m_tcpSocket->localPort(),
+ QKnxNetIp::HostProtocol::TCP_IPv4 };
+
+ auto request = QKnxNetIpConnectRequestProxy::builder()
+ .setControlEndpoint(d->m_routeBack)
+ .setDataEndpoint(d->m_routeBack)
+ .setRequestInformation(d->m_cri)
+ .create();
+ d->m_controlEndpointVersion = request.header().protocolVersion();
+
+ qDebug() << "Sending connect request:" << request;
+ d->m_tcpSocket->write(request.bytes().toByteArray());
+ d->m_connectRequestTimer->start(QKnxNetIp::ConnectRequestTimeout);
+ });
+ d->m_tcpSocket->connectToHost(address, port);
+}
+
+/*!
+ \since 5.13
+
+ Returns the serial number of the device using this connection. The default
+ value is set to \c {0x000000000000}.
+*/
+QKnxByteArray QKnxNetIpEndpointConnection::serialNumber() const
+{
+ Q_D(const QKnxNetIpEndpointConnection);
+ return d->m_serialNumber;
+}
+
+/*!
+ \since 5.13
+
+ Sets the serial number to \a serialNumber of the device using this
+ connection.
+
+ \note The serial number must contain exactly 6 bytes and cannot be changed
+ while the secure session is established.
+*/
+void QKnxNetIpEndpointConnection::setSerialNumber(const QKnxByteArray &serialNumber)
+{
Q_D(QKnxNetIpEndpointConnection);
- if (d->m_state != State::Disconnected)
- return;
+ if (d->m_state == QKnxNetIpEndpointConnection::State::Disconnected)
+ d->m_serialNumber = serialNumber;
+}
- auto isIPv4 = false;
- address.toIPv4Address(&isIPv4);
- if (!isIPv4) {
- d->setAndEmitErrorOccurred(Error::NotIPv4, tr("Only IPv4 addresses as remote control "
- "endpoint supported."));
- return;
- }
- d->m_remoteControlEndpoint = Endpoint(address, port);
- d->m_remoteControlEndpoint.code = QKnxNetIp::HostProtocol::TCP_IPv4;
+/*!
+ \since 5.13
- isIPv4 = false;
- d->m_user.address.toIPv4Address(&isIPv4);
- if (!isIPv4) {
- d->setAndEmitErrorOccurred(Error::NotIPv4, tr("Only IPv4 addresses as local control "
- "endpoint supported."));
- return;
+ Returns the secure configuration used to establish the secure session.
+*/
+QKnxNetIpSecureConfiguration QKnxNetIpEndpointConnection::secureConfiguration() const
+{
+ Q_D(const QKnxNetIpEndpointConnection);
+ return d->m_secureConfig;
+}
+
+/*!
+ \since 5.13
+
+ Sets a secure configuration to be used to establish secure session
+ to \a {config}. The configuration cannot be changed while the secure
+ connection is established.
+*/
+void QKnxNetIpEndpointConnection::setSecureConfiguration(const QKnxNetIpSecureConfiguration &config)
+{
+ Q_D(QKnxNetIpEndpointConnection);
+ if (d->m_state == QKnxNetIpEndpointConnection::State::Disconnected) {
+ d->m_secureConfig = config;
+ d->updateCri(config.individualAddress());
}
+}
- d->setAndEmitStateChanged(QKnxNetIpEndpointConnection::State::Starting);
+/*!
+ \since 5.13
- QKnxPrivate::clearSocket(&(d->m_tcpSocket));
+ Establishes a connection to the KNXnet/IP control endpoint \a controlEndpoint.
- d->m_tcpSocket = new QTcpSocket(this);
- d->setup();
+ \sa setSerialNumber(), setSecureConfiguration()
+*/
+void QKnxNetIpEndpointConnection::connectToHostEncrypted(const QKnxNetIpHpai &controlEndpoint)
+{
+ const QKnxNetIpHpaiProxy proxy(controlEndpoint);
+ if (proxy.isValid() && proxy.hostProtocol() == QKnxNetIp::HostProtocol::TCP_IPv4)
+ connectToHost(proxy.hostAddress(), proxy.port());
+}
- d->setAndEmitStateChanged(QKnxNetIpEndpointConnection::State::Connecting);
+/*!
+ \since 5.13
- d->m_tcpSocket->abort();
- d->m_tcpSocket->connectToHost(address, port);
+ Establishes a secure session to the host with \a address and \a port.
+
+ \sa setSerialNumber(), setSecureConfiguration()
+*/
+void QKnxNetIpEndpointConnection::connectToHostEncrypted(const QHostAddress &address, quint16 port)
+{
+ Q_D(QKnxNetIpEndpointConnection);
+ if (!d->initConnection(address, port, QKnxNetIp::HostProtocol::TCP_IPv4))
+ return;
+
+ if (!d->m_secureConfig.isValid())
+ return d->setAndEmitErrorOccurred(Error::SecureConfig, tr("Invalid secure configuration."));
- d->m_localEndpoint = Endpoint(d->m_tcpSocket->localAddress(),
- d->m_tcpSocket->localPort());
- d->m_localEndpoint.code = QKnxNetIp::HostProtocol::TCP_IPv4;
- d->m_natEndpoint.code = QKnxNetIp::HostProtocol::TCP_IPv4;
- d->m_remoteDataEndpoint.code = QKnxNetIp::HostProtocol::TCP_IPv4;
+ if (d->m_serialNumber.size() != 6)
+ return d->setAndEmitErrorOccurred(Error::SerialNumber, tr("Invalid device serial number."));
connect(d->m_tcpSocket, &QTcpSocket::connected, this, [&]() {
Q_D(QKnxNetIpEndpointConnection);
- auto request = QKnxNetIpConnectRequestProxy::builder()
- .setControlEndpoint(d->m_nat ? d->m_natEndpoint : d->m_localEndpoint)
- .setDataEndpoint(d->m_nat ? d->m_natEndpoint : d->m_localEndpoint)
- .setRequestInformation(d->m_cri)
+ d->m_localEndpoint = Endpoint(d->m_tcpSocket->localAddress(),
+ d->m_tcpSocket->localPort(), QKnxNetIp::HostProtocol::TCP_IPv4);
+
+ auto request = QKnxNetIpSessionRequestProxy::builder()
+ .setControlEndpoint(d->m_routeBack)
+ .setPublicKey(d->m_secureConfig.d->publicKey.bytes())
.create();
d->m_controlEndpointVersion = request.header().protocolVersion();
- qDebug() << "Sending connect request:" << request;
+ qDebug() << "Sending secure session request:" << request;
d->m_tcpSocket->write(request.bytes().toByteArray());
- });
- // TODO: Implement connect request timeout.
+ QObject::connect(d->m_secureTimer, &QTimer::timeout, this, [&]() {
+ Q_D(QKnxNetIpEndpointConnection);
+ d->m_secureTimer->stop();
+ d->setAndEmitErrorOccurred(QKnxNetIpEndpointConnection::Error::AuthFailed,
+ QKnxNetIpEndpointConnection::tr("Could not establish secure session."));
+ disconnectFromHost();
+ });
+ d->m_secureTimer->start(QKnxNetIp::SecureSessionRequestTimeout);
+ });
+ d->m_tcpSocket->connectToHost(address, port);
}
/*!
@@ -1076,12 +1428,24 @@ void QKnxNetIpEndpointConnection::disconnectFromHost()
} else {
auto frame = QKnxNetIpDisconnectRequestProxy::builder()
.setChannelId(d->m_channelId)
- .setControlEndpoint(d->m_nat ? d->m_natEndpoint : d->m_localEndpoint)
+ .setControlEndpoint(d->m_nat ? d->m_routeBack : d->m_localEndpoint)
.create();
qDebug() << "Sending disconnect request:" << frame;
if (d->m_tcpSocket) {
- d->m_tcpSocket->write(frame.bytes().toByteArray());
+ if (d->m_secureConfig.isValid()) {
+ auto secureFrame = QKnxNetIpSecureWrapperProxy::secureBuilder()
+ .setSecureSessionId(d->m_sessionId)
+ .setSequenceNumber(d->m_sequenceNumber)
+ .setSerialNumber(d->m_serialNumber)
+ // .setMessageTag(0x0000) TODO: Do we need an API for this?
+ .setEncapsulatedFrame(frame)
+ .create(d->m_sessionKey);
+ ++(d->m_sequenceNumber);
+ d->m_tcpSocket->write(secureFrame.bytes().toByteArray());
+ } else {
+ d->m_tcpSocket->write(frame.bytes().toByteArray());
+ }
} else {
d->m_udpSocket->writeDatagram(frame.bytes().toByteArray(),
d->m_remoteControlEndpoint.address, d->m_remoteControlEndpoint.port);
@@ -1092,6 +1456,9 @@ void QKnxNetIpEndpointConnection::disconnectFromHost()
}
}
+/*!
+ \internal
+*/
QKnxNetIpEndpointConnection::QKnxNetIpEndpointConnection(QKnxNetIpEndpointConnectionPrivate &dd,
QObject *parent)
: QObject(dd, parent)