/* Copyright (C) 2019 * swift project Community / Contributors * * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, * or distributed except according to the terms contained in the LICENSE file. */ #include "clientconnection.h" #include "blackmisc/logmessage.h" #include using namespace BlackMisc; using namespace BlackCore::Afv::Crypto; namespace BlackCore { namespace Afv { namespace Connection { CClientConnection::CClientConnection(const QString &apiServer, QObject *parent) : QObject(parent), m_udpSocket(new QUdpSocket(this)), m_voiceServerTimer(new QTimer(this)), m_apiServerConnection(new CApiServerConnection(apiServer, this)) { CLogMessage(this).debug(u"ClientConnection instantiated"); // connect(&m_apiServerConnection, &ApiServerConnection::authenticationFinished, this, &ClientConnection::apiConnectionFinished); // connect(&m_apiServerConnection, &ApiServerConnection::addCallsignFinished, this, &ClientConnection::addCallsignFinished); // connect(&m_apiServerConnection, &ApiServerConnection::removeCallsignFinished, this, &ClientConnection::removeCallsignFinished); connect(m_voiceServerTimer, &QTimer::timeout, this, &CClientConnection::voiceServerHeartbeat); connect(m_udpSocket, &QUdpSocket::readyRead, this, &CClientConnection::readPendingDatagrams); connect(m_udpSocket, qOverload(&QUdpSocket::error), this, &CClientConnection::handleSocketError); } void CClientConnection::connectTo(const QString &userName, const QString &password, const QString &callsign) { if (m_connection.isConnected()) { CLogMessage(this).debug(u"Client already connected"); return; } m_connection.setUserName(userName); m_connection.setCallsign(callsign); bool result = m_apiServerConnection->connectTo(userName, password, m_networkVersion); if (!result) { return; } m_connection.setTokens(m_apiServerConnection->addCallsign(m_connection.getCallsign())); m_connection.setTsAuthenticatedToNow(); m_connection.createCryptoChannels(); connectToVoiceServer(); // taskServerConnectionCheck.Start(); m_connection.setConnected(true); CLogMessage(this).debug(u"Connected: '%1'") << callsign; } void CClientConnection::disconnectFrom(const QString &reason) { if (!m_connection.isConnected()) { CLogMessage(this).debug(u"Client not connected"); return; } m_connection.setConnected(false); // TODO emit disconnected(reason) CLogMessage(this).debug(u"Disconnected client: %1") << reason; if (! m_connection.getCallsign().isEmpty()) { m_apiServerConnection->removeCallsign(m_connection.getCallsign()); } // TODO connectionCheckCancelTokenSource.Cancel(); //Stops connection check loop disconnectFromVoiceServer(); m_apiServerConnection->forceDisconnect(); m_connection.setTokens({}); CLogMessage(this).debug(u"Disconnection complete"); } void CClientConnection::updateTransceivers(const QString &callsign, const QVector &transceivers) { m_apiServerConnection->updateTransceivers(callsign, transceivers); } QVector CClientConnection::getAllAliasedStations() { return m_apiServerConnection->getAllAliasedStations(); } bool CClientConnection::updateVoiceServerUrl(const QString &url) { if (!m_apiServerConnection) { return false; } return m_apiServerConnection->setUrl(url); } void CClientConnection::connectToVoiceServer() { QHostAddress localAddress(QHostAddress::AnyIPv4); m_udpSocket->bind(localAddress); m_voiceServerTimer->start(3000); CLogMessage(this).info(u"Connected to voice server '%1'") << m_connection.getTokens().VoiceServer.addressIpV4; } void CClientConnection::disconnectFromVoiceServer() { m_voiceServerTimer->stop(); m_udpSocket->disconnectFromHost(); CLogMessage(this).info(u"All TaskVoiceServer tasks stopped"); } void CClientConnection::readPendingDatagrams() { while (m_udpSocket->hasPendingDatagrams()) { const QNetworkDatagram datagram = m_udpSocket->receiveDatagram(); this->processMessage(datagram.data()); } } void CClientConnection::processMessage(const QByteArray &messageDdata, bool loopback) { CryptoDtoSerializer::Deserializer deserializer = CryptoDtoSerializer::deserialize(*m_connection.m_voiceCryptoChannel, messageDdata, loopback); if (deserializer.dtoNameBuffer == AudioRxOnTransceiversDto::getShortDtoName()) { // qDebug() << "Received audio data"; const AudioRxOnTransceiversDto audioOnTransceiverDto = deserializer.getDto(); if (m_connection.isReceivingAudio() && m_connection.isConnected()) { emit audioReceived(audioOnTransceiverDto); } } else if (deserializer.dtoNameBuffer == HeartbeatAckDto::getShortDtoName()) { m_connection.setTsHeartbeatToNow(); CLogMessage(this).debug(u"Received voice server heartbeat"); } else { CLogMessage(this).warning(u"Received unknown data: %1 %2") << QString(deserializer.dtoNameBuffer) << deserializer.dataLength; } } void CClientConnection::handleSocketError(QAbstractSocket::SocketError error) { Q_UNUSED(error) CLogMessage(this).debug(u"UDP socket error: '%1'") << m_udpSocket->errorString(); } void CClientConnection::voiceServerHeartbeat() { const QUrl voiceServerUrl("udp://" + m_connection.getTokens().VoiceServer.addressIpV4); CLogMessage(this).debug(u"Sending voice server heartbeat to '%1'") << voiceServerUrl.host(); HeartbeatDto keepAlive; keepAlive.callsign = m_connection.getCallsign().toStdString(); const QByteArray dataBytes = CryptoDtoSerializer::serialize(*m_connection.m_voiceCryptoChannel, CryptoDtoMode::AEAD_ChaCha20Poly1305, keepAlive); m_udpSocket->writeDatagram(dataBytes, QHostAddress(voiceServerUrl.host()), static_cast(voiceServerUrl.port())); } } // ns } // ns } // ns