diff options
Diffstat (limited to 'src/knx/netip/qknxnetipendpointconnection.cpp')
-rw-r--r-- | src/knx/netip/qknxnetipendpointconnection.cpp | 707 |
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) |