swift
clientconnection.cpp
1 // SPDX-FileCopyrightText: Copyright (C) 2019 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
5 
6 #include <QNetworkDatagram>
7 
8 #include "config/buildconfig.h"
9 #include "misc/logmessage.h"
10 
11 using namespace swift::config;
12 using namespace swift::misc;
13 using namespace swift::core::afv::crypto;
14 
15 namespace swift::core::afv::connection
16 {
17  CClientConnection::CClientConnection(const QString &apiServer, QObject *parent)
18  : QObject(parent), m_udpSocket(new QUdpSocket(this)), m_voiceServerTimer(new QTimer(this)),
19  m_apiServerConnection(new CApiServerConnection(apiServer, this))
20  {
21  CLogMessage(this).debug(u"ClientConnection instantiated");
22 
23  // connect(&m_apiServerConnection, &ApiServerConnection::authenticationFinished, this,
24  // &ClientConnection::apiConnectionFinished); connect(&m_apiServerConnection,
25  // &ApiServerConnection::addCallsignFinished, this, &ClientConnection::addCallsignFinished);
26  // connect(&m_apiServerConnection, &ApiServerConnection::removeCallsignFinished, this,
27  // &ClientConnection::removeCallsignFinished);
28 
29  connect(m_voiceServerTimer, &QTimer::timeout, this,
30  &CClientConnection::voiceServerHeartbeat); // sends heartbeat to server
31  connect(m_udpSocket, &QUdpSocket::readyRead, this, &CClientConnection::readPendingDatagrams);
32  connect(m_udpSocket, &QAbstractSocket::errorOccurred, this, &CClientConnection::handleSocketError);
33  }
34 
35  void CClientConnection::connectTo(const QString &userName, const QString &password, const QString &callsign,
36  const QString &client, ConnectionCallback callback)
37  {
38  if (m_connection.isConnected())
39  {
40  CLogMessage(this).debug(u"Client already connected to network");
41  return;
42  }
43 
44  m_connection.setUserName(userName);
45  m_connection.setCallsign(callsign);
46 
47  QPointer<CClientConnection> myself(this);
48  m_apiServerConnection->connectTo(
49  userName, password, client, m_networkVersion,
50  { // callback called when connected
51  this, [=](bool authenticated) {
52  // callback when connection has been established
53  if (!myself) { return; } // cppcheck-suppress knownConditionTrueFalse
54 
55  if (authenticated)
56  {
57  const QString cs = m_connection.getCallsign();
58  m_connection.setTokens(m_apiServerConnection->addCallsign(cs));
59  m_connection.setTsAuthenticatedToNow();
60  m_connection.createCryptoChannels();
61  m_connection.setTsHeartbeatToNow();
62  this->connectToVoiceServer();
63  // taskServerConnectionCheck.Start();
64 
65  CLogMessage(this).info(u"Connected: '%1' to voice server, socket open: %2")
66  << cs << boolToYesNo(m_udpSocket->isOpen());
67  }
68  else { m_connection.reset(); }
69 
70  // Make sure crypto channels etc. are created
71  m_connection.setConnected(authenticated);
72 
73  // callback of the calling parent
74  if (callback) { callback(authenticated); }
75  } });
76  }
77 
78  void CClientConnection::disconnectFrom(const QString &reason)
79  {
80  if (!m_connection.isConnected())
81  {
82  CLogMessage(this).debug(u"Client not connected");
83  return;
84  }
85 
86  m_connection.setConnected(false);
87  // TODO emit disconnected(reason)
88  CLogMessage(this).debug(u"Disconnected client: %1") << reason;
89 
90  if (!m_connection.getCallsign().isEmpty())
91  {
92  m_apiServerConnection->removeCallsign(m_connection.getCallsign());
93  }
94 
95  // TODO connectionCheckCancelTokenSource.Cancel(); // Stops connection check loop
96  disconnectFromVoiceServer();
97  m_apiServerConnection->forceDisconnect();
98  m_connection.reset();
99 
100  CLogMessage(this).debug(u"Disconnection complete");
101  }
102 
103  void CClientConnection::updateTransceivers(const QString &callsign, const QVector<TransceiverDto> &transceivers)
104  {
105  m_apiServerConnection->updateTransceivers(callsign, transceivers);
106  }
107 
109  {
110  return m_apiServerConnection->getAllAliasedStations();
111  }
112 
114  {
115  if (!m_apiServerConnection) { return false; }
116  return m_apiServerConnection->setUrl(url);
117  }
118 
120  {
121  static const QString e;
122  if (!m_apiServerConnection) { return e; }
123  return m_apiServerConnection->getUrl();
124  }
125 
126  void CClientConnection::connectToVoiceServer()
127  {
128  const QHostAddress localAddress(QHostAddress::AnyIPv4);
129  m_udpSocket->bind(localAddress);
130  m_voiceServerTimer->start(3000);
131 
132  CLogMessage(this).info(u"Connected to voice server '%1'") << m_connection.getTokens().VoiceServer.addressIpV4;
133  }
134 
135  void CClientConnection::disconnectFromVoiceServer()
136  {
137  m_voiceServerTimer->stop();
138  m_udpSocket->disconnectFromHost();
139  CLogMessage(this).info(u"All TaskVoiceServer tasks stopped");
140  }
141 
142  void CClientConnection::readPendingDatagrams()
143  {
144  while (m_udpSocket->hasPendingDatagrams())
145  {
146  const QNetworkDatagram datagram = m_udpSocket->receiveDatagram();
147  this->processMessage(datagram.data());
148  }
149  }
150 
151  void CClientConnection::processMessage(const QByteArray &messageDdata, bool loopback)
152  {
153  if (!m_connection.m_voiceCryptoChannel)
154  {
155  SWIFT_VERIFY_X(false, Q_FUNC_INFO, "processMessage used without crypto channel");
156  return;
157  }
158 
159  CryptoDtoSerializer::Deserializer deserializer =
160  CryptoDtoSerializer::deserialize(*m_connection.m_voiceCryptoChannel, messageDdata, loopback);
161 
163  {
164  // qDebug() << "Received audio data";
165  const AudioRxOnTransceiversDto audioOnTransceiverDto = deserializer.getDto<AudioRxOnTransceiversDto>();
166  if (m_connection.isReceivingAudio() && m_connection.isConnected())
167  {
168  emit audioReceived(audioOnTransceiverDto);
169  }
170  }
171  else if (deserializer.m_dtoNameBuffer == HeartbeatAckDto::getShortDtoName())
172  {
173  m_connection.setTsHeartbeatToNow();
174  if (CBuildConfig::isLocalDeveloperDebugBuild())
175  {
176  CLogMessage(this).debug(u"Received voice server heartbeat");
177  }
178  }
179  else
180  {
181  CLogMessage(this).warning(u"Received unknown data: %1 %2")
182  << QString(deserializer.m_dtoNameBuffer) << deserializer.m_dataLength;
183  }
184  }
185 
186  void CClientConnection::handleSocketError(QAbstractSocket::SocketError error)
187  {
188  Q_UNUSED(error)
189  CLogMessage(this).debug(u"UDP socket error: '%1'") << m_udpSocket->errorString();
190  }
191 
192  void CClientConnection::voiceServerHeartbeat()
193  {
194  if (!m_connection.m_voiceCryptoChannel || !m_udpSocket)
195  {
196  SWIFT_VERIFY_X(false, Q_FUNC_INFO, "voiceServerHeartbeat used without crypto channel or socket");
197  return;
198  }
199 
200  const QUrl voiceServerUrl("udp://" + m_connection.getTokens().VoiceServer.addressIpV4);
201  if (CBuildConfig::isLocalDeveloperDebugBuild())
202  {
203  CLogMessage(this).debug(u"Sending voice server heartbeat to '%1'") << voiceServerUrl.host();
204  }
205  HeartbeatDto keepAlive;
206  keepAlive.callsign = m_connection.getCallsign().toStdString();
207  const QByteArray dataBytes = CryptoDtoSerializer::serialize(*m_connection.m_voiceCryptoChannel,
208  CryptoDtoMode::AEAD_ChaCha20Poly1305, keepAlive);
209  m_udpSocket->writeDatagram(dataBytes, QHostAddress(voiceServerUrl.host()),
210  static_cast<quint16>(voiceServerUrl.port()));
211  }
212 } // namespace swift::core::afv::connection
void removeCallsign(const QString &callsign)
Remove callsign from network.
PostCallsignResponseDto addCallsign(const QString &callsign)
Add callsign to network.
void forceDisconnect()
Force disconnect from network.
QVector< StationDto > getAllAliasedStations()
All aliased stations.
void connectTo(const QString &username, const QString &password, const QString &client, const QUuid &networkVersion, ConnectionCallback callback)
Connect to network.
void updateTransceivers(const QString &callsign, const QVector< TransceiverDto > &transceivers)
Update transceivers.
const PostCallsignResponseDto & getTokens() const
Tokens.
void createCryptoChannels()
Crypto channels for voice and data.
void setCallsign(const QString &callsign)
Callsign.
void setTokens(const PostCallsignResponseDto &dto)
Tokens.
QScopedPointer< crypto::CCryptoDtoChannel > m_voiceCryptoChannel
used crypto channel
void updateTransceivers(const QString &callsign, const QVector< TransceiverDto > &transceivers)
Update transceivers.
bool updateVoiceServerUrl(const QString &url)
Update the voice server URL.
void disconnectFrom(const QString &reason={})
Disconnect.
void audioReceived(const AudioRxOnTransceiversDto &dto)
Audio has been received.
QVector< StationDto > getAllAliasedStations()
All aliased stations.
const QString & getVoiceServerUrl() const
Get the voice server URL.
void connectTo(const QString &userName, const QString &password, const QString &callsign, const QString &client, ConnectionCallback callback)
Connect.
Class for emitting a log message.
Definition: logmessage.h:27
Derived & warning(const char16_t(&format)[N])
Set the severity to warning, providing a format string.
Derived & debug()
Set the severity to debug.
Derived & info(const char16_t(&format)[N])
Set the severity to info, providing a format string.
Callable wrapper for a member function with function signature F.
Definition: slot.h:62
Free functions in swift::misc.
static QByteArray getShortDtoName()
Names.
Definition: dto.h:260
static QByteArray getShortDtoName()
Name.
Definition: dto.h:198
VoiceServerConnectionDataDto VoiceServer
Properties.
Definition: dto.h:91
#define SWIFT_VERIFY_X(COND, WHERE, WHAT)
A weaker kind of assert.
Definition: verify.h:26