swift
fsdclient.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 
4 #include "core/fsd/fsdclient.h"
5 
6 #include <QHostAddress>
7 #include <QMetaEnum>
8 #include <QNetworkReply>
9 #include <QStringView>
10 
11 #include "config/buildconfig.h"
12 #include "core/application.h"
13 #include "core/fsd/addatc.h"
14 #include "core/fsd/addpilot.h"
15 #include "core/fsd/atcdataupdate.h"
16 #include "core/fsd/authchallenge.h"
17 #include "core/fsd/authresponse.h"
19 #include "core/fsd/clientquery.h"
21 #include "core/fsd/deleteatc.h"
22 #include "core/fsd/deletepilot.h"
24 #include "core/fsd/flightplan.h"
27 #include "core/fsd/killrequest.h"
28 #include "core/fsd/mute.h"
30 #include "core/fsd/ping.h"
35 #include "core/fsd/pong.h"
36 #include "core/fsd/rehost.h"
37 #include "core/fsd/serializer.h"
38 #include "core/fsd/servererror.h"
39 #include "core/fsd/textmessage.h"
45 #include "misc/logmessage.h"
47 #include "misc/range.h"
48 #include "misc/swiftdirectories.h"
49 #include "misc/threadutils.h"
50 #include "misc/verify.h"
51 
52 using namespace swift::config;
53 using namespace swift::core::vatsim;
54 using namespace swift::misc;
55 using namespace swift::misc::aviation;
56 using namespace swift::misc::geo;
57 using namespace swift::misc::json;
58 using namespace swift::misc::network;
59 using namespace swift::misc::physical_quantities;
60 using namespace swift::misc::simulation;
61 
62 namespace swift::core::fsd
63 {
64  QString convertToUnicodeEscaped(const QString &str)
65  {
66  QString escaped;
67  for (const auto &ch : str)
68  {
69  const ushort code = ch.unicode();
70  if (code < 0x80) { escaped += ch; }
71  else { escaped += "\\u" % QString::number(code, 16).rightJustified(4, '0'); }
72  }
73  return escaped;
74  }
75 
76  const QStringList &CFSDClient::getLogCategories()
77  {
78  static const QStringList cats = [] {
79  QStringList cl = CContinuousWorker::getLogCategories();
80  cl.push_back(CLogCategories::network());
81  cl.push_back(CLogCategories::fsd());
82  return cl;
83  }();
84  return cats;
85  }
86 
87  // NOLINTBEGIN(cppcoreguidelines-pro-type-member-init)
88  CFSDClient::CFSDClient(IClientProvider *clientProvider, IOwnAircraftProvider *ownAircraftProvider,
89  IRemoteAircraftProvider *remoteAircraftProvider, QObject *owner)
90  : CContinuousWorker(owner, "FSDClient"), CClientAware(clientProvider), COwnAircraftAware(ownAircraftProvider),
91  CRemoteAircraftAware(remoteAircraftProvider), m_tokenBucket(10, 5000, 1)
92  {
93  // NOLINTEND(cppcoreguidelines-pro-type-member-init)
94 
95  initializeMessageTypes();
96  connectSocketSignals();
97 
98  m_positionUpdateTimer.setObjectName(this->objectName().append(":m_positionUpdateTimer"));
99  connect(&m_positionUpdateTimer, &QTimer::timeout, this, &CFSDClient::sendPilotDataUpdate);
100 
101  m_interimPositionUpdateTimer.setObjectName(this->objectName().append(":m_interimPositionUpdateTimer"));
102  connect(&m_interimPositionUpdateTimer, &QTimer::timeout, this, &CFSDClient::sendInterimPilotDataUpdate);
103 
104  m_visualPositionUpdateTimer.setObjectName(this->objectName().append(":m_visualPositionUpdateTimer"));
105  connect(&m_visualPositionUpdateTimer, &QTimer::timeout, this, [this] { sendVisualPilotDataUpdate(); });
106 
107  m_scheduledConfigUpdate.setObjectName(this->objectName().append(":m_scheduledConfigUpdate"));
108  connect(&m_scheduledConfigUpdate, &QTimer::timeout, this, &CFSDClient::sendIncrementalAircraftConfig);
109 
110  m_fsdSendMessageTimer.setObjectName(this->objectName().append(":m_fsdSendMessageTimer"));
111  connect(&m_fsdSendMessageTimer, &QTimer::timeout, this, [this]() { this->sendQueuedMessage(); });
112 
113  fsdMessageSettingsChanged();
114 
115  if (!m_statistics &&
116  (CBuildConfig::isLocalDeveloperDebugBuild() || (sApp && sApp->getOwnDistribution().isRestricted())))
117  {
118  CLogMessage(this).info(u"Enabled network statistics");
119  m_statistics = true;
120  }
121  }
122 
123  void CFSDClient::connectSocketSignals()
124  {
125  connect(m_socket.get(), &QTcpSocket::readyRead, this, &CFSDClient::readDataFromSocket, Qt::QueuedConnection);
126  connect(m_socket.get(), &QTcpSocket::connected, this, &CFSDClient::handleSocketConnected);
127  connect(m_socket.get(), &QTcpSocket::errorOccurred, this, &CFSDClient::printSocketError, Qt::QueuedConnection);
128  connect(m_socket.get(), &QTcpSocket::errorOccurred, this, &CFSDClient::handleSocketError, Qt::QueuedConnection);
129  }
130 
131 #ifdef SWIFT_VATSIM_SUPPORT
132  void CFSDClient::setClientIdAndKey(quint16 id, const QByteArray &key)
133  {
134  QWriteLocker l(&m_lockUserClientBuffered);
135  m_clientAuth = vatsim_auth_create(id, qPrintable(key));
136  m_serverAuth = vatsim_auth_create(id, qPrintable(key));
137  }
138 #endif
139 
140  void CFSDClient::setServer(const CServer &server)
141  {
142  Q_ASSERT_X(this->getConnectionStatus().isDisconnected(), Q_FUNC_INFO,
143  "Can't change server details while still connected");
144 
145  const QString codecName(server.getFsdSetup().getTextCodec());
146  auto codec = QStringDecoder::encodingForName(codecName);
147  if (!codec.has_value()) { codec = QStringConverter::Utf8; }
148  const int protocolRev = (server.getServerType() == CServer::FSDServerVatsim) ?
151 
152  QWriteLocker l(&m_lockUserClientBuffered);
153  m_server = server;
154  m_protocolRevision = protocolRev;
155  m_encoder = QStringEncoder(codec.value_or(QStringConverter::Utf8));
156  m_decoder = QStringDecoder(codec.value_or(QStringConverter::Utf8));
157  }
158 
159  void CFSDClient::setCallsign(const CCallsign &callsign)
160  {
161  Q_ASSERT_X(this->getConnectionStatus().isDisconnected(), Q_FUNC_INFO,
162  "Can't change callsign while still connected");
163  updateOwnCallsign(callsign);
164 
165  QWriteLocker l(&m_lockUserClientBuffered);
166  m_ownCallsign = callsign;
167  }
168 
170  {
171  Q_ASSERT_X(this->getConnectionStatus().isDisconnected(), Q_FUNC_INFO,
172  "Can't change ICAO codes while still connected");
173  updateOwnIcaoCodes(ownAircraft.getAircraftIcaoCode(), ownAircraft.getAirlineIcaoCode());
174 
175  QWriteLocker l(&m_lockUserClientBuffered);
176  m_ownAircraftIcaoCode = ownAircraft.getAircraftIcaoCode();
177  m_ownAirlineIcaoCode = ownAircraft.getAirlineIcaoCode();
178 
179  /* use setLiveryAndModelString
180  No longer do it here, use setLiveryAndModelString
181  m_ownLivery = ownAircraft.getModel().getSwiftLiveryString(m_simTypeInfo);
182  m_ownModelString = ownAircraft.getModelString();
183  m_sendLiveryString = true;
184  m_sendModelString = true;
185  */
186  }
187 
188  void CFSDClient::setLiveryAndModelString(const QString &livery, bool sendLiveryString, const QString &modelString,
189  bool sendModelString)
190  {
191  QWriteLocker l(&m_lockUserClientBuffered);
192  m_ownLivery = livery;
193  m_ownModelString = modelString;
194  m_sendLiveryString = sendLiveryString;
195  m_sendModelString = sendModelString;
196  }
197 
198  void CFSDClient::setSimType(const CSimulatorInfo &simInfo) { this->setSimType(simInfo.getSimulator()); }
199 
200  void CFSDClient::setSimType(swift::misc::simulation::CSimulatorInfo::Simulator simulator)
201  {
202  QWriteLocker l(&m_lockUserClientBuffered);
203  switch (simulator)
204  {
205  case CSimulatorInfo::FSX: m_simType = SimType::MSFSX; break;
206  case CSimulatorInfo::P3D: m_simType = SimType::P3Dv4; break;
207  case CSimulatorInfo::FS9: m_simType = SimType::MSFS2004; break;
208  case CSimulatorInfo::FG: m_simType = SimType::FlightGear; break;
209  case CSimulatorInfo::XPLANE: m_simType = SimType::XPLANE11; break;
210  case CSimulatorInfo::MSFS: m_simType = SimType::MSFS; break;
211  case CSimulatorInfo::MSFS2024: m_simType = SimType::MSFS2024; break;
212  default: m_simType = SimType::Unknown; break;
213  }
214  m_simTypeInfo = CSimulatorInfo(simulator);
215  }
216 
218  {
219  QReadLocker l(&m_lockUserClientBuffered);
220  const QStringList v = { m_ownModelString,
221  m_ownLivery,
222  m_ownAircraftIcaoCode.getDesignator(),
223  m_ownAirlineIcaoCode.getVDesignator(),
224  m_ownCallsign.asString(),
225  m_partnerCallsign.asString() };
226  return v;
227  }
228 
230  {
231  if (!CThreadUtils::isInThisThread(this))
232  {
233  QMetaObject::invokeMethod(this, [=, this] {
234  if (sApp && !sApp->isShuttingDown()) { connectToServer(); }
235  });
236  return;
237  }
238 
239  if (m_socket->isOpen()) { return; }
240  Q_ASSERT(!m_clientName.isEmpty());
241  Q_ASSERT((m_versionMajor + m_versionMinor) > 0);
242  Q_ASSERT(m_capabilities != Capabilities::None);
243 
244  if (m_hostApplication.isEmpty()) { m_hostApplication = this->getSimulatorNameAndVersion().replace(':', ' '); }
245 
246  this->clearState();
247  m_filterPasswordFromLogin = true;
248 
249  m_loginSince = QDateTime::currentMSecsSinceEpoch();
250  const qint64 timerMs = qRound(PendingConnectionTimeoutMs * 1.25);
251 
252  const QPointer<CFSDClient> myself(this);
253  QTimer::singleShot(timerMs, this, [=, this] {
254  if (!myself || !sApp || sApp->isShuttingDown()) { return; }
255  this->pendingTimeoutCheck();
256  });
257 
258  this->updateConnectionStatus(CConnectionStatus::Connecting);
259 
260  initiateConnection();
261  }
262 
264  {
265  if (!CThreadUtils::isInThisThread(this))
266  {
267  QMetaObject::invokeMethod(this, [=, this] {
268  if (sApp && !sApp->isShuttingDown()) { disconnectFromServer(); }
269  });
270  return;
271  }
272 
273  this->stopPositionTimers();
274  this->updateConnectionStatus(CConnectionStatus::Disconnecting);
275 
276  // allow also to close if broken
277  CLoginMode mode = this->getLoginMode();
278  if (m_socket->isOpen())
279  {
280  if (mode.isPilot()) { this->sendDeletePilot(); }
281  else if (mode.isObserver()) { this->sendDeleteAtc(); }
282  }
283  m_socket->close();
284 
285  this->updateConnectionStatus(CConnectionStatus::Disconnected);
286  this->clearState();
287  }
288 
289  void CFSDClient::sendLogin(const QString &token)
290  {
291  const CServer s = this->getServer();
292  const QString cid = s.getUser().getId();
293  const QString password = token.isEmpty() ? s.getUser().getPassword() : token;
294  const QString name = s.getUser().getRealNameAndHomeBase(); // m_server.getUser().getRealName();
295  const QString callsign = m_ownCallsign.asString();
296 
297  const CLoginMode m = this->getLoginMode();
298  if (m.isPilot())
299  {
300  const AddPilot pilotLogin(callsign, cid, password, m_pilotRating, m_protocolRevision, m_simType, name);
301  sendQueuedMessage(pilotLogin);
302  CStatusMessage(this).info(u"Sending login as '%1' '%2' '%3' '%4' '%5' '%6'")
303  << callsign << cid << toQString(m_pilotRating) << m_protocolRevision << toQString(m_simType) << name;
304  }
305  else if (m.isObserver())
306  {
307  const AddAtc addAtc(callsign, name, cid, password, m_atcRating, m_protocolRevision);
308  sendQueuedMessage(addAtc);
309  CStatusMessage(this).info(u"Sending OBS login as '%1' '%2' '%3' '%4' '%5'")
310  << callsign << cid << toQString(m_atcRating) << m_protocolRevision << name;
311  }
312 
313  if (m_server.getFsdSetup().receiveEuroscopeSimData())
314  {
315  this->sendClientQuery(ClientQueryType::EuroscopeSimData, {}, {});
316  }
317  }
318 
319  void CFSDClient::sendDeletePilot()
320  {
321  const QString cid = this->getServer().getUser().getId();
322  const DeletePilot deletePilot(m_ownCallsign.getFsdCallsignString(), cid);
323  sendQueuedMessage(deletePilot);
324  }
325 
326  void CFSDClient::sendDeleteAtc()
327  {
328  const QString cid = this->getServer().getUser().getId();
329  const DeleteAtc deleteAtc(getOwnCallsignAsString(), cid);
330  sendQueuedMessage(deleteAtc);
331  }
332 
333  void CFSDClient::sendPilotDataUpdate()
334  {
335  if (this->getConnectionStatus().isDisconnected() && !m_unitTestMode) { return; }
336  const CSimulatedAircraft myAircraft(getOwnAircraft());
337  if (m_loginMode == CLoginMode::Observer)
338  {
339  sendAtcDataUpdate(myAircraft.latitude().value(CAngleUnit::deg()),
340  myAircraft.longitude().value(CAngleUnit::deg()));
341  }
342  else
343  {
344  if (this->isVisualPositionSendingEnabledForServer())
345  {
346  // Slowfast must be sent before the ordinary update.
347  // Sending after causes a server performance issue.
348  // See https://discord.com/channels/775552633918062643/775736423319732256/960746661259390996
349  sendVisualPilotDataUpdate(true);
350  }
351 
352  PilotRating r = this->getPilotRating();
353  PilotDataUpdate pilotDataUpdate(
354  myAircraft.getTransponderMode(), getOwnCallsignAsString(),
355  static_cast<qint16>(myAircraft.getTransponderCode()), r, myAircraft.latitude().value(CAngleUnit::deg()),
356  myAircraft.longitude().value(CAngleUnit::deg()),
357  myAircraft.getAltitude().valueInteger(CLengthUnit::ft()),
358  myAircraft.getPressureAltitude().valueInteger(CLengthUnit::ft()),
359  myAircraft.getGroundSpeed().valueInteger(CSpeedUnit::kts()),
360  myAircraft.getPitch().value(CAngleUnit::deg()), myAircraft.getBank().value(CAngleUnit::deg()),
361  myAircraft.getHeading().normalizedTo360Degrees().value(CAngleUnit::deg()),
362  myAircraft.getParts().isOnGround());
363  sendQueuedMessage(pilotDataUpdate);
364  }
365  }
366 
367  void CFSDClient::sendInterimPilotDataUpdate()
368  {
369  if (this->getConnectionStatus().isDisconnected()) { return; }
370  const CSimulatedAircraft myAircraft(getOwnAircraft());
371  InterimPilotDataUpdate interimPilotDataUpdate(
372  getOwnCallsignAsString(), QString(), myAircraft.latitude().value(CAngleUnit::deg()),
373  myAircraft.longitude().value(CAngleUnit::deg()), myAircraft.getAltitude().valueInteger(CLengthUnit::ft()),
374  myAircraft.getGroundSpeed().valueInteger(CSpeedUnit::kts()), myAircraft.getPitch().value(CAngleUnit::deg()),
375  myAircraft.getBank().value(CAngleUnit::deg()),
376  myAircraft.getHeading().normalizedTo360Degrees().value(CAngleUnit::deg()),
377  myAircraft.getParts().isOnGround());
378 
379  for (const auto &receiver : std::as_const(m_interimPositionReceivers))
380  {
381  interimPilotDataUpdate.setReceiver(receiver.asString());
382  sendQueuedMessage(interimPilotDataUpdate);
383  // statistics
384  }
385  }
386 
387  void CFSDClient::sendVisualPilotDataUpdate(bool slowUpdate)
388  {
389  if (this->getConnectionStatus().isDisconnected() && !m_unitTestMode) { return; }
390  if (m_loginMode == CLoginMode::Observer || !isVisualPositionSendingEnabledForServer()) { return; }
391  const CSimulatedAircraft myAircraft(getOwnAircraft());
392 
393  if (!slowUpdate)
394  {
395  static constexpr double minVelocity = 0.00005;
396  if (std::abs(myAircraft.getVelocity().getVelocityX(CSpeedUnit::m_s())) < minVelocity &&
397  std::abs(myAircraft.getVelocity().getVelocityY(CSpeedUnit::m_s())) < minVelocity &&
398  std::abs(myAircraft.getVelocity().getVelocityZ(CSpeedUnit::m_s())) < minVelocity &&
399  std::abs(myAircraft.getVelocity().getPitchVelocity(CAngleUnit::rad(), CTimeUnit::s())) < minVelocity &&
400  std::abs(myAircraft.getVelocity().getRollVelocity(CAngleUnit::rad(), CTimeUnit::s())) < minVelocity &&
401  std::abs(myAircraft.getVelocity().getHeadingVelocity(CAngleUnit::rad(), CTimeUnit::s())) < minVelocity)
402  {
403  if (m_stoppedSendingVisualPositions) { return; }
404  m_stoppedSendingVisualPositions = true;
405  m_visualPositionUpdateSentCount = 0;
406  }
407  else { m_stoppedSendingVisualPositions = false; }
408 
409  if (!m_serverWantsVisualPositions) { return; }
410  }
411  VisualPilotDataUpdate visualPilotDataUpdate(
412  getOwnCallsignAsString(), myAircraft.latitude().value(CAngleUnit::deg()),
413  myAircraft.longitude().value(CAngleUnit::deg()), myAircraft.getAltitude().value(CLengthUnit::ft()),
414  myAircraft.getAltitude().value(CLengthUnit::ft()) -
415  myAircraft.getGroundElevation().value(CLengthUnit::ft()),
416  myAircraft.getPitch().value(CAngleUnit::deg()), myAircraft.getBank().value(CAngleUnit::deg()),
417  myAircraft.getHeading().normalizedTo360Degrees().value(CAngleUnit::deg()),
418  myAircraft.getVelocity().getVelocityX(CSpeedUnit::m_s()),
419  myAircraft.getVelocity().getVelocityY(CSpeedUnit::m_s()),
420  myAircraft.getVelocity().getVelocityZ(CSpeedUnit::m_s()),
421  myAircraft.getVelocity().getPitchVelocity(CAngleUnit::rad(), CTimeUnit::s()),
422  myAircraft.getVelocity().getRollVelocity(CAngleUnit::rad(), CTimeUnit::s()),
423  myAircraft.getVelocity().getHeadingVelocity(CAngleUnit::rad(), CTimeUnit::s()));
424 
425  if (m_stoppedSendingVisualPositions) { sendQueuedMessage(visualPilotDataUpdate.toStopped()); }
426  else if (m_visualPositionUpdateSentCount++ % 25 == 0) { sendQueuedMessage(visualPilotDataUpdate.toPeriodic()); }
427  else { sendQueuedMessage(visualPilotDataUpdate); }
428  }
429 
430  void CFSDClient::sendAtcDataUpdate(double latitude, double longitude)
431  {
432  const AtcDataUpdate atcDataUpdate(getOwnCallsignAsString(), 199998, CFacilityType::OBS, 300,
433  AtcRating::Observer, latitude, longitude, 0);
434  sendQueuedMessage(atcDataUpdate);
435  }
436 
437  void CFSDClient::sendPing(const QString &receiver)
438  {
439  const qint64 msecSinceEpoch = QDateTime::currentMSecsSinceEpoch();
440  const QString timeString = QString::number(msecSinceEpoch);
441 
442  const Ping ping(getOwnCallsignAsString(), receiver, timeString);
443  sendQueuedMessage(ping);
444 
445  // statistics
446  increaseStatisticsValue(QStringLiteral("sendPing"));
447  }
448 
449  void CFSDClient::sendClientQueryIsValidAtc(const CCallsign &callsign)
450  {
451  sendClientQuery(ClientQueryType::IsValidATC, {}, { callsign.asString() });
452  }
453 
454  void CFSDClient::sendClientQueryCapabilities(const CCallsign &callsign)
455  {
456  sendClientQuery(ClientQueryType::Capabilities, callsign);
457  }
458 
460  {
461  sendClientQuery(ClientQueryType::Com1Freq, callsign);
462  }
463 
465  {
466  sendClientQuery(ClientQueryType::RealName, callsign);
467  }
468 
470  {
471  sendClientQuery(ClientQueryType::Server, callsign);
472  }
473 
475  {
476  sendClientQuery(ClientQueryType::ATIS, callsign);
477  }
478 
480  {
481  sendClientQuery(ClientQueryType::FP, {}, { callsign.toQString() });
482  }
483 
485  {
486  QString data = QJsonDocument(JsonPackets::aircraftConfigRequest()).toJson(QJsonDocument::Compact);
487  data = convertToUnicodeEscaped(data);
488  sendClientQuery(ClientQueryType::AircraftConfig, callsign, { data });
489  }
490 
491  void CFSDClient::sendClientQuery(ClientQueryType queryType, const CCallsign &receiver, const QStringList &queryData)
492  {
493  if (queryType == ClientQueryType::Unknown) { return; }
494  if (!CThreadUtils::isInThisThread(this))
495  {
496  QMetaObject::invokeMethod(this, [=, this] {
497  if (sApp && !sApp->isShuttingDown()) { sendClientQuery(queryType, receiver, queryData); }
498  });
499  return;
500  }
501 
502  const QString receiverCallsign = receiver.getFsdCallsignString();
503  if (queryType == ClientQueryType::IsValidATC)
504  {
505  const ClientQuery clientQuery(getOwnCallsignAsString(), "SERVER", ClientQueryType::IsValidATC, queryData);
506  sendQueuedMessage(clientQuery);
507  }
508  else if (queryType == ClientQueryType::Capabilities)
509  {
510  const ClientQuery clientQuery(getOwnCallsignAsString(), receiverCallsign, ClientQueryType::Capabilities);
511  sendQueuedMessage(clientQuery);
512  }
513  else if (queryType == ClientQueryType::Com1Freq)
514  {
515  const ClientQuery clientQuery(getOwnCallsignAsString(), receiverCallsign, ClientQueryType::Com1Freq);
516  sendQueuedMessage(clientQuery);
517  }
518  else if (queryType == ClientQueryType::RealName)
519  {
520  const ClientQuery clientQuery(getOwnCallsignAsString(), receiverCallsign, ClientQueryType::RealName);
521  sendQueuedMessage(clientQuery);
522  }
523  else if (queryType == ClientQueryType::Server)
524  {
525  ClientQuery clientQuery(getOwnCallsignAsString(), receiverCallsign, ClientQueryType::Server);
526  sendQueuedMessage(clientQuery);
527  }
528  else if (queryType == ClientQueryType::ATIS)
529  {
530  const ClientQuery clientQuery(getOwnCallsignAsString(), receiverCallsign, ClientQueryType::ATIS);
531  sendQueuedMessage(clientQuery);
532  if (m_serverType != ServerType::Vatsim) { m_pendingAtisQueries.insert(receiver, {}); }
533  }
534  else if (queryType == ClientQueryType::PublicIP)
535  {
536  const ClientQuery clientQuery(getOwnCallsignAsString(), receiverCallsign, ClientQueryType::PublicIP);
537  sendQueuedMessage(clientQuery);
538  }
539  else if (queryType == ClientQueryType::INF)
540  {
541  const ClientQuery clientQuery(getOwnCallsignAsString(), receiverCallsign, ClientQueryType::INF);
542  sendQueuedMessage(clientQuery);
543  }
544  else if (queryType == ClientQueryType::FP)
545  {
546  if (queryData.isEmpty()) { return; }
547  const ClientQuery clientQuery(getOwnCallsignAsString(), "SERVER", ClientQueryType::FP, queryData);
548  sendQueuedMessage(clientQuery);
549  }
550  else if (queryType == ClientQueryType::AircraftConfig)
551  {
552  if (queryData.isEmpty()) { return; }
553  const ClientQuery clientQuery(getOwnCallsignAsString(), receiverCallsign, ClientQueryType::AircraftConfig,
554  queryData);
555  sendQueuedMessage(clientQuery);
556  }
557  else if (queryType == ClientQueryType::EuroscopeSimData)
558  {
559  const ClientQuery clientQuery(getOwnCallsignAsString(), "@94835", ClientQueryType::EuroscopeSimData,
560  { "1" });
561  sendQueuedMessage(clientQuery);
562  }
563 
564  increaseStatisticsValue(QStringLiteral("sendClientQuery"), toQString(queryType));
565  }
566 
568  {
569  if (messages.isEmpty()) { return; }
570  if (!CThreadUtils::isInThisThread(this))
571  {
572  QMetaObject::invokeMethod(this, [=, this] {
573  if (sApp && !sApp->isShuttingDown()) { sendTextMessages(messages); }
574  });
575  return;
576  }
577 
578  const CTextMessageList privateMessages = messages.getPrivateMessages().markedAsSent();
579  const QString ownCallsign = getOwnCallsignAsString();
580 
581  for (const auto &message : privateMessages)
582  {
583  if (message.getRecipientCallsign().isEmpty()) { continue; }
584  const TextMessage textMessage(ownCallsign, message.getRecipientCallsign().getFsdCallsignString(),
585  message.getMessage());
586  sendQueuedMessage(textMessage);
587  increaseStatisticsValue(QStringLiteral("sendTextMessages.PM"));
588  emit textMessageSent(message);
589  }
590 
591  const CTextMessageList radioMessages = messages.getRadioMessages().markedAsSent();
592  QVector<int> frequencies;
593  for (const auto &message : radioMessages)
594  {
595  // Adjust to nearest frequency, in case of 5kHz difference
596  const CAtcStationList stations = m_atcStations.findIfFrequencyIsWithinSpacing(message.getFrequency());
597  const CFrequency freq = stations.isEmpty() ?
598  message.getFrequency() :
599  stations.findClosest(1, getOwnAircraftPosition()).front().getFrequency();
600 
601  // I could send the same message to n frequencies in one step
602  // if this is really required, I need to group by message
603  // currently I send individual messages
604  frequencies.clear();
605  int freqkHz = freq.valueInteger(CFrequencyUnit::kHz());
606  frequencies.push_back(freqkHz);
607  sendRadioMessage(frequencies, message.getMessage());
608  increaseStatisticsValue(QStringLiteral("sendTextMessages.FREQ"));
609  emit textMessageSent(message);
610  }
611  }
612 
614  {
615  if (message.isEmpty()) { return; }
616  sendTextMessages({ message });
617  }
618 
619  void CFSDClient::sendTextMessage(TextMessageGroups receiverGroup, const QString &message)
620  {
621  if (message.isEmpty()) { return; }
622  if (!CThreadUtils::isInThisThread(this))
623  {
624  QMetaObject::invokeMethod(this, [=, this] {
625  if (sApp && !sApp->isShuttingDown()) { sendTextMessage(receiverGroup, message); }
626  });
627  return;
628  }
629 
630  QString receiver;
631  if (receiverGroup == TextMessageGroups::AllClients) { receiver = '*'; }
632  else if (receiverGroup == TextMessageGroups::AllAtcClients) { receiver = QStringLiteral("*A"); }
633  else if (receiverGroup == TextMessageGroups::AllPilotClients) { receiver = QStringLiteral("*P"); }
634  else if (receiverGroup == TextMessageGroups::AllSups) { receiver = QStringLiteral("*S"); }
635  else { return; }
636  const TextMessage textMessage(getOwnCallsignAsString(), receiver, message);
637  sendQueuedMessage(textMessage);
638  if (receiver == QStringLiteral("*S"))
639  {
640  const CCallsign sender(getOwnCallsignAsString());
641  const CCallsign recipient(receiver);
642  CTextMessage t(message, sender, recipient);
643  t.markAsSent();
644  emit textMessageSent(t);
645  }
646  increaseStatisticsValue(QStringLiteral("sendTextMessages"));
647  }
648 
649  void CFSDClient::sendTextMessage(const QString &receiver, const QString &message)
650  {
651  const CTextMessage msg(message, getOwnCallsign(), { receiver });
652  sendTextMessage(msg);
653  }
654 
655  void CFSDClient::sendRadioMessage(const QVector<int> &frequencieskHz, const QString &message)
656  {
658  for (const int &frequency : frequencieskHz)
659  {
660  receivers.push_back(QStringLiteral("@%1").arg(frequency - 100000));
661  }
662 
663  const TextMessage radioMessage(getOwnCallsignAsString(), receivers.join('&'), message);
664  sendQueuedMessage(radioMessage);
665  increaseStatisticsValue(QStringLiteral("sendTextMessages"));
666  }
667 
668  void CFSDClient::sendFlightPlan(const CFlightPlan &flightPlan)
669  {
670  if (!CThreadUtils::isInThisThread(this))
671  {
672  QMetaObject::invokeMethod(this, [=, this] {
673  if (sApp && !sApp->isShuttingDown()) { sendFlightPlan(flightPlan); }
674  });
675  return;
676  }
677 
678  // Removed with T353 although it is standard
679  // const QString route = QString(flightPlan.getRoute()).replace(" ", ".");
680 
681  QString route = flightPlan.getRoute();
682  QString remarks = flightPlan.getRemarks();
683  route.remove(':');
684  remarks.remove(':');
685 
687  const QString alt = flightPlan.getCruiseAltitude().asFpVatsimAltitudeString();
688  // const QString alt = flightPlan.getCruiseAltitude().asFpAltitudeString();
689 
690  FlightType flightType = getFlightType(flightPlan.getFlightRules());
691 
692  QString act;
693 
695  {
696  act = flightPlan.getAircraftInfo().asIcaoString();
697  }
698  else { act = flightPlan.getAircraftInfo().asFaaString(); }
699 
700  Q_ASSERT_X(!act.isEmpty(), Q_FUNC_INFO, "Aircraft type must not be empty");
701 
702  const QList<int> timePartsEnroute = flightPlan.getEnrouteTime().getHrsMinSecParts();
703  const QList<int> timePartsFuel = flightPlan.getFuelTime().getHrsMinSecParts();
704  const FlightPlan fp(getOwnCallsignAsString(), "SERVER", flightType, act,
705  flightPlan.getCruiseTrueAirspeed().valueInteger(CSpeedUnit::kts()),
706  flightPlan.getOriginAirportIcao().asString(),
707  flightPlan.getTakeoffTimePlanned().toUTC().toString("hhmm").toInt(),
708  flightPlan.getTakeoffTimeActual().toUTC().toString("hhmm").toInt(), alt,
709  flightPlan.getDestinationAirportIcao().asString(), timePartsEnroute[CTime::Hours],
710  timePartsEnroute[CTime::Minutes], timePartsFuel[CTime::Hours],
711  timePartsFuel[CTime::Minutes], flightPlan.getAlternateAirportIcao().asString(), remarks,
712  route);
713 
714  sendQueuedMessage(fp);
715  increaseStatisticsValue(QStringLiteral("sendFlightPlan"));
716  }
717 
719  {
720  if (!CThreadUtils::isInThisThread(this))
721  {
722  QMetaObject::invokeMethod(this, [=, this] {
723  if (sApp && !sApp->isShuttingDown()) { sendPlaneInfoRequest(receiver); }
724  });
725  return;
726  }
727 
728  const PlaneInfoRequest planeInfoRequest(getOwnCallsignAsString(), receiver.toQString());
729  sendQueuedMessage(planeInfoRequest);
730  increaseStatisticsValue(QStringLiteral("sendPlaneInfoRequest"));
731  }
732 
734  {
735  if (!CThreadUtils::isInThisThread(this))
736  {
737  QMetaObject::invokeMethod(this, [=, this] {
738  if (sApp && !sApp->isShuttingDown()) { sendPlaneInfoRequestFsinn(callsign); }
739  });
740  return;
741  }
742 
743  const bool connected = isConnected();
744  SWIFT_VERIFY_X(connected, Q_FUNC_INFO, "Can't send to server when disconnected");
745  if (!connected) { return; }
746 
747  const CSimulatedAircraft myAircraft(getOwnAircraft());
748  const QString modelString = this->getConfiguredModelString(myAircraft);
749  const PlaneInfoRequestFsinn planeInfoRequestFsinn(
750  getOwnCallsignAsString(), callsign.toQString(), myAircraft.getAirlineIcaoCodeDesignator(),
751  myAircraft.getAircraftIcaoCodeDesignator(), myAircraft.getAircraftIcaoCombinedType(), modelString);
752  sendQueuedMessage(planeInfoRequestFsinn);
753  increaseStatisticsValue(QStringLiteral("sendPlaneInfoRequestFsinn"));
754  }
755 
756  void CFSDClient::sendPlaneInformation(const QString &receiver, const QString &aircraft, const QString &airline,
757  const QString &livery)
758  {
759  const PlaneInformation planeInformation(getOwnCallsignAsString(), receiver, aircraft, airline, livery);
760  sendQueuedMessage(planeInformation);
761  increaseStatisticsValue(QStringLiteral("sendPlaneInformation"));
762  }
763 
764  void CFSDClient::sendPlaneInformationFsinn(const CCallsign &callsign)
765  {
766  if (this->getConnectionStatus().isDisconnected() && !m_unitTestMode) { return; }
767  const CSimulatedAircraft myAircraft(getOwnAircraft());
768  const QString modelString = this->getConfiguredModelString(myAircraft);
769  const PlaneInformationFsinn planeInformationFsinn(
770  getOwnCallsignAsString(), callsign.toQString(), myAircraft.getAirlineIcaoCodeDesignator(),
771  myAircraft.getAircraftIcaoCodeDesignator(), myAircraft.getAircraftIcaoCombinedType(), modelString);
772  sendQueuedMessage(planeInformationFsinn);
773  increaseStatisticsValue(QStringLiteral("sendPlaneInformationFsinn"));
774  }
775 
776  void CFSDClient::sendAircraftConfiguration(const QString &receiver, const QString &aircraftConfigJson)
777  {
778  if (aircraftConfigJson.size() == 0) { return; }
779  const ClientQuery clientQuery(getOwnCallsignAsString(), receiver, ClientQueryType::AircraftConfig,
780  { aircraftConfigJson });
781  sendQueuedMessage(clientQuery);
782  }
783 
784  void CFSDClient::sendMessageString(const QString &message)
785  {
786  if (message.isEmpty()) { return; }
787  const QByteArray bufferEncoded = m_encoder(message);
788  if (m_printToConsole) { qDebug() << "FSD Sent=>" << bufferEncoded; }
789  if (!m_unitTestMode) { m_socket->write(bufferEncoded); }
790 
791  // remove CR/LF and emit
792  emitRawFsdMessage(message.trimmed(), true);
793  }
794 
795  void CFSDClient::sendQueuedMessage()
796  {
797  if (m_queuedFsdMessages.isEmpty()) { return; }
798  const qsizetype s = m_queuedFsdMessages.size(); // NOLINT(cppcoreguidelines-init-variables)
799  this->sendMessageString(m_queuedFsdMessages.dequeue());
800 
801  // send up to 6 at once
802  if (s > 5) { this->sendMessageString(m_queuedFsdMessages.dequeue()); }
803  if (s > 10) { this->sendMessageString(m_queuedFsdMessages.dequeue()); }
804  if (s > 20) { this->sendMessageString(m_queuedFsdMessages.dequeue()); }
805  if (s > 30) { this->sendMessageString(m_queuedFsdMessages.dequeue()); }
806 
807  // overload
808  // no idea, if we ever get here
809  if (s > 50)
810  {
811  const StatusSeverity severity = s > 75 ? SeverityWarning : SeverityInfo;
812  CLogMessage(this).log(severity, u"Too many queued messages (%1), bulk send!") << s;
813  int sendNo = 10;
814  if (s > 75) { sendNo = 20; }
815  if (s > 100) { sendNo = 30; }
816 
817  for (int i = 0; i < sendNo; i++) { this->sendMessageString(m_queuedFsdMessages.dequeue()); }
818  }
819  }
820 
821  void CFSDClient::sendFsdMessage(const QString &message)
822  {
823  // UNIT tests
824  parseMessage(message);
825  }
826 
827  QString CFSDClient::getConfiguredModelString(const CSimulatedAircraft &myAircraft) const
828  {
829  if (!m_sendModelString) { return noModelString(); }
830  QReadLocker l(&m_lockUserClientBuffered);
831  const QString ms = m_ownModelString.isEmpty() ? myAircraft.getModelString() : m_ownModelString;
832  return ms.isEmpty() ? noModelString() : ms;
833  }
834 
835  QString CFSDClient::getConfiguredLiveryString(const CSimulatedAircraft &myAircraft) const
836  {
837  if (!m_sendLiveryString) { return {}; }
838  QReadLocker l(&m_lockUserClientBuffered);
839  const QString livery = m_ownLivery.isEmpty() ? myAircraft.getModel().getSwiftLiveryString() : m_ownLivery;
840  return livery;
841  }
842 
843  void CFSDClient::sendAuthChallenge(const QString &challenge)
844  {
845  const AuthChallenge pduAuthChallenge(getOwnCallsignAsString(), "SERVER", challenge);
846  sendDirectMessage(pduAuthChallenge); // avoid timeouts
847  increaseStatisticsValue(QStringLiteral("sendAuthChallenge"));
848  }
849 
850  void CFSDClient::sendAuthResponse(const QString &response)
851  {
852  const AuthResponse pduAuthResponse(getOwnCallsignAsString(), "SERVER", response);
853  sendDirectMessage(pduAuthResponse); // avoid timeouts
854  increaseStatisticsValue(QStringLiteral("sendAuthResponse"));
855  }
856 
857  void CFSDClient::sendPong(const QString &receiver, const QString &timestamp)
858  {
859  const Pong pong(getOwnCallsignAsString(), receiver, timestamp);
860  sendQueuedMessage(pong);
861  increaseStatisticsValue(QStringLiteral("sendPong"));
862  }
863 
864  void CFSDClient::sendClientResponse(ClientQueryType queryType, const QString &receiver)
865  {
866  if (queryType == ClientQueryType::Unknown) { return; }
867  if (queryType == ClientQueryType::IsValidATC)
868  {
869  this->handleIllegalFsdState("Never use sendClientResponse with IsValidATC from the client");
870  return;
871  }
872 
873  increaseStatisticsValue(QStringLiteral("sendClientResponse"), toQString(queryType));
874 
875  QStringList responseData;
876  const QString ownCallsign = getOwnCallsignAsString();
877 
878  if (queryType == ClientQueryType::Capabilities)
879  {
880  responseData.clear();
881  if (m_capabilities & Capabilities::AtcInfo) responseData.push_back(toQString(Capabilities::AtcInfo) % "=1");
882  if (m_capabilities & Capabilities::SecondaryPos)
883  responseData.push_back(toQString(Capabilities::SecondaryPos) % "=1");
884  if (m_capabilities & Capabilities::AircraftInfo)
885  responseData.push_back(toQString(Capabilities::AircraftInfo) % "=1");
886  if (m_capabilities & Capabilities::OngoingCoord)
887  responseData.push_back(toQString(Capabilities::OngoingCoord) % "=1");
888  if (m_capabilities & Capabilities::InterminPos)
889  responseData.push_back(toQString(Capabilities::InterminPos) % "=1");
890  if (m_capabilities & Capabilities::FastPos) responseData.push_back(toQString(Capabilities::FastPos) % "=1");
891  if (m_capabilities & Capabilities::VisPos) responseData.push_back(toQString(Capabilities::VisPos) % "=1");
892  if (m_capabilities & Capabilities::Stealth) responseData.push_back(toQString(Capabilities::Stealth) % "=1");
893  if (m_capabilities & Capabilities::AircraftConfig)
894  responseData.push_back(toQString(Capabilities::AircraftConfig) % "=1");
895  if (m_capabilities & Capabilities::IcaoEquipment)
896  responseData.push_back(toQString(Capabilities::IcaoEquipment) % "=1");
897  const ClientResponse clientResponse(ownCallsign, receiver, ClientQueryType::Capabilities, responseData);
898  sendQueuedMessage(clientResponse);
899  }
900  else if (queryType == ClientQueryType::Com1Freq)
901  {
902  const QString com1Frequency = QString::number(
903  getOwnAircraft().getCom1System().getFrequencyActive().value(CFrequencyUnit::MHz()), 'f', 3);
904  responseData.push_back(com1Frequency);
905  const ClientResponse pduClientResponse(ownCallsign, receiver, ClientQueryType::Com1Freq, responseData);
906  sendQueuedMessage(pduClientResponse);
907  }
908  else if (queryType == ClientQueryType::RealName)
909  {
910  // real name
911  // responseData.push_back(m_server.getUser().getRealName());
912  responseData.push_back(m_server.getUser().getRealNameAndHomeBase()); // getHomeBase
913  // sector file in use (blank if pilot)
914  responseData.push_back({});
915  // current user rating
916  if (m_loginMode.isObserver()) { responseData.push_back(toQString(m_atcRating)); }
917  else { responseData.push_back(toQString(m_pilotRating)); }
918 
919  const ClientResponse pduClientQueryResponse(ownCallsign, receiver, ClientQueryType::RealName, responseData);
920  sendQueuedMessage(pduClientQueryResponse);
921  }
922  else if (queryType == ClientQueryType::Server)
923  {
924  responseData.push_back(m_server.getAddress());
925  const ClientResponse pduClientQueryResponse(ownCallsign, receiver, ClientQueryType::Server, responseData);
926  sendQueuedMessage(pduClientQueryResponse);
927  }
928  else if (queryType == ClientQueryType::ATIS)
929  {
930  this->handleIllegalFsdState(
931  QStringLiteral("Dont send '%1' as pilot client!").arg(toQString(ClientQueryType::ATIS)));
932  }
933  else if (queryType == ClientQueryType::PublicIP)
934  {
935  this->handleIllegalFsdState(
936  QStringLiteral("Dont send '%1' as pilot client!").arg(toQString(ClientQueryType::PublicIP)));
937  }
938  else if (queryType == ClientQueryType::INF)
939  {
940  const QString cid = m_server.getUser().getId();
941  const QString realName = m_server.getUser().getRealName();
942 
943  const CAircraftSituation situation = this->getOwnAircraftPosition();
944  const double latitude = situation.latitude().value(CAngleUnit::deg());
945  const double longitude = situation.longitude().value(CAngleUnit::deg());
946  const int altitude = situation.getAltitude().valueInteger(CLengthUnit::ft());
947 
948  std::array<char, 50> sysuid = {};
949 #ifdef SWIFT_VATSIM_SUPPORT
950  vatsim_get_system_unique_id(sysuid.data());
951 #endif
952 
953  const QString userInfo = QStringLiteral("CID=") % cid % " " % m_clientName % " IP=" %
954  m_socket->localAddress().toString() % " SYS_UID=" % sysuid.data() % " FSVER=" %
955  m_hostApplication % " LT=" % QString::number(latitude) % " LO=" %
956  QString::number(longitude) % " AL=" % QString::number(altitude) % " " % realName;
957 
958  const TextMessage textMessage(ownCallsign, receiver, userInfo);
959  sendQueuedMessage(textMessage);
960  }
961  else if (queryType == ClientQueryType::FP)
962  {
963  this->handleIllegalFsdState(
964  QStringLiteral("Dont send '%1' as pilot client!").arg(toQString(ClientQueryType::FP)));
965  }
966  else if (queryType == ClientQueryType::AircraftConfig)
967  {
968  this->handleIllegalFsdState(
969  QStringLiteral("Dont send '%1' as pilot client!").arg(toQString(ClientQueryType::AircraftConfig)));
970  }
971  }
972 
973 #ifdef SWIFT_VATSIM_SUPPORT
974  void CFSDClient::sendClientIdentification(const QString &fsdChallenge)
975  {
976  std::array<char, 50> sysuid = {};
977  vatsim_get_system_unique_id(sysuid.data());
978  const QString cid = m_server.getUser().getId();
979  const ClientIdentification clientIdentification(
980  getOwnCallsignAsString(), vatsim_auth_get_client_id(m_clientAuth), m_clientName, m_versionMajor,
981  m_versionMinor, cid, sysuid.data(), fsdChallenge);
982  this->sendQueuedMessage(clientIdentification);
983 
984  if (getServer().getEcosystem().isSystem(CEcosystem::VATSIM))
985  {
986  this->getVatsimAuthToken(cid, m_server.getUser().getPassword(),
987  { this, [this](const QString &token) {
988  this->sendLogin(token);
989  this->updateConnectionStatus(CConnectionStatus::Connected);
990  } });
991  }
992  else
993  {
994  this->sendLogin();
995  this->updateConnectionStatus(CConnectionStatus::Connected);
996  }
997  increaseStatisticsValue(QStringLiteral("sendClientIdentification"));
998  }
999 #endif
1000 
1001  void CFSDClient::getVatsimAuthToken(const QString &cid, const QString &password,
1002  const swift::misc::CSlot<void(const QString &)> &callback)
1003  {
1004  Q_ASSERT_X(sApp, Q_FUNC_INFO, "Need app");
1006  nwRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
1007  const QJsonObject jsonRequest { { "cid", cid }, { "password", password } };
1008 
1009  sApp->postToNetwork(nwRequest, CApplication::NoLogRequestId, QJsonDocument(jsonRequest).toJson(),
1010  { this, [=, this](QNetworkReply *nwReply) {
1011  const QByteArray data = nwReply->readAll();
1012  const QJsonObject json = QJsonDocument::fromJson(data).object();
1013 
1014  if (json.value("success").toBool()) { callback(json.value("token").toString()); }
1015  else
1016  {
1017  const QString error = json.value("error_msg").isString() ?
1018  json.value("error_msg").toString() :
1019  nwReply->errorString();
1020  CLogMessage(this).error(u"Connecting to VATSIM with CID %1 failed: %2")
1021  << cid << error;
1022  disconnectFromServer();
1023  }
1024  nwReply->deleteLater();
1025  } });
1026  }
1027 
1028  void CFSDClient::sendIncrementalAircraftConfig()
1029  {
1030  if (!m_unitTestMode && (!this->isConnected() || !this->getSetupForServer().sendAircraftParts())) { return; }
1031  const CAircraftParts currentParts(this->getOwnAircraftParts());
1032 
1033  // If it hasn't changed, return
1034  if (m_sentAircraftConfig == currentParts) { return; }
1035 
1036  if (!m_tokenBucket.tryConsume()) { return; }
1037 
1038  const QJsonObject previousConfig = m_sentAircraftConfig.toJson();
1039  const QJsonObject currentConfig = currentParts.toJson();
1040  const QJsonObject incrementalConfig = getIncrementalObject(previousConfig, currentConfig);
1041 
1042  const QString dataStr = convertToUnicodeEscaped(
1043  QJsonDocument(QJsonObject { { "config", incrementalConfig } }).toJson(QJsonDocument::Compact));
1044 
1045  sendAircraftConfiguration("@94836", dataStr);
1046  m_sentAircraftConfig = currentParts;
1047  }
1048 
1049  void CFSDClient::initializeMessageTypes()
1050  {
1051  m_messageTypeMapping["#AA"] = MessageType::AddAtc;
1052  m_messageTypeMapping["#AP"] = MessageType::AddPilot;
1053  m_messageTypeMapping["%"] = MessageType::AtcDataUpdate;
1054  m_messageTypeMapping["$ZC"] = MessageType::AuthChallenge;
1055  m_messageTypeMapping["$ZR"] = MessageType::AuthResponse;
1056  m_messageTypeMapping["$ID"] = MessageType::ClientIdentification;
1057  m_messageTypeMapping["$CQ"] = MessageType::ClientQuery;
1058  m_messageTypeMapping["$CR"] = MessageType::ClientResponse;
1059  m_messageTypeMapping["#DA"] = MessageType::DeleteATC;
1060  m_messageTypeMapping["#DP"] = MessageType::DeletePilot;
1061  m_messageTypeMapping["$FP"] = MessageType::FlightPlan;
1062  m_messageTypeMapping["#PC"] = MessageType::ProController;
1063  m_messageTypeMapping["$DI"] = MessageType::FsdIdentification;
1064  m_messageTypeMapping["$!!"] = MessageType::KillRequest;
1065  m_messageTypeMapping["@"] = MessageType::PilotDataUpdate;
1066  m_messageTypeMapping["^"] = MessageType::VisualPilotDataUpdate;
1067  m_messageTypeMapping["#SL"] = MessageType::VisualPilotDataPeriodic;
1068  m_messageTypeMapping["#ST"] = MessageType::VisualPilotDataStopped;
1069  m_messageTypeMapping["$SF"] = MessageType::VisualPilotDataToggle;
1070  m_messageTypeMapping["$PI"] = MessageType::Ping;
1071  m_messageTypeMapping["$PO"] = MessageType::Pong;
1072  m_messageTypeMapping["$ER"] = MessageType::ServerError;
1073  m_messageTypeMapping["#DL"] = MessageType::ServerHeartbeat;
1074  m_messageTypeMapping["#TM"] = MessageType::TextMessage;
1075  m_messageTypeMapping["#SB"] = MessageType::PilotClientCom;
1076  m_messageTypeMapping["$XX"] = MessageType::Rehost;
1077  m_messageTypeMapping["#MU"] = MessageType::Mute;
1078 
1079  // Euroscope
1080  m_messageTypeMapping["SIMDATA"] = MessageType::EuroscopeSimData;
1081  }
1082 
1083  void CFSDClient::handleAtcDataUpdate(const QStringList &tokens)
1084  {
1085  const AtcDataUpdate atcDataUpdate = AtcDataUpdate::fromTokens(tokens);
1086  const QString senderCs = atcDataUpdate.sender();
1087  const CCallsign cs(senderCs, CCallsign::Atc);
1088 
1089  // Filter non-ATC like OBS stations, like pilots logging in as shared cockpit co-pilots.
1090  if (atcDataUpdate.m_facility == CFacilityType::Unknown && !cs.isObserverCallsign())
1091  {
1092  return;
1093  } // like in old version
1094  if (atcDataUpdate.m_facility == CFacilityType::OBS && !cs.hasSuffix()) { return; }
1095 
1096  CFrequency freq(atcDataUpdate.m_frequencykHz, CFrequencyUnit::kHz());
1097  freq.switchUnit(CFrequencyUnit::MHz()); // we would not need to bother, but this makes it easier to identify
1098 
1099  // Here we could round to channel spacing, based on
1100  // https://discordapp.com/channels/539048679160676382/539486489977946112/651514202405601291
1101  // CComSystem::roundToChannelSpacing(freq, CComSystem::ChannelSpacing25KHz);
1102 
1103  const CLength networkRange(atcDataUpdate.m_visibleRange, CLengthUnit::NM());
1104  const CLength range = fixAtcRange(networkRange, cs);
1105  const CCoordinateGeodetic position(atcDataUpdate.m_latitude, atcDataUpdate.m_longitude, 0);
1106 
1107  emit this->atcDataUpdateReceived(cs, freq, position, range);
1108 
1109  m_atcStations.replaceOrAddObjectByCallsign({ cs, {}, freq, position, range });
1110  }
1111 
1112 #ifdef SWIFT_VATSIM_SUPPORT
1113  void CFSDClient::handleAuthChallenge(const QStringList &tokens)
1114  {
1115  const AuthChallenge authChallenge = AuthChallenge::fromTokens(tokens);
1116  char response[33];
1117  vatsim_auth_generate_response(m_clientAuth, qPrintable(authChallenge.m_challengeKey), response);
1118  sendAuthResponse(QString(response));
1119 
1120  char challenge[33];
1121  vatsim_auth_generate_challenge(m_serverAuth, challenge);
1122  m_lastServerAuthChallenge = QString(challenge);
1123  sendAuthChallenge(m_lastServerAuthChallenge);
1124  }
1125 #endif
1126 
1127 #ifdef SWIFT_VATSIM_SUPPORT
1128  void CFSDClient::handleAuthResponse(const QStringList &tokens)
1129  {
1130  const AuthResponse authResponse = AuthResponse::fromTokens(tokens);
1131 
1132  char expectedResponse[33];
1133  vatsim_auth_generate_response(m_serverAuth, qPrintable(m_lastServerAuthChallenge), expectedResponse);
1134  if (authResponse.m_response != QString(expectedResponse))
1135  {
1136  CLogMessage().error(u"The server you are connected to is not a VATSIM server. Disconnecting!");
1137  disconnectFromServer();
1138  }
1139  }
1140 #endif
1141 
1142  void CFSDClient::handleDeleteATC(const QStringList &tokens)
1143  {
1144  const DeleteAtc deleteAtc = DeleteAtc::fromTokens(tokens);
1145  emit deleteAtcReceived(deleteAtc.m_cid);
1146 
1147  m_atcStations.removeByCallsign(deleteAtc.m_cid);
1148  }
1149 
1150  void CFSDClient::handleDeletePilot(const QStringList &tokens)
1151  {
1152  const DeletePilot deletePilot = DeletePilot::fromTokens(tokens);
1153  const CCallsign cs(deletePilot.sender(), CCallsign::Aircraft);
1154  clearState(cs);
1155  emit deletePilotReceived(deletePilot.m_cid);
1156  }
1157 
1158  void CFSDClient::handleTextMessage(const QStringList &tokens)
1159  {
1160  const TextMessage textMessage = TextMessage::fromTokens(tokens);
1161 
1162  const CCallsign sender(textMessage.sender());
1163  const CCallsign receiver(textMessage.receiver());
1164 
1165  if (textMessage.m_type == TextMessage::PrivateMessage)
1166  {
1167  // Other FSD servers send the controller ATIS as text message. The following conditions need to be met:
1168  // * non-VATSIM server. VATSIM has a specific ATIS message
1169  // * Receiver callsign must be owner callsign and not any type of broadcast.
1170  // * We have requested the ATIS of this controller before.
1171  if (m_server.getServerType() != CServer::FSDServerVatsim &&
1172  m_ownCallsign.asString() == textMessage.receiver() && m_pendingAtisQueries.contains(sender))
1173  {
1174  maybeHandleAtisReply(sender, receiver, textMessage.m_message);
1175  return;
1176  }
1177 
1178  CTextMessage tm(textMessage.m_message, sender, receiver);
1179  tm.setCurrentUtcTime();
1180  this->consolidateTextMessage(tm); // emit textMessagesReceived({ tm });
1181  }
1182  else if (textMessage.m_type == TextMessage::RadioMessage)
1183  {
1184  const CFrequency com1 = getOwnAircraft().getCom1System().getFrequencyActive();
1185  const CFrequency com2 = getOwnAircraft().getCom2System().getFrequencyActive();
1186  QList<CFrequency> frequencies;
1187 
1188  for (int freqKhz : textMessage.m_frequencies)
1189  {
1190  CFrequency f(freqKhz, CFrequencyUnit::kHz());
1191  CComSystem::roundToChannelSpacing(f, CComSystem::ChannelSpacing8_33KHz);
1192  if (f == com1 || f == com2) { frequencies.push_back(f); }
1193  }
1194  if (frequencies.isEmpty()) { return; }
1195  CTextMessageList messages(textMessage.m_message, frequencies, CCallsign(textMessage.sender()));
1196  messages.setCurrentUtcTime();
1197  emit textMessagesReceived(messages);
1198  }
1199  }
1200 
1201  void CFSDClient::handlePilotDataUpdate(const QStringList &tokens)
1202  {
1203  const PilotDataUpdate dataUpdate = PilotDataUpdate::fromTokens(tokens);
1204  const CCallsign callsign(dataUpdate.sender(), CCallsign::Aircraft);
1205 
1206  CAircraftSituation situation(
1207  callsign, CCoordinateGeodetic(dataUpdate.m_latitude, dataUpdate.m_longitude, dataUpdate.m_altitudeTrue),
1208  CHeading(dataUpdate.m_heading, CHeading::True, CAngleUnit::deg()),
1209  CAngle(dataUpdate.m_pitch, CAngleUnit::deg()), CAngle(dataUpdate.m_bank, CAngleUnit::deg()),
1210  CSpeed(dataUpdate.m_groundSpeed, CSpeedUnit::kts()));
1211  situation.setPressureAltitude(CAltitude(dataUpdate.m_altitudePressure, CAltitude::MeanSeaLevel,
1212  CAltitude::PressureAltitude, CLengthUnit::ft()));
1213  // NotSetGroundDetails because here we do not know if this FSD protocol actually utilizes this flag
1214  const COnGroundInfo og(dataUpdate.m_onGround ? COnGroundInfo::OnGround : COnGroundInfo::NotOnGround,
1215  COnGroundInfo::NotSetGroundDetails);
1216  situation.setOnGroundInfo(og);
1217 
1218  // Ref T297, default offset time
1219  situation.setCurrentUtcTime();
1220  const qint64 offsetTimeMs =
1221  receivedPositionFixTsAndGetOffsetTime(situation.getCallsign(), situation.getMSecsSinceEpoch());
1222  situation.setTimeOffsetMs(offsetTimeMs);
1223 
1224  // I did have a situation where I got wrong transponder codes (KB)
1225  // So I now check for a valid code in order to detect such codes
1226  CTransponder transponder;
1227  if (CTransponder::isValidTransponderCode(dataUpdate.m_transponderCode))
1228  {
1229  transponder = CTransponder(dataUpdate.m_transponderCode, dataUpdate.m_transponderMode);
1230  }
1231  else
1232  {
1233  if (CBuildConfig::isLocalDeveloperDebugBuild())
1234  {
1235  CLogMessage(this).debug(u"Wrong transponder code '%1' for '%2'")
1236  << dataUpdate.m_transponderCode << callsign;
1237  }
1238 
1239  // I set a default: IFR standby is a reasonable default
1240  transponder = CTransponder(2000, CTransponder::StateStandby);
1241  }
1242  emit pilotDataUpdateReceived(situation, transponder);
1243  }
1244 
1245  void CFSDClient::handleEuroscopeSimData(const QStringList &tokens)
1246  {
1247  const EuroscopeSimData data = EuroscopeSimData::fromTokens(tokens);
1248 
1249  CAircraftSituation situation(CCallsign(data.sender(), CCallsign::Aircraft),
1250  CCoordinateGeodetic(data.m_latitude, data.m_longitude, data.m_altitude),
1251  CHeading(data.m_heading, CAngleUnit::deg()),
1252  CAngle(-data.m_pitch, CAngleUnit::deg()), CAngle(-data.m_bank, CAngleUnit::deg()),
1253  CSpeed(data.m_groundSpeed, CSpeedUnit::kts()));
1254  // NotSetGroundDetails because here we do not know if this FSD protocol actually utilizes this flag
1255  const COnGroundInfo og(data.m_onGround ? COnGroundInfo::OnGround : COnGroundInfo::NotOnGround,
1256  COnGroundInfo::NotSetGroundDetails);
1257  situation.setOnGroundInfo(og);
1258 
1259  // Ref T297, default offset time
1260  situation.setCurrentUtcTime();
1261  const qint64 offsetTimeMs =
1262  receivedPositionFixTsAndGetOffsetTime(situation.getCallsign(), situation.getMSecsSinceEpoch());
1263  situation.setTimeOffsetMs(offsetTimeMs);
1264 
1265  CAircraftParts parts;
1266  parts.setLights(data.m_lights);
1267  parts.setGearDown(data.m_gearPercent);
1268  parts.setOnGround(data.m_onGround);
1269 
1270  emit euroscopeSimDataUpdatedReceived(situation, parts, currentOffsetTime(data.sender()), data.m_model,
1271  data.m_livery);
1272  }
1273 
1274  void CFSDClient::handleVisualPilotDataUpdate(const QStringList & /*tokens*/, MessageType /*messageType*/)
1275  {
1276 #if 0
1277  VisualPilotDataUpdate dataUpdate;
1278  switch (messageType)
1279  {
1280  case MessageType::VisualPilotDataUpdate: dataUpdate = VisualPilotDataUpdate::fromTokens(tokens); break;
1281  case MessageType::VisualPilotDataPeriodic: dataUpdate = VisualPilotDataPeriodic::fromTokens(tokens).toUpdate(); break;
1282  case MessageType::VisualPilotDataStopped: dataUpdate = VisualPilotDataStopped::fromTokens(tokens).toUpdate(); break;
1283  default: qFatal("Precondition violated"); break;
1284  }
1285  const CCallsign callsign(dataUpdate.sender(), CCallsign::Aircraft);
1286 
1287  CAircraftSituation situation(
1288  callsign,
1289  CCoordinateGeodetic(dataUpdate.m_latitude, dataUpdate.m_longitude, dataUpdate.m_altitudeTrue),
1290  CHeading(dataUpdate.m_heading, CHeading::True, CAngleUnit::deg()),
1291  CAngle(dataUpdate.m_pitch, CAngleUnit::deg()),
1292  CAngle(dataUpdate.m_bank, CAngleUnit::deg()));
1293 
1294  // not used
1295  // situation.setVelocity(CAircraftVelocity(
1296  // dataUpdate.m_xVelocity, dataUpdate.m_yVelocity, dataUpdate.m_zVelocity, CSpeedUnit::m_s(),
1297  // dataUpdate.m_pitchRadPerSec, dataUpdate.m_bankRadPerSec, dataUpdate.m_headingRadPerSec, CAngleUnit::rad(), CTimeUnit::s()));
1298 
1299  // Ref T297, default offset time
1300  situation.setCurrentUtcTime();
1301  const qint64 offsetTimeMs = receivedPositionFixTsAndGetOffsetTime(situation.getCallsign(), situation.getMSecsSinceEpoch());
1302  situation.setTimeOffsetMs(offsetTimeMs);
1303 
1304  emit visualPilotDataUpdateReceived(situation);
1305 #endif
1306  }
1307 
1308  void CFSDClient::handleVisualPilotDataToggle(const QStringList &tokens)
1309  {
1310  const VisualPilotDataToggle toggle = VisualPilotDataToggle::fromTokens(tokens);
1311  m_serverWantsVisualPositions = toggle.m_active;
1312  }
1313 
1314  void CFSDClient::handlePing(const QStringList &tokens)
1315  {
1316  const Ping ping = Ping::fromTokens(tokens);
1317  sendPong(ping.sender(), ping.m_timestamp);
1318  }
1319 
1320  void CFSDClient::handlePong(const QStringList &tokens)
1321  {
1322  const Pong pong = Pong::fromTokens(tokens);
1323  const qint64 msecSinceEpoch = QDateTime::currentMSecsSinceEpoch();
1324  const qint64 elapsedTime = msecSinceEpoch - pong.m_timestamp.toLongLong();
1325  emit pongReceived(pong.sender(), static_cast<double>(elapsedTime));
1326  }
1327 
1328  void CFSDClient::handleKillRequest(const QStringList &tokens)
1329  {
1330  KillRequest killRequest = KillRequest::fromTokens(tokens);
1331  emit killRequestReceived(killRequest.m_reason);
1332  disconnectFromServer();
1333  }
1334 
1335  void CFSDClient::handleFlightPlan(const QStringList &tokens)
1336  {
1337  FlightPlan fp = FlightPlan::fromTokens(tokens);
1338  CFlightPlan::FlightRules rules = CFlightPlan::VFR;
1339 
1340  switch (fp.m_flightType)
1341  {
1342  case FlightType::VFR: rules = CFlightPlan::VFR; break;
1343  case FlightType::IFR: rules = CFlightPlan::IFR; break;
1344  case FlightType::DVFR: rules = CFlightPlan::DVFR; break;
1345  case FlightType::SVFR: rules = CFlightPlan::SVFR; break;
1346  }
1347 
1348  QString cruiseAltString = fp.m_cruiseAlt.trimmed();
1349  if (!cruiseAltString.isEmpty() && is09OnlyString(cruiseAltString))
1350  {
1351  int ca = cruiseAltString.toInt();
1352  // we have a 0-9 only string
1353  // we assume values like 24000 as FL
1354  // RefT323, also major tool such as PFPX and Simbrief do so
1355  if (rules == CFlightPlan::IFR)
1356  {
1357  if (ca >= 1000) { cruiseAltString = u"FL" % QString::number(ca / 100); }
1358  else { cruiseAltString = u"FL" % cruiseAltString; }
1359  }
1360  else // VFR
1361  {
1362  if (ca >= 5000) { cruiseAltString = u"FL" % QString::number(ca / 100); }
1363  else { cruiseAltString = cruiseAltString % u"ft"; }
1364  }
1365  }
1366  CAltitude cruiseAlt;
1367  cruiseAlt.parseFromString(cruiseAltString, CPqString::SeparatorBestGuess);
1368 
1369  const QString depTimePlanned = QStringLiteral("0000").append(QString::number(fp.m_estimatedDepTime)).right(4);
1370  const QString depTimeActual = QStringLiteral("0000").append(QString::number(fp.m_actualDepTime)).right(4);
1371 
1372  const CCallsign callsign(fp.sender(), CCallsign::Aircraft);
1373  const CFlightPlan flightPlan(
1374  callsign, CFlightPlanAircraftInfo(fp.m_aircraftIcaoType), fp.m_depAirport, fp.m_destAirport,
1375  fp.m_altAirport, fromStringUtc(depTimePlanned, "hhmm"), fromStringUtc(depTimeActual, "hhmm"),
1376  CTime(fp.m_hoursEnroute * 60 + fp.m_minutesEnroute, CTimeUnit::min()),
1377  CTime(fp.m_fuelAvailHours * 60 + fp.m_fuelAvailMinutes, CTimeUnit::min()), cruiseAlt,
1378  CSpeed(fp.m_trueCruisingSpeed, CSpeedUnit::kts()), rules, fp.m_route, fp.m_remarks);
1379 
1380  emit flightPlanReceived(callsign, flightPlan);
1381  }
1382 
1383  void CFSDClient::handleClientQuery(const QStringList &tokens)
1384  {
1385  const ClientQuery clientQuery = ClientQuery::fromTokens(tokens);
1386  // NOLINTBEGIN(bugprone-branch-clone)
1387  if (clientQuery.m_queryType == ClientQueryType::Unknown) { return; }
1388  if (clientQuery.m_queryType == ClientQueryType::IsValidATC)
1389  {
1390  // This is usually sent to the server only. If it ever arrives here, just ignore it.
1391  }
1392  else if (clientQuery.m_queryType == ClientQueryType::Capabilities)
1393  {
1394  sendClientResponse(ClientQueryType::Capabilities, clientQuery.sender());
1395  }
1396  else if (clientQuery.m_queryType == ClientQueryType::Com1Freq)
1397  {
1398  sendClientResponse(ClientQueryType::Com1Freq, clientQuery.sender());
1399  }
1400  else if (clientQuery.m_queryType == ClientQueryType::RealName)
1401  {
1402  sendClientResponse(ClientQueryType::RealName, clientQuery.sender());
1403  }
1404  else if (clientQuery.m_queryType == ClientQueryType::Server)
1405  {
1406  sendClientResponse(ClientQueryType::Server, clientQuery.sender());
1407  }
1408  else if (clientQuery.m_queryType == ClientQueryType::ATIS)
1409  {
1410  // This is answered by ATC clients only. If we get such a request, ignore it.
1411  }
1412  else if (clientQuery.m_queryType == ClientQueryType::PublicIP)
1413  {
1414  // This is usually sent to the server only. If it ever arrives here, just ignore it.
1415  }
1416  else if (clientQuery.m_queryType == ClientQueryType::INF)
1417  {
1418  sendClientResponse(ClientQueryType::INF, clientQuery.sender());
1419  }
1420  else if (clientQuery.m_queryType == ClientQueryType::FP)
1421  {
1422  // This is usually sent to the server only. If it ever arrives here, just ignore it.
1423  }
1424  else if (clientQuery.m_queryType == ClientQueryType::AircraftConfig)
1425  {
1426  QStringList aircraftConfigTokens = tokens.mid(3);
1427  QString aircraftConfigJson = aircraftConfigTokens.join(":");
1428 
1429  const CCallsign callsign(clientQuery.sender(), CCallsign::Aircraft);
1430 
1431  QJsonParseError parserError;
1432  const QByteArray json = aircraftConfigJson.toUtf8();
1433  const QJsonDocument doc = QJsonDocument::fromJson(json, &parserError);
1434 
1435  if (parserError.error != QJsonParseError::NoError)
1436  {
1437  CLogMessage(this).warning(u"Failed to parse aircraft config packet: '%1' packet: '%2'")
1438  << parserError.errorString() << QString(json);
1439  return; // we cannot parse the packet, so we give up here
1440  }
1441 
1442  const QJsonObject packet = doc.object();
1443  if (packet == JsonPackets::aircraftConfigRequest())
1444  {
1445  // this MUST work for NOT IN RANGE aircraft as well
1446  // Here we send our OWN parts
1447  QJsonObject config = this->getOwnAircraftParts().toJson();
1448  config.insert(CAircraftParts::attributeNameIsFullJson(), true);
1449  QString data = QJsonDocument(QJsonObject { { "config", config } }).toJson(QJsonDocument::Compact);
1450  data = convertToUnicodeEscaped(data);
1451  sendAircraftConfiguration(clientQuery.sender(), data);
1452  return;
1453  }
1454 
1455  const bool inRange = isAircraftInRange(callsign);
1456  if (!inRange) { return; } // sort out all broadcasts we DO NOT NEED
1457  if (!getSetupForServer().receiveAircraftParts()) { return; }
1458  const QJsonObject config = doc.object().value("config").toObject();
1459  if (config.isEmpty()) { return; }
1460 
1461  const qint64 offsetTimeMs = currentOffsetTime(callsign);
1462  emit aircraftConfigReceived(clientQuery.sender(), config, offsetTimeMs);
1463  }
1464  // NOLINTEND(bugprone-branch-clone)
1465  }
1466 
1467  void CFSDClient::handleClientResponse(const QStringList &tokens)
1468  {
1469  const ClientResponse clientResponse = ClientResponse::fromTokens(tokens);
1470  if (clientResponse.isUnknownQuery()) { return; }
1471  const QString sender = clientResponse.sender();
1472 
1473  QString responseData1;
1474  QString responseData2;
1475  // NOLINTBEGIN(bugprone-branch-clone)
1476  if (!clientResponse.m_responseData.empty()) { responseData1 = clientResponse.m_responseData.at(0); }
1477 
1478  if (clientResponse.m_responseData.size() > 1) { responseData2 = clientResponse.m_responseData.at(1); }
1479 
1480  if (clientResponse.m_queryType == ClientQueryType::IsValidATC)
1481  {
1482  emit validAtcResponseReceived(responseData2, responseData1 == u"Y");
1483  }
1484  else if (clientResponse.m_queryType == ClientQueryType::Capabilities)
1485  {
1486  Capabilities capabilities = Capabilities::None;
1487  for (auto keyValuePair : clientResponse.m_responseData)
1488  {
1489  if (keyValuePair.count('=') != 1) { continue; }
1490 
1491  const QStringList split = keyValuePair.split('=');
1492  if (split.size() < 2) { continue; }
1493  const QString key = split.at(0);
1494  const QString value = split.at(1);
1495 
1496  if (value == "1") { capabilities |= fromQString<Capabilities>(key); }
1497  }
1498 
1499  CClient::Capabilities caps = CClient::None;
1500  if (capabilities & Capabilities::AtcInfo) { caps |= CClient::FsdAtisCanBeReceived; }
1501  if (capabilities & Capabilities::FastPos) { caps |= CClient::FsdWithInterimPositions; }
1502  if (capabilities & Capabilities::VisPos) { caps |= CClient::FsdWithVisualPositions; }
1503  if (capabilities & Capabilities::AircraftInfo) { caps |= CClient::FsdWithIcaoCodes; }
1504  if (capabilities & Capabilities::AircraftConfig) { caps |= CClient::FsdWithAircraftConfig; }
1505 
1506  emit capabilityResponseReceived(clientResponse.sender(), caps);
1507  }
1508  else if (clientResponse.m_queryType == ClientQueryType::Com1Freq)
1509  {
1510  if (responseData1.isEmpty()) { return; }
1511  bool ok {};
1512  const double freqMHz = responseData1.toDouble(&ok);
1513  if (!ok) { return; }
1514  emit com1FrequencyResponseReceived(clientResponse.sender(), CFrequency(freqMHz, CFrequencyUnit::MHz()));
1515  }
1516  else if (clientResponse.m_queryType == ClientQueryType::RealName)
1517  {
1518  // The response also includes sector name and pilot rating, but we ignore them here.
1519  emit realNameResponseReceived(clientResponse.sender(), responseData1);
1520  }
1521  else if (clientResponse.m_queryType == ClientQueryType::Server)
1522  {
1523  emit serverResponseReceived(clientResponse.sender(), responseData1);
1524  }
1525  else if (clientResponse.m_queryType == ClientQueryType::ATIS)
1526  {
1527  if (responseData1.isEmpty())
1528  {
1529  // networkLog(vatSeverityDebug, "VatFsdClient::handleClientQueryResponse", "ATIS line type cannot be
1530  // empty!");
1531  }
1532  updateAtisMap(clientResponse.sender(), fromQString<AtisLineType>(responseData1), responseData2);
1533  }
1534  else if (clientResponse.m_queryType == ClientQueryType::PublicIP)
1535  {
1536  // To be implemented if needed
1537  }
1538  else if (clientResponse.m_queryType == ClientQueryType::INF)
1539  {
1540  // To be implemented if needed
1541  }
1542  else if (clientResponse.m_queryType == ClientQueryType::FP)
1543  {
1544  // FP is sent back as a $FP answer from the server and never as part of a client response.
1545  }
1546  else if (clientResponse.m_queryType == ClientQueryType::AircraftConfig)
1547  {
1548  // Currently not existing.
1549  }
1550  // NOLINTEND(bugprone-branch-clone)
1551  }
1552 
1553  void CFSDClient::handleServerError(const QStringList &tokens)
1554  {
1555  const ServerError serverError = ServerError::fromTokens(tokens);
1556  switch (serverError.m_errorNumber)
1557  {
1558  case ServerErrorCode::CallsignInUse: CLogMessage(this).error(u"The requested callsign is already taken"); break;
1559  case ServerErrorCode::InvalidCallsign: CLogMessage(this).error(u"The requested callsign is not valid"); break;
1560  case ServerErrorCode::InvalidCidPassword:
1561  CLogMessage(this).error(u"Wrong user ID (%1) or password") << m_server.getUser().getId();
1562  break;
1563  case ServerErrorCode::InvalidRevision:
1564  CLogMessage(this).error(u"This server does not support our protocol version");
1565  break;
1566  case ServerErrorCode::ServerFull: CLogMessage(this).error(u"The server is full"); break;
1567  case ServerErrorCode::CidSuspended: CLogMessage(this).error(u"Your user account is suspended"); break;
1568  case ServerErrorCode::RatingTooLow:
1569  CLogMessage(this).error(u"You are not authorized to use the requested rating");
1570  break;
1571  case ServerErrorCode::InvalidClient:
1572  CLogMessage(this).error(u"This software is not authorized for use on this network");
1573  break;
1574  case ServerErrorCode::RequestedLevelTooHigh:
1575  CLogMessage(this).error(u"You are not authorized to use the requested pilot rating");
1576  break;
1577 
1578  case ServerErrorCode::NoError: CLogMessage(this).info(u"OK"); break;
1579  case ServerErrorCode::SyntaxError:
1580  CLogMessage(this).error(
1581  u"Malformed packet, syntax error: '%1'. This can also occur if an OBS sends frequency text messages.")
1582  << serverError.getCausingParameter();
1583  break;
1584  case ServerErrorCode::InvalidSrcCallsign:
1585  CLogMessage(this).info(u"FSD message was using an invalid callsign: %1 (%2)")
1586  << serverError.getCausingParameter() << serverError.getDescription();
1587  break;
1588  case ServerErrorCode::NoSuchCallsign:
1589  CLogMessage(this).info(u"FSD Server: no such callsign: %1 %2")
1590  << serverError.getCausingParameter() << serverError.getDescription();
1591  break;
1592  case ServerErrorCode::NoFlightPlan: CLogMessage(this).info(u"FSD Server: no flight plan"); break;
1593  case ServerErrorCode::NoWeatherProfile:
1594  CLogMessage(this).info(u"FSD Server: requested weather profile does not exist");
1595  break;
1596 
1597  // we have no idea what these mean
1598  case ServerErrorCode::AlreadyRegistered:
1599  CLogMessage(this).warning(u"Server says already registered: %1") << serverError.getDescription();
1600  break;
1601  case ServerErrorCode::InvalidCtrl:
1602  CLogMessage(this).warning(u"Server invalid control: %1") << serverError.getDescription();
1603  break;
1604  case ServerErrorCode::Unknown:
1605  CLogMessage(this).warning(u"Server sent unknown error code: %1 (%2)")
1606  << serverError.getCausingParameter() << serverError.getDescription();
1607  break;
1608  case ServerErrorCode::AuthTimeout: CLogMessage(this).warning(u"Client did not authenticate in time"); break;
1609  }
1610  if (serverError.isFatalError()) { disconnectFromServer(); }
1611  }
1612 
1613  void CFSDClient::handleRehost(const QStringList &tokens)
1614  {
1615  const Rehost rehost = Rehost::fromTokens(tokens);
1616 
1617  CLogMessage(this).info(u"Server requested we switch server to %1") << rehost.m_hostname;
1618 
1619  SWIFT_AUDIT_X(!m_rehosting, Q_FUNC_INFO, "Rehosting already in progress");
1620 
1621  m_rehosting = true;
1622  auto rehostingSocket = std::make_shared<QTcpSocket>();
1623  connect(rehostingSocket.get(), &QTcpSocket::connected, this, [this, rehostingSocket] {
1624  readDataFromSocket();
1625  CLogMessage(this).debug(u"Successfully switched server");
1626  m_socket = rehostingSocket;
1627  m_rehosting = false;
1628  rehostingSocket->disconnect(this);
1629  connectSocketSignals();
1630  readDataFromSocket();
1631  });
1632  connect(rehostingSocket.get(), &QTcpSocket::errorOccurred, this, [this, rehostingSocket] {
1633  CLogMessage(this).warning(u"Failed to switch server: %1") << rehostingSocket->errorString();
1634  m_rehosting = false;
1635  rehostingSocket->disconnect(this);
1636  if (m_socket->state() != QAbstractSocket::ConnectedState)
1637  {
1638  updateConnectionStatus(CConnectionStatus::Disconnected);
1639  }
1640  });
1641 
1642  initiateConnection(rehostingSocket, rehost.m_hostname);
1643  }
1644 
1645  void CFSDClient::handleMute(const QStringList &tokens)
1646  {
1647  const Mute mute = Mute::fromTokens(tokens);
1648  if (mute.receiver() != m_ownCallsign.asString()) { return; }
1649  emit muteRequestReceived(mute.m_mute);
1650  }
1651 
1652  void CFSDClient::initiateConnection(std::shared_ptr<QTcpSocket> rehostingSocket, const QString &rehostingHost)
1653  {
1654  const CServer server = this->getServer();
1655  const auto socket = rehostingSocket ? rehostingSocket : m_socket;
1656 
1657  // NOLINTBEGIN(cppcoreguidelines-init-variables)
1658  const QString host = rehostingSocket ? rehostingHost : server.getAddress();
1659  const quint16 port = rehostingSocket ? m_socket->peerPort() : static_cast<quint16>(getServer().getPort());
1660  // NOLINTEND(cppcoreguidelines-init-variables)
1661 
1662  resolveLoadBalancing(host, [=, this](const QString &host) {
1663  socket->connectToHost(host, port);
1664  if (!rehostingSocket) { this->startPositionTimers(); }
1665  });
1666  }
1667 
1668  void CFSDClient::resolveLoadBalancing(const QString &host, std::function<void(const QString &)> callback)
1669  {
1670  if (QHostAddress(host).isNull() && (getServer().getName() == "AUTOMATIC" || m_rehosting) &&
1671  getServer().getEcosystem() == CEcosystem::VATSIM)
1672  {
1673  // Not an IP -> Get IP for load balancing via HTTP
1674  Q_ASSERT_X(sApp, Q_FUNC_INFO, "Need app");
1675  CUrl url = sApp->getVatsimFsdHttpUrl();
1676  sApp->getFromNetwork(url, { this, [=](QNetworkReply *nwReplyPtr) {
1678 
1679  if (nwReply->error() == QNetworkReply::NoError)
1680  {
1681  QHostAddress addr(static_cast<QString>(nwReply->readAll()));
1682  if (!addr.isNull())
1683  {
1684  callback(addr.toString());
1685  return;
1686  }
1687  }
1688  callback(host);
1689  } });
1690  }
1691  else { callback(host); }
1692  }
1693 
1694  void CFSDClient::handleCustomPilotPacket(const QStringList &tokens)
1695  {
1696  const QString subType = tokens.at(2);
1697 
1698  if (subType == u"PIR")
1699  {
1700  PlaneInfoRequest planeInfoRequest = PlaneInfoRequest::fromTokens(tokens);
1701 
1702  const CSimulatedAircraft myAircraft = this->getOwnAircraft();
1703  const QString airlineIcao = m_server.getFsdSetup().force3LetterAirlineCodes() ?
1704  myAircraft.getAirlineIcaoCode().getDesignator() :
1705  myAircraft.getAirlineIcaoCode().getVDesignator();
1706  const QString acTypeICAO = myAircraft.getAircraftIcaoCode().getDesignator();
1707  const QString livery = this->getConfiguredLiveryString(myAircraft);
1708 
1709  sendPlaneInformation(planeInfoRequest.sender(), acTypeICAO, airlineIcao, livery);
1710  }
1711  else if (subType == "PI")
1712  {
1713  if (tokens.size() > 6 && tokens.at(3) == "X")
1714  {
1715  // This is the old version of a plane info request and no active client should ever send it.
1716  }
1717  else if (tokens.size() > 4 && tokens.at(3) == "GEN")
1718  {
1719  const PlaneInformation planeInformation = PlaneInformation::fromTokens(tokens);
1720  emit planeInformationReceived(planeInformation.sender(), planeInformation.m_aircraft,
1721  planeInformation.m_airline, planeInformation.m_livery);
1722  }
1723  }
1724  else if (subType == "I")
1725  {
1726  // SquawkBox interim pilot position. This one is producing too many precision errors. Therefore ignore it.
1727  }
1728  else if (subType == "VI")
1729  {
1730  // swift's updated interim pilot update.
1731  if (!isInterimPositionReceivingEnabledForServer()) { return; }
1732 
1733  const InterimPilotDataUpdate interimPilotDataUpdate = InterimPilotDataUpdate::fromTokens(tokens);
1734  const CCallsign callsign(interimPilotDataUpdate.sender(), CCallsign::Aircraft);
1735 
1736  CAircraftSituation situation(callsign,
1737  CCoordinateGeodetic(interimPilotDataUpdate.m_latitude,
1738  interimPilotDataUpdate.m_longitude,
1739  interimPilotDataUpdate.m_altitudeTrue),
1740  CHeading(interimPilotDataUpdate.m_heading, CHeading::True, CAngleUnit::deg()),
1741  CAngle(interimPilotDataUpdate.m_pitch, CAngleUnit::deg()),
1742  CAngle(interimPilotDataUpdate.m_bank, CAngleUnit::deg()),
1743  CSpeed(interimPilotDataUpdate.m_groundSpeed, CSpeedUnit::kts()));
1744  // NotSetGroundDetails because here we do not know if this FSD protocol actually utilizes this flag
1745  const COnGroundInfo og(interimPilotDataUpdate.m_onGround ? COnGroundInfo::OnGround :
1746  COnGroundInfo::NotOnGround,
1747  COnGroundInfo::NotSetGroundDetails);
1748  situation.setOnGroundInfo(og);
1749 
1750  // Ref T297, default offset time
1751  situation.setCurrentUtcTime();
1752  const qint64 offsetTimeMs =
1753  receivedPositionFixTsAndGetOffsetTime(situation.getCallsign(), situation.getMSecsSinceEpoch());
1754  situation.setTimeOffsetMs(offsetTimeMs);
1755 
1756  emit interimPilotDataUpdatedReceived(situation);
1757  }
1758  else if (subType == "FSIPI")
1759  {
1760  const PlaneInformationFsinn planeInformationFsinn = PlaneInformationFsinn::fromTokens(tokens);
1761  emit planeInformationFsinnReceived(planeInformationFsinn.sender(), planeInformationFsinn.m_airlineIcao,
1762  planeInformationFsinn.m_aircraftIcao,
1763  planeInformationFsinn.m_aircraftIcaoCombinedType,
1764  planeInformationFsinn.m_sendMModelString);
1765  }
1766  else if (subType == "FSIPIR")
1767  {
1768  const PlaneInfoRequestFsinn planeInfoRequestFsinn = PlaneInfoRequestFsinn::fromTokens(tokens);
1769  sendPlaneInformationFsinn(planeInfoRequestFsinn.sender());
1770  emit planeInformationFsinnReceived(planeInfoRequestFsinn.sender(), planeInfoRequestFsinn.m_airlineIcao,
1771  planeInfoRequestFsinn.m_aircraftIcao,
1772  planeInfoRequestFsinn.m_aircraftIcaoCombinedType,
1773  planeInfoRequestFsinn.m_sendMModelString);
1774  }
1775  else
1776  {
1777  // Unknown #SB opcode, just pass it on.
1778  const QString sender = tokens.at(0);
1779  const QStringList data = tokens.mid(3);
1780  emit customPilotPacketReceived(sender, data);
1781  }
1782  }
1783 
1784 #ifdef SWIFT_VATSIM_SUPPORT
1785  void CFSDClient::handleFsdIdentification(const QStringList &tokens)
1786  {
1787  if (m_protocolRevision >= PROTOCOL_REVISION_VATSIM_AUTH)
1788  {
1789  const FSDIdentification fsdIdentification = FSDIdentification::fromTokens(tokens);
1790  vatsim_auth_set_initial_challenge(m_clientAuth, qPrintable(fsdIdentification.m_initialChallenge));
1791 
1792  char fsdChallenge[33];
1793  vatsim_auth_generate_challenge(m_serverAuth, fsdChallenge);
1794  vatsim_auth_set_initial_challenge(m_serverAuth, fsdChallenge);
1795  sendClientIdentification(QString(fsdChallenge));
1796  }
1797  else
1798  {
1799  CLogMessage(this).error(
1800  u"You tried to connect to a VATSIM server without using VATSIM protocol, disconnecting!");
1801  disconnectFromServer();
1802  }
1803  }
1804 #endif
1805 
1806  void CFSDClient::handleUnknownPacket(const QString &line)
1807  {
1808  CLogMessage(this).warning(u"FSD unknown packet: '%1'") << line;
1809  }
1810 
1811  void CFSDClient::handleUnknownPacket(const QStringList &tokens) { this->handleUnknownPacket(tokens.join(", ")); }
1812 
1813  void CFSDClient::printSocketError(QAbstractSocket::SocketError socketError)
1814  {
1815  if (m_rehosting) { return; }
1816 
1817  CLogMessage(this).error(u"FSD socket error: %1") << this->socketErrorString(socketError);
1818  }
1819 
1820  void CFSDClient::handleSocketError(QAbstractSocket::SocketError socketError)
1821  {
1822  if (m_rehosting) { return; }
1823 
1824  const QString error = this->socketErrorString(socketError);
1825  switch (socketError)
1826  {
1827  // all named here need a logoff
1829  emit this->severeNetworkError(error);
1830  this->disconnectFromServer();
1831  break;
1832  default: break;
1833  }
1834  }
1835 
1836  void CFSDClient::handleSocketConnected()
1837  {
1838  if (m_protocolRevision == PROTOCOL_REVISION_CLASSIC)
1839  {
1840  this->sendLogin();
1841  this->updateConnectionStatus(CConnectionStatus::Connected);
1842  }
1843  }
1844 
1845  void CFSDClient::updateConnectionStatus(CConnectionStatus newStatus)
1846  {
1847  if (this->getConnectionStatus() == newStatus) { return; }
1848  if (newStatus.isConnected())
1849  {
1850  CEcosystem ecoSystem;
1851  {
1852  QWriteLocker l(&m_lockUserClientBuffered);
1853  m_server.setConnectedSinceNow();
1854  ecoSystem = m_server.getEcosystem();
1855  }
1856  this->setCurrentEcosystem(ecoSystem);
1857  }
1858  else
1859  {
1860  QWriteLocker l(&m_lockUserClientBuffered);
1861  m_server.markAsDisconnected();
1862  }
1863 
1864  if (newStatus.isDisconnected())
1865  {
1866  this->stopPositionTimers();
1867  this->clearState();
1868  this->setLastEcosystem(m_server.getEcosystem());
1869  this->setCurrentEcosystem(CEcosystem::NoSystem);
1870  this->saveNetworkStatistics(m_server.getName());
1871  }
1872 
1873  CConnectionStatus oldStatus;
1874  {
1875  QWriteLocker l(&m_lockConnectionStatus);
1876  oldStatus = m_connectionStatus;
1877  m_connectionStatus = newStatus;
1878  }
1879 
1880  emit this->connectionStatusChanged(oldStatus, newStatus);
1881  }
1882 
1883  void CFSDClient::consolidateTextMessage(const CTextMessage &textMessage)
1884  {
1885  if (textMessage.isSupervisorMessage()) { emit this->textMessagesReceived(textMessage); }
1886  else
1887  {
1888  m_textMessagesToConsolidate.addConsolidatedTextMessage(textMessage);
1889  m_dsSendTextMessage.inputSignal(); // trigger
1890  }
1891  }
1892 
1893  void CFSDClient::emitConsolidatedTextMessages()
1894  {
1895  emit this->textMessagesReceived(m_textMessagesToConsolidate);
1896  m_textMessagesToConsolidate.clear();
1897  }
1898 
1899  qint64 CFSDClient::receivedPositionFixTsAndGetOffsetTime(const CCallsign &callsign, qint64 markerTs)
1900  {
1901  Q_ASSERT_X(!callsign.isEmpty(), Q_FUNC_INFO, "Need callsign");
1902 
1903  if (markerTs < 0) { markerTs = QDateTime::currentMSecsSinceEpoch(); }
1904  if (!m_lastPositionUpdate.contains(callsign))
1905  {
1906  m_lastPositionUpdate.insert(callsign, markerTs);
1907  return CFsdSetup::c_positionTimeOffsetMsec;
1908  }
1909  const qint64 oldTs = m_lastPositionUpdate.value(callsign); // NOLINT(cppcoreguidelines-init-variables)
1910  m_lastPositionUpdate[callsign] = markerTs;
1911 
1912  // Ref T297, dynamic offsets
1913  const qint64 diff = qAbs(markerTs - oldTs);
1914  this->insertLatestOffsetTime(callsign, diff);
1915 
1916  int count = 0;
1917  const qint64 avgTimeMs = this->averageOffsetTimeMs(callsign, count, 3); // latest average
1918  qint64 offsetTime = CFsdSetup::c_positionTimeOffsetMsec;
1919 
1920  if (avgTimeMs < CFsdSetup::c_interimPositionTimeOffsetMsec && count >= 3)
1921  {
1922  offsetTime = CFsdSetup::c_interimPositionTimeOffsetMsec;
1923  }
1924 
1925  return m_additionalOffsetTime + offsetTime;
1926  }
1927 
1928  qint64 CFSDClient::currentOffsetTime(const CCallsign &callsign) const
1929  {
1930  Q_ASSERT_X(!callsign.isEmpty(), Q_FUNC_INFO, "Need callsign");
1931 
1932  if (!m_lastOffsetTimes.contains(callsign) || m_lastOffsetTimes[callsign].isEmpty())
1933  {
1934  return CFsdSetup::c_positionTimeOffsetMsec;
1935  }
1936  return m_lastOffsetTimes[callsign].front();
1937  }
1938 
1939  void CFSDClient::clearState()
1940  {
1941  m_rehosting = false;
1942  m_stoppedSendingVisualPositions = false;
1943  m_serverWantsVisualPositions = false;
1944  m_visualPositionUpdateSentCount = 0;
1945  m_textMessagesToConsolidate.clear();
1946  m_pendingAtisQueries.clear();
1947  m_lastPositionUpdate.clear();
1948  m_lastOffsetTimes.clear();
1949  m_atcStations.clear();
1950  m_queuedFsdMessages.clear();
1951  m_sentAircraftConfig = CAircraftParts::null();
1952  m_loginSince = -1;
1953  }
1954 
1955  void CFSDClient::clearState(const CCallsign &callsign)
1956  {
1957  if (callsign.isEmpty()) { return; }
1958  m_pendingAtisQueries.remove(callsign);
1959  m_lastPositionUpdate.remove(callsign);
1960  m_interimPositionReceivers.remove(callsign);
1961  m_lastOffsetTimes.remove(callsign);
1962  }
1963 
1964  void CFSDClient::insertLatestOffsetTime(const CCallsign &callsign, qint64 offsetMs)
1965  {
1966  QList<qint64> &offsets = m_lastOffsetTimes[callsign];
1967  offsets.push_front(offsetMs);
1968  if (offsets.size() > c_maxOffsetTimes) { offsets.removeLast(); }
1969  }
1970 
1971  qint64 CFSDClient::averageOffsetTimeMs(const CCallsign &callsign, int &count, int maxLastValues) const
1972  {
1973  const QList<qint64> &offsets = m_lastOffsetTimes[callsign];
1974  if (offsets.size() < 1) { return -1; }
1975  qint64 sum = 0;
1976  count = 0;
1977  for (qint64 v : offsets)
1978  {
1979  count++;
1980  sum += v;
1981  if (count > maxLastValues) { break; }
1982  }
1983  return qRound(static_cast<double>(sum) / count);
1984  }
1985 
1986  bool CFSDClient::isInterimPositionSendingEnabledForServer() const
1987  {
1988  const CFsdSetup::SendReceiveDetails d = this->getSetupForServer().getSendReceiveDetails();
1989  return (d & CFsdSetup::SendInterimPositions);
1990  }
1991 
1992  bool CFSDClient::isInterimPositionReceivingEnabledForServer() const
1993  {
1994  const CFsdSetup::SendReceiveDetails d = this->getSetupForServer().getSendReceiveDetails();
1995  return (d & CFsdSetup::ReceiveInterimPositions);
1996  }
1997 
1998  bool CFSDClient::isVisualPositionSendingEnabledForServer() const
1999  {
2000  const CFsdSetup::SendReceiveDetails d = this->getSetupForServer().getSendReceiveDetails();
2001  return (d & CFsdSetup::SendVisualPositions);
2002  }
2003 
2004  const CFsdSetup &CFSDClient::getSetupForServer() const { return m_server.getFsdSetup(); }
2005 
2006  void CFSDClient::maybeHandleAtisReply(const CCallsign &sender, const CCallsign &receiver, const QString &message)
2007  {
2008  Q_ASSERT(m_pendingAtisQueries.contains(sender));
2009  PendingAtisQuery &pendingQuery = m_pendingAtisQueries[sender];
2010  pendingQuery.m_atisMessage.push_back(message);
2011 
2012  // Wait maximum 5 seconds for the reply and release as text message after
2013  if (pendingQuery.m_queryTime.secsTo(QDateTime::currentDateTimeUtc()) > 5)
2014  {
2015  const QString atisMessage(pendingQuery.m_atisMessage.join(QChar::LineFeed));
2016  CTextMessage tm(atisMessage, sender, receiver);
2017  tm.setCurrentUtcTime();
2018  this->consolidateTextMessage(tm); // emit textMessagesReceived(tm);
2019  m_pendingAtisQueries.remove(sender);
2020  return;
2021  }
2022 
2023  // 4 digits followed by z (e.g. 0200z) is always the last atis line.
2024  // Some controllers leave the logoff time empty. Hence we accept anything
2025  // between 0-4 digits.
2026  thread_local const QRegularExpression reLogoff("^\\d{0,4}z$");
2027  if (reLogoff.match(message).hasMatch())
2028  {
2029  emit atisLogoffTimeReplyReceived(sender, message);
2030  CInformationMessage atisMessage(CInformationMessage::ATIS);
2031  for (const auto &line : std::as_const(pendingQuery.m_atisMessage))
2032  {
2033  if (!atisMessage.isEmpty()) atisMessage.appendMessage("\n");
2034  atisMessage.appendMessage(line);
2035  }
2036  emit atisReplyReceived(CCallsign(sender.toQString(), CCallsign::Atc), atisMessage);
2037  m_pendingAtisQueries.remove(sender);
2038  return;
2039  }
2040  }
2041 
2042  void CFSDClient::fsdMessageSettingsChanged()
2043  {
2044  if (m_rawFsdMessageLogFile.isOpen()) { m_rawFsdMessageLogFile.close(); }
2045  const CRawFsdMessageSettings setting = m_fsdMessageSetting.get();
2046  m_rawFsdMessagesEnabled = setting.areRawFsdMessagesEnabled();
2047 
2048  if (setting.getFileWriteMode() == CRawFsdMessageSettings::None || setting.getFileDir().isEmpty()) { return; }
2049  if (setting.getFileWriteMode() == CRawFsdMessageSettings::Truncate)
2050  {
2051  const QString filePath = CFileUtils::appendFilePaths(setting.getFileDir(), "rawfsdmessages.log");
2052  m_rawFsdMessageLogFile.setFileName(filePath);
2053  const bool res = m_rawFsdMessageLogFile.open(QIODevice::Text | QIODevice::WriteOnly);
2054  SWIFT_VERIFY_X(res, Q_FUNC_INFO, "Could not open log file");
2055  }
2056  else if (setting.getFileWriteMode() == CRawFsdMessageSettings::Append)
2057  {
2058  const QString filePath = CFileUtils::appendFilePaths(setting.getFileDir(), "rawfsdmessages.log");
2059  m_rawFsdMessageLogFile.setFileName(filePath);
2060  const bool res = m_rawFsdMessageLogFile.open(QIODevice::Text | QIODevice::WriteOnly | QIODevice::Append);
2061  SWIFT_VERIFY_X(res, Q_FUNC_INFO, "Could not open log file");
2062  }
2063  else if (setting.getFileWriteMode() == CRawFsdMessageSettings::Timestamped)
2064  {
2065  QString filename("rawfsdmessages");
2066  filename += QLatin1String("_");
2067  filename += QDateTime::currentDateTime().toString(QStringLiteral("yyMMddhhmmss"));
2068  filename += QLatin1String(".log");
2069  const QString filePath = CFileUtils::appendFilePaths(setting.getFileDir(), filename);
2070  m_rawFsdMessageLogFile.setFileName(filePath);
2071  const bool res = m_rawFsdMessageLogFile.open(QIODevice::Text | QIODevice::WriteOnly);
2072  SWIFT_VERIFY_X(res, Q_FUNC_INFO, "Could not open log file");
2073  }
2074  }
2075 
2076  swift::misc::aviation::CCallsignSet CFSDClient::getInterimPositionReceivers() const
2077  {
2078  return m_interimPositionReceivers;
2079  }
2080 
2081  void CFSDClient::setInterimPositionReceivers(const swift::misc::aviation::CCallsignSet &interimPositionReceivers)
2082  {
2083  m_interimPositionReceivers = interimPositionReceivers;
2084  }
2085 
2086  bool CFSDClient::isPendingConnection() const
2087  {
2088  return m_connectionStatus.isConnecting() || m_connectionStatus.isDisconnecting();
2089  }
2090 
2091  int CFSDClient::increaseStatisticsValue(const QString &identifier, const QString &appendix)
2092  {
2093  if (identifier.isEmpty() || !m_statistics) { return -1; }
2094 
2095  QWriteLocker l(&m_lockStatistics);
2096  const QString i = appendix.isEmpty() ? identifier : identifier % u"." % appendix;
2097  int &v = m_callStatistics[i];
2098  v++;
2099 
2100  constexpr int MaxTimeValues = 50;
2101  m_callByTime.push_front(QPair<qint64, QString>(QDateTime::currentMSecsSinceEpoch(), i));
2102  if (m_callByTime.size() > MaxTimeValues) { m_callByTime.removeLast(); }
2103  return v;
2104  }
2105 
2106  int CFSDClient::increaseStatisticsValue(const QString &identifier, int value)
2107  {
2108  return increaseStatisticsValue(identifier, QString::number(value));
2109  }
2110 
2111  void CFSDClient::clearStatistics()
2112  {
2113  QWriteLocker l(&m_lockStatistics);
2114  m_callStatistics.clear();
2115  m_callByTime.clear();
2116  }
2117 
2118  QString CFSDClient::getNetworkStatisticsAsText(bool reset, const QString &separator)
2119  {
2120  QVector<std::pair<int, QString>> transformed;
2121  QMap<QString, int> callStatistics;
2122  QVector<QPair<qint64, QString>> callByTime;
2123 
2124  {
2125  QReadLocker l(&m_lockStatistics);
2126  callStatistics = m_callStatistics;
2127  callByTime = m_callByTime;
2128  }
2129 
2130  if (callStatistics.isEmpty()) { return {}; }
2131  for (const auto [key, value] : makePairsRange(std::as_const(callStatistics)))
2132  {
2133  // key is pair.first, value is pair.second
2134  transformed.push_back({ value, key });
2135  }
2136 
2137  // sorted by value
2138  std::sort(transformed.begin(), transformed.end(), std::greater<>());
2139  QString stats;
2140  for (const auto &pair : transformed)
2141  {
2142  stats += (stats.isEmpty() ? QString() : separator) % pair.second % u": " % QString::number(pair.first);
2143  }
2144 
2145  for (const auto &pair : transformed)
2146  {
2147  stats += (stats.isEmpty() ? QString() : separator) % pair.second % u": " % QString::number(pair.first);
2148  }
2149 
2150  if (!callByTime.isEmpty())
2151  {
2152  const qint64 lastTs = callByTime.front().first;
2153  for (const auto &pair : std::as_const(callByTime))
2154  {
2155  const qint64 deltaTs = lastTs - pair.first;
2156  stats += separator % QStringLiteral("%1").arg(deltaTs, 5, 10, QChar('0')) % u": " % pair.second;
2157  }
2158  }
2159 
2160  if (reset) { this->clearStatistics(); }
2161  return stats;
2162  }
2163 
2164  void CFSDClient::gracefulShutdown()
2165  {
2166  disconnectFromServer(); // async, runs in background thread
2167  quitAndWait();
2168  }
2169 
2170  void CFSDClient::readDataFromSocketMaxLines(int maxLines)
2171  {
2172  if (m_socket->bytesAvailable() < 1) { return; }
2173 
2174  int lines = 0;
2175 
2176  // reads at least one line if available
2177  while (m_socket->canReadLine())
2178  {
2179  const QByteArray dataEncoded = m_socket->readLine();
2180  if (dataEncoded.isEmpty()) { continue; }
2181  const QString data = m_decoder(dataEncoded);
2182  this->parseMessage(data);
2183  lines++;
2184 
2185  static constexpr int MaxLines = 75 - 1;
2186  if (maxLines < 0) { maxLines = MaxLines; }
2187 
2188  if (lines > maxLines)
2189  {
2190  static constexpr int DelayMs = 10;
2191  const int newMax = qRound(1.2 * lines); // 20% more
2192 
2193  CLogMessage(this).debug(u"ReadDataFromSocket has too many lines (>%1), will read again in %2ms")
2194  << MaxLines << DelayMs;
2195  QPointer<CFSDClient> myself(this);
2196  QTimer::singleShot(DelayMs, this, [=] {
2197  if (!sApp || sApp->isShuttingDown()) { return; }
2198  if (myself) { myself->readDataFromSocketMaxLines(newMax); }
2199  });
2200  break;
2201  }
2202  }
2203  }
2204 
2205  QString CFSDClient::socketErrorString(QAbstractSocket::SocketError error) const
2206  {
2207  QString e = CFSDClient::socketErrorToQString(error);
2208  if (!m_socket->errorString().isEmpty()) { e += QStringLiteral(": ") % m_socket->errorString(); }
2209  return e;
2210  }
2211 
2212  QString CFSDClient::socketErrorToQString(QAbstractSocket::SocketError error)
2213  {
2214  static const QMetaEnum metaEnum = QMetaEnum::fromType<QAbstractSocket::SocketError>();
2215  return metaEnum.valueToKey(error);
2216  }
2217 
2218  void CFSDClient::parseMessage(const QString &lineRaw)
2219  {
2220  MessageType messageType = MessageType::Unknown;
2221  QString cmd;
2222  const QString line = lineRaw.trimmed();
2223 
2224  if (m_printToConsole) { qDebug() << "FSD Recv=>" << line; }
2225  emitRawFsdMessage(line, false);
2226 
2227  for (const QString &str : makeKeysRange(std::as_const(m_messageTypeMapping)))
2228  {
2229  if (line.startsWith(str))
2230  {
2231  cmd = str;
2232  messageType = m_messageTypeMapping.value(str, MessageType::Unknown);
2233  break;
2234  }
2235  }
2236 
2237  // statistics
2238  if (m_statistics)
2239  {
2240  increaseStatisticsValue(QStringLiteral("parseMessage"), this->messageTypeToString(messageType));
2241  }
2242 
2243  if (messageType != MessageType::Unknown)
2244  {
2245  // Cutoff the cmd from the beginning
2246  const QString payload = line.mid(cmd.size()).trimmed();
2247 
2248  // We expected a payload, but there is nothing
2249  if (payload.length() == 0) { return; }
2250 
2251  const QStringList tokens = payload.split(':');
2252  switch (messageType)
2253  {
2254  // ignored ones
2255  case MessageType::AddAtc:
2256  case MessageType::AddPilot:
2257  case MessageType::ServerHeartbeat:
2258  case MessageType::ProController:
2259  case MessageType::ClientIdentification: break;
2260 
2261  // handled ones
2262  case MessageType::AtcDataUpdate: handleAtcDataUpdate(tokens); break;
2263 #ifdef SWIFT_VATSIM_SUPPORT
2264  case MessageType::AuthChallenge: handleAuthChallenge(tokens); break;
2265  case MessageType::AuthResponse: handleAuthResponse(tokens); break;
2266 #endif
2267  case MessageType::ClientQuery: handleClientQuery(tokens); break;
2268  case MessageType::ClientResponse: handleClientResponse(tokens); break;
2269  case MessageType::DeleteATC: handleDeleteATC(tokens); break;
2270  case MessageType::DeletePilot: handleDeletePilot(tokens); break;
2271  case MessageType::FlightPlan: handleFlightPlan(tokens); break;
2272 #ifdef SWIFT_VATSIM_SUPPORT
2273  case MessageType::FsdIdentification: handleFsdIdentification(tokens); break;
2274 #endif
2275  case MessageType::KillRequest: handleKillRequest(tokens); break;
2276  case MessageType::PilotDataUpdate: handlePilotDataUpdate(tokens); break;
2277  case MessageType::Ping: handlePing(tokens); break;
2278  case MessageType::Pong: handlePong(tokens); break;
2279  case MessageType::ServerError: handleServerError(tokens); break;
2280  case MessageType::TextMessage: handleTextMessage(tokens); break;
2281  case MessageType::PilotClientCom: handleCustomPilotPacket(tokens); break;
2282  case MessageType::VisualPilotDataUpdate:
2283  case MessageType::VisualPilotDataPeriodic:
2284  case MessageType::VisualPilotDataStopped: handleVisualPilotDataUpdate(tokens, messageType); break;
2285  case MessageType::VisualPilotDataToggle: handleVisualPilotDataToggle(tokens); break;
2286  case MessageType::EuroscopeSimData: handleEuroscopeSimData(tokens); break;
2287  case MessageType::Rehost: handleRehost(tokens); break;
2288  case MessageType::Mute: handleMute(tokens); break;
2289 
2290  // normally we should not get here
2291  default:
2292  case MessageType::Unknown: handleUnknownPacket(tokens); break;
2293  }
2294  }
2295  else { handleUnknownPacket(line); }
2296  }
2297 
2298  void CFSDClient::emitRawFsdMessage(const QString &fsdMessage, bool isSent)
2299  {
2300  if (!m_unitTestMode && !m_rawFsdMessagesEnabled) { return; }
2301  QString fsdMessageFiltered(fsdMessage);
2302  if (m_filterPasswordFromLogin)
2303  {
2304  if (fsdMessageFiltered.startsWith("#AP"))
2305  {
2306  thread_local const QRegularExpression re(R"(^(#AP\w+:SERVER:\d+:)[^:]+(:\d+:\d+:\d+:.+)$)");
2307  fsdMessageFiltered.replace(re, "\\1<password>\\2");
2308  m_filterPasswordFromLogin = false;
2309  }
2310  }
2311 
2312  const QString prefix = isSent ? "FSD Sent=>" : "FSD Recv=>";
2313  CRawFsdMessage rawMessage(prefix + fsdMessageFiltered);
2314  rawMessage.setCurrentUtcTime();
2315  if (m_rawFsdMessageLogFile.isOpen())
2316  {
2317  QTextStream stream(&m_rawFsdMessageLogFile);
2318  stream << rawMessage.toQString().trimmed() << Qt::endl;
2319  }
2320  emit rawFsdMessage(rawMessage);
2321  }
2322 
2323  bool CFSDClient::saveNetworkStatistics(const QString &server)
2324  {
2325  if (m_callStatistics.isEmpty()) { return false; }
2326 
2327  const QString s = this->getNetworkStatisticsAsText(false, "\n");
2328  if (s.isEmpty()) { return false; }
2329  const QString fn = QStringLiteral("networkstatistics_%1_%2.log")
2330  .arg(QDateTime::currentDateTimeUtc().toString("yyMMddhhmmss"), server);
2331  const QString fp = CFileUtils::appendFilePaths(CSwiftDirectories::logDirectory(), fn);
2332  return CFileUtils::writeStringToFile(s, fp);
2333  }
2334 
2335  void CFSDClient::startPositionTimers()
2336  {
2337  m_positionUpdateTimer.start(c_updatePositionIntervalMsec);
2338  m_scheduledConfigUpdate.start(c_processingIntervalMsec);
2339  m_fsdSendMessageTimer.start(c_sendFsdMsgIntervalMsec);
2340  m_queuedFsdMessages.clear(); // clear everything before the timer is started
2341 
2342  // interim positions
2343  // NOLINTBEGIN(bugprone-branch-clone)
2344  if (this->isInterimPositionSendingEnabledForServer())
2345  {
2346  m_interimPositionUpdateTimer.start(c_updateInterimPositionIntervalMsec);
2347  }
2348  else { m_interimPositionUpdateTimer.stop(); }
2349  if (this->isVisualPositionSendingEnabledForServer())
2350  {
2351  m_visualPositionUpdateTimer.start(c_updateVisualPositionIntervalMsec);
2352  }
2353  else { m_visualPositionUpdateTimer.stop(); }
2354  // NOLINTEND(bugprone-branch-clone)
2355  }
2356 
2357  void CFSDClient::stopPositionTimers()
2358  {
2359  m_positionUpdateTimer.stop();
2360  m_interimPositionUpdateTimer.stop();
2361  m_visualPositionUpdateTimer.stop();
2362  m_scheduledConfigUpdate.stop();
2363  m_fsdSendMessageTimer.stop();
2364  }
2365 
2366  void CFSDClient::updateAtisMap(const QString &callsign, AtisLineType type, const QString &line)
2367  {
2368  // NOLINTBEGIN(bugprone-branch-clone)
2369  if (type == AtisLineType::VoiceRoom)
2370  {
2371  m_mapAtisMessages[callsign].m_voiceRoom = line;
2372  m_mapAtisMessages[callsign].m_lineCount++;
2373  return;
2374  }
2375  if (type == AtisLineType::TextMessage)
2376  {
2377  m_mapAtisMessages[callsign].m_textLines.push_back(line);
2378  m_mapAtisMessages[callsign].m_lineCount++;
2379  return;
2380  }
2381  if (type == AtisLineType::ZuluLogoff)
2382  {
2383  m_mapAtisMessages[callsign].m_zuluLogoff = line;
2384  m_mapAtisMessages[callsign].m_lineCount++;
2385  return;
2386  }
2387  if (!m_mapAtisMessages.contains(callsign)) { return; }
2388 
2389  // Ignore the check for line count.
2390  m_mapAtisMessages[callsign].m_lineCount++;
2391 
2392  const CCallsign cs(callsign, CCallsign::Atc);
2393  // emit atisVoiceRoomReplyReceived(cs, m_mapAtisMessages[callsign].voiceRoom);
2394  emit atisLogoffTimeReplyReceived(cs, m_mapAtisMessages[callsign].m_zuluLogoff);
2395 
2396  CInformationMessage atisMessage(CInformationMessage::ATIS);
2397  for (const QString &tm : std::as_const(m_mapAtisMessages[callsign].m_textLines))
2398  {
2399  const QString fixed = tm.trimmed();
2400  if (!fixed.isEmpty())
2401  {
2402  // detect the stupid z1, z2, z3 placeholders
2404  thread_local const QRegularExpression RegExp(R"([\n\t\r])");
2405  const QString test = fixed.toLower().remove(RegExp);
2406  if (test == "z") return;
2407  if (test.startsWith("z") && test.length() == 2) return; // z1, z2, ..
2408  if (test.length() == 1) return; // sometimes just z
2409 
2410  // append
2411  if (!atisMessage.isEmpty()) atisMessage.appendMessage("\n");
2412  atisMessage.appendMessage(fixed);
2413  }
2414  }
2415 
2416  emit this->atisReplyReceived(cs, atisMessage);
2417 
2418  m_mapAtisMessages.remove(callsign);
2419  // NOLINTEND(bugprone-branch-clone)
2420  }
2421 
2422  void CFSDClient::pendingTimeoutCheck()
2423  {
2424  if (!this->isPendingConnection()) { return; }
2425 
2426  const qint64 age = QDateTime::currentMSecsSinceEpoch() - m_loginSince;
2427  if (age < PendingConnectionTimeoutMs) { return; }
2428 
2429  // time out
2430  CLogMessage(this).warning(u"Timeout on pending connection to '%1'") << this->getServer().getName();
2431  this->disconnectFromServer();
2432  }
2433 
2434  CLength CFSDClient::fixAtcRange(const CLength &networkRange, const CCallsign &cs)
2435  {
2445  // ATIS often have a range of 0 nm. Correct this to a proper value.
2446  const QString suffix = cs.getSuffix();
2447  if (suffix.contains(QStringLiteral("ATIS"), Qt::CaseInsensitive))
2448  {
2449  static const CLength l_Atis(150.0, CLengthUnit::NM());
2450  return maxOrNotNull(networkRange, l_Atis);
2451  }
2452  if (suffix.contains(QStringLiteral("GND"), Qt::CaseInsensitive))
2453  {
2454  static const CLength l_Gnd(10.0, CLengthUnit::NM());
2455  return maxOrNotNull(networkRange, l_Gnd);
2456  }
2457  if (suffix.contains(QStringLiteral("TWR"), Qt::CaseInsensitive))
2458  {
2459  static const CLength l_Twr(25.0, CLengthUnit::NM());
2460  return maxOrNotNull(networkRange, l_Twr);
2461  }
2462  if (suffix.contains(QStringLiteral("DEP"), Qt::CaseInsensitive))
2463  {
2464  static const CLength l_Dep(150.0, CLengthUnit::NM());
2465  return maxOrNotNull(networkRange, l_Dep);
2466  }
2467  if (suffix.contains(QStringLiteral("APP"), Qt::CaseInsensitive))
2468  {
2469  static const CLength l_App(150.0, CLengthUnit::NM());
2470  return maxOrNotNull(networkRange, l_App);
2471  }
2472  if (suffix.contains(QStringLiteral("CTR"), Qt::CaseInsensitive))
2473  {
2474  static const CLength l_Ctr(300.0, CLengthUnit::NM());
2475  return maxOrNotNull(networkRange, l_Ctr);
2476  }
2477  if (suffix.contains(QStringLiteral("FSS"), Qt::CaseInsensitive))
2478  {
2479  static const CLength l_Fss(1500.0, CLengthUnit::NM());
2480  return maxOrNotNull(networkRange, l_Fss);
2481  }
2482 
2483  return networkRange;
2484  }
2485 
2486  CLength CFSDClient::maxOrNotNull(const CLength &l1, const CLength &l2)
2487  {
2488  if (l1.isNull()) { return l2; }
2489  if (l2.isNull()) { return l1; }
2490  return (l2 > l1) ? l2 : l1;
2491  }
2492 
2493  QString CFSDClient::noColons(const QString &input)
2494  {
2495  if (!input.contains(':')) { return input; }
2496  QString copy(input);
2497  return copy.remove(':');
2498  }
2499 
2500  FlightType CFSDClient::getFlightType(CFlightPlan::FlightRules flightRules)
2501  {
2502  switch (flightRules)
2503  {
2504  case CFlightPlan::IFR: return FlightType::IFR;
2505  case CFlightPlan::VFR: return FlightType::VFR;
2506  case CFlightPlan::SVFR: return FlightType::SVFR;
2507  case CFlightPlan::DVFR: return FlightType::DVFR;
2508  default: return FlightType::IFR;
2509  }
2510  }
2511 
2512  const QString &CFSDClient::messageTypeToString(MessageType mt) const
2513  {
2514  QHash<QString, MessageType>::const_iterator i = m_messageTypeMapping.constBegin();
2515  while (i != m_messageTypeMapping.constEnd())
2516  {
2517  if (i.value() == mt) { return i.key(); }
2518  ++i;
2519  }
2520 
2521  static const QString empty;
2522  return empty;
2523  }
2524 
2525  void CFSDClient::handleIllegalFsdState(const QString &message)
2526  {
2527  if (CBuildConfig::isLocalDeveloperDebugBuild()) { SWIFT_VERIFY_X(false, Q_FUNC_INFO, "Illegal FSD state"); }
2528  CLogMessage(this).warning(message);
2529  }
2530 
2531  const QJsonObject &CFSDClient::JsonPackets::aircraftConfigRequest()
2532  {
2533  static const QJsonObject jsonObject { { "request", "full" } };
2534  return jsonObject;
2535  }
2536 
2537 } // namespace swift::core::fsd
SWIFT_CORE_EXPORT swift::core::CApplication * sApp
Single instance of application object.
Definition: application.cpp:71
QNetworkReply * getFromNetwork(const swift::misc::network::CUrl &url, const CallbackSlot &callback, int maxRedirects=DefaultMaxRedirects)
Request to get network reply.
data::CGlobalSetup getGlobalSetup() const
Global setup.
static constexpr int NoLogRequestId
network request without logging
Definition: application.h:413
swift::misc::db::CDistribution getOwnDistribution() const
Own distribution.
QNetworkReply * postToNetwork(const QNetworkRequest &request, int logId, const QByteArray &data, const CallbackSlot &callback)
Post to network.
swift::misc::network::CUrl getVatsimFsdHttpUrl() const
Get VATSIM FSD HTTP URL.
bool isShuttingDown() const
Is application shutting down?
swift::misc::network::CUrl getVatsimAuthUrl() const
VATSIM auth URL.
Definition: globalsetup.h:143
FSD Message: Add Pilot.
Definition: addpilot.h:19
void setCallsign(const swift::misc::aviation::CCallsign &callsign)
Preset functions.
Definition: fsdclient.cpp:159
void setIcaoCodes(const swift::misc::simulation::CSimulatedAircraft &ownAircraft)
Preset functions.
Definition: fsdclient.cpp:169
void sendClientQueryCom1Freq(const swift::misc::aviation::CCallsign &callsign)
Definition: fsdclient.cpp:459
void sendFlightPlan(const swift::misc::aviation::CFlightPlan &flightPlan)
Definition: fsdclient.cpp:668
bool isConnected() const
Connection status.
Definition: fsdclient.h:254
void sendTextMessages(const swift::misc::network::CTextMessageList &messages)
Definition: fsdclient.cpp:567
void sendClientQueryAtis(const swift::misc::aviation::CCallsign &callsign)
Definition: fsdclient.cpp:474
void setServer(const swift::misc::network::CServer &server)
Preset functions.
Definition: fsdclient.cpp:140
void setLiveryAndModelString(const QString &livery, bool sendLiveryString, const QString &modelString, bool sendModelString)
Preset functions.
Definition: fsdclient.cpp:188
const swift::misc::network::CServer & getServer() const
Get the server.
Definition: fsdclient.h:161
swift::misc::network::CLoginMode getLoginMode() const
Mode.
Definition: fsdclient.h:189
void disconnectFromServer()
Connect/disconnect.
Definition: fsdclient.cpp:263
void sendPlaneInfoRequest(const swift::misc::aviation::CCallsign &receiver)
Definition: fsdclient.cpp:718
void sendClientQueryRealName(const swift::misc::aviation::CCallsign &callsign)
Definition: fsdclient.cpp:464
PilotRating getPilotRating() const
Rating.
Definition: fsdclient.h:197
QStringList getPresetValues() const
List of all preset values.
Definition: fsdclient.cpp:217
void sendClientQueryServer(const swift::misc::aviation::CCallsign &callsign)
Definition: fsdclient.cpp:469
void setSimType(const swift::misc::simulation::CSimulatorInfo &simInfo)
Preset functions.
Definition: fsdclient.cpp:198
void sendClientQueryAircraftConfig(const swift::misc::aviation::CCallsign &callsign)
Definition: fsdclient.cpp:484
void textMessageSent(const swift::misc::network::CTextMessage &sentMessage)
We have sent a text message.
void sendTextMessage(const swift::misc::network::CTextMessage &message)
Definition: fsdclient.cpp:613
void sendClientQueryFlightPlan(const swift::misc::aviation::CCallsign &callsign)
Definition: fsdclient.cpp:479
void sendRadioMessage(const QVector< int > &frequencieskHz, const QString &message)
Definition: fsdclient.cpp:655
void connectToServer()
Connect/disconnect.
Definition: fsdclient.cpp:229
void sendPlaneInfoRequestFsinn(const swift::misc::aviation::CCallsign &callsign)
Definition: fsdclient.cpp:733
swift::misc::network::CConnectionStatus getConnectionStatus() const
Connection status.
Definition: fsdclient.h:249
bool isDisconnected() const
Connection status.
Definition: fsdclient.h:255
FSD Message: flightplan.
Definition: flightplan.h:16
FSinn specific version of plane information request.
Request to send plane information. Shall be answered by a PlaneInformation message.
This packet is sent in reply to a PIR request to inform the client which multiplayer model to use....
Text, radio or private message.
Definition: textmessage.h:19
QString getFileDir() const
Get file directory.
FileWriteMode getFileWriteMode() const
Get file write mode.
bool areRawFsdMessagesEnabled() const
Are raw FSD messages enabled?
Base class for a long-lived worker object which lives in its own thread.
Definition: worker.h:300
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 & log(StatusSeverity s, const char16_t(&m)[N])
Set the severity and format string.
Derived & error(const char16_t(&format)[N])
Set the severity to error, 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.
bool isEmpty() const
Synonym for empty.
Definition: sequence.h:285
Callable wrapper for a member function with function signature F.
Definition: slot.h:62
Streamable status message, e.g.
static bool isInThisThread(const QObject *toBeTested)
Is the current thread the object's thread?
Definition: threadutils.cpp:16
qint64 getMSecsSinceEpoch() const
Timestamp as ms value.
void setCurrentUtcTime()
Set the current time as timestamp.
void setTimeOffsetMs(qint64 offset)
Milliseconds to add to timestamp for interpolation.
const QString & getDesignator() const
Get ICAO designator, e.g. "B737".
Value object encapsulating information of aircraft's parts.
Definition: aircraftparts.h:26
void setOnGround(bool onGround)
Set aircraft on ground.
void setGearDown(bool down)
Set gear down.
Definition: aircraftparts.h:97
void setLights(const CAircraftLights &lights)
Set aircraft lights.
Definition: aircraftparts.h:81
Value object encapsulating information of an aircraft's situation.
void setPressureAltitude(const CAltitude &altitude)
Set pressure altitude.
geo::CLongitude longitude() const
Longitude.
geo::CLatitude latitude() const
Latitude.
const CCallsign & getCallsign() const
Corresponding callsign.
void setOnGroundInfo(const aviation::COnGroundInfo &info)
Set the on ground info.
const CAltitude & getAltitude() const
Get altitude.
const QString & getDesignator() const
Get airline, e.g. "DLH".
QString getVDesignator() const
Get airline, e.g. "DLH", but "VMVA" for virtual airlines.
const QString & asString() const
Get code.
Altitude as used in aviation, can be AGL or MSL altitude.
Definition: altitude.h:52
QString asFpVatsimAltitudeString() const
As simple VATSIM string, only FLxxx or altitude as ft.
Definition: altitude.cpp:363
void parseFromString(const QString &value)
Parse value from string.
Definition: altitude.cpp:140
Value object for a list of ATC stations.
CAtcStationList findIfFrequencyIsWithinSpacing(const physical_quantities::CFrequency &frequency)
Find 0..n stations with frequency (with 5 kHz spacing for .x20/.x25 and .x70/.x75)
Value object encapsulating information of a callsign.
Definition: callsign.h:30
const QString & asString() const
Get callsign (normalized)
Definition: callsign.h:96
QString getSuffix() const
Get the callsign suffix ("TWR", "ATIS" ...) if any ("_" is removed)
Definition: callsign.cpp:212
bool isEmpty() const
Is empty?
Definition: callsign.h:63
QString getFsdCallsignString() const
The callsign string used with FSD.
Definition: callsign.cpp:176
Value object for a set of callsigns.
Definition: callsignset.h:26
Flightplan-related information about an aircraft (aircraft ICAO, equipment and WTC)
QString asIcaoString() const
Full string in ICAO format: "AIRCRAFT_ICAO/WTC-EQUIPMENT/SSR".
QString asFaaString() const
Full string in FAA format: "H/J (if heavy/super)/AIRCRAFT_ICAO/EQUIPMENT-CODE".
Value object for a flight plan.
Definition: flightplan.h:148
const physical_quantities::CTime & getFuelTime() const
Get amount of fuel load in time.
Definition: flightplan.h:339
const physical_quantities::CSpeed & getCruiseTrueAirspeed() const
Get planned cruise TAS.
Definition: flightplan.h:354
const QString & getRoute() const
Get route string.
Definition: flightplan.h:363
FlightRules
Flight rules (VFR or IFR)
Definition: flightplan.h:155
const QDateTime & getTakeoffTimePlanned() const
Get planned takeoff time (planned)
Definition: flightplan.h:318
FlightRules getFlightRules() const
Get flight rules as in FlightRules.
Definition: flightplan.h:357
const QString & getRemarks() const
Get remarks string.
Definition: flightplan.h:375
const CAirportIcaoCode & getAlternateAirportIcao() const
Get alternate destination airport ICAO code.
Definition: flightplan.h:315
const CAltitude & getCruiseAltitude() const
Cruising altitudes.
Definition: flightplan.h:348
const physical_quantities::CTime & getEnrouteTime() const
Get planned enroute flight time.
Definition: flightplan.h:330
const CAirportIcaoCode & getOriginAirportIcao() const
Get origin airport ICAO code.
Definition: flightplan.h:309
const CAirportIcaoCode & getDestinationAirportIcao() const
Get destination airport ICAO code.
Definition: flightplan.h:312
const QDateTime & getTakeoffTimeActual() const
Get actual takeoff time (actual)
Definition: flightplan.h:324
CFlightPlanAircraftInfo getAircraftInfo() const
Get ICAO aircraft NAV/COM equipment.
Definition: flightplan.h:384
Heading as used in aviation, can be true or magnetic heading.
Definition: heading.h:41
Value object encapsulating information message (ATIS, METAR, TAF)
Information about the ground status.
Definition: ongroundinfo.h:19
bool isRestricted() const
Restricted channel?
Definition: distribution.h:66
CONTAINER findClosest(int number, const ICoordinateGeodetic &coordinate) const
Find 0..n objects closest to the given coordinate.
QString toQString(bool i18n=false) const
Cast as QString.
Definition: mixinstring.h:74
Class which can be directly used to access an.
Value object encapsulating information about a connection status.
bool isConnected() const
Query status.
bool isDisconnected() const
Query status.
Ecosystem of server belonging together.
Definition: ecosystem.h:21
Value object for a FSD setup.
Definition: fsdsetup.h:24
bool shouldSendFlightPlanEquipmentInIcaoFormat() const
FSD setup flags.
Definition: fsdsetup.h:110
const QString & getTextCodec() const
Get codec.
Definition: fsdsetup.h:77
bool receiveEuroscopeSimData() const
FSD setup flags.
Definition: fsdsetup.h:109
Value object encapsulating information about login mode.
Definition: loginmode.h:18
bool isObserver() const
Is login as observer?
Definition: loginmode.h:37
bool isPilot() const
Is login as pilot?
Definition: loginmode.h:34
Value object for a raw FSD message.
Definition: rawfsdmessage.h:25
Value object encapsulating information of a server.
Definition: server.h:28
const CFsdSetup & getFsdSetup() const
Get FSD setup.
Definition: server.h:140
const QString & getAddress() const
Get address.
Definition: server.h:80
const CUser & getUser() const
Get user.
Definition: server.h:86
ServerType getServerType() const
Get server type.
Definition: server.h:164
Value object encapsulating information of a text message.
Definition: textmessage.h:31
bool isEmpty() const
Empty message.
Definition: textmessage.h:90
bool isSupervisorMessage() const
Supervisor message?
Definition: textmessage.cpp:58
Value object encapsulating a list of text messages.
CTextMessageList getPrivateMessages() const
Private messages.
CTextMessageList getRadioMessages() const
Public messages.
CTextMessageList markedAsSent()
Marked as sent.
Value object encapsulating information of a location, kind of simplified CValueObject compliant versi...
Definition: url.h:27
const QString & getPassword() const
Get password.
Definition: user.h:65
const QString & getRealName() const
Get full name.
Definition: user.h:59
const QString & getId() const
Get id.
Definition: user.h:119
QString getRealNameAndHomeBase(const QString &separator=QString(" ")) const
Real name + homebase.
Definition: user.cpp:45
Direct in memory access to client (network client) data.
Physical unit angle (radians, degrees)
Definition: angle.h:23
Physical unit length (length)
Definition: length.h:18
int valueInteger(MU unit) const
As integer value.
double value(MU unit) const
Value in given unit.
QList< int > getHrsMinSecParts() const
Parts hh, mm, ss.
Definition: time.cpp:89
QString getSwiftLiveryString(bool aircraftIcao=true, bool livery=true, bool model=true) const
swift livery string (to be sent via network)
Delegating class which can be directly used to access an.
bool updateOwnCallsign(const aviation::CCallsign &callsign)
Update aircraft's callsign.
aviation::CCallsign getOwnCallsign() const
Own aircraft's callsign.
bool updateOwnIcaoCodes(const aviation::CAircraftIcaoCode &aircraftIcaoData, const aviation::CAirlineIcaoCode &airlineIcaoCode)
Update ICAO data.
swift::misc::geo::CCoordinateGeodetic getOwnAircraftPosition() const
Own aircraft's position.
CSimulatedAircraft getOwnAircraft() const
Own aircraft.
Class which can be directly used to access an.
Comprehensive information of an aircraft.
const QString & getAirlineIcaoCodeDesignator() const
Airline ICAO code designator.
const aviation::CAircraftIcaoCode & getAircraftIcaoCode() const
Get aircraft ICAO info.
const QString & getAircraftIcaoCombinedType() const
Aircraft ICAO combined code.
const QString & getAircraftIcaoCodeDesignator() const
Aircraft ICAO code designator.
const simulation::CAircraftModel & getModel() const
Get model (model used for mapping)
const aviation::CAirlineIcaoCode & getAirlineIcaoCode() const
Airline ICAO code if any.
const QString & getModelString() const
Get model string.
QString getSimulatorNameAndVersion() const
Version and simulator details info.
Simple hardcoded info about the corresponding simulator.
Definition: simulatorinfo.h:41
Simulator getSimulator() const
Simulator.
Direct threadsafe in memory access to own aircraft.
Direct thread safe in memory access to remote aircraft.
FlightType
Flight types.
Definition: enums.h:90
PilotRating
Pilot ratings.
Definition: enums.h:33
ClientQueryType
Client query types.
Definition: enums.h:72
@ EuroscopeSimData
Broadcast to announce we request SIMDATA packets.
Capabilities
Client capability flags *‍/.
Definition: enums.h:130
TextMessageGroups
Message groups.
Definition: fsdclient.h:66
constexpr int PROTOCOL_REVISION_VATSIM_AUTH
Protocol version.
Definition: fsdclient.h:52
constexpr int PROTOCOL_REVISION_VATSIM_VELOCITY
Protocol version.
Definition: fsdclient.h:53
constexpr int PROTOCOL_REVISION_CLASSIC
Protocol version.
Definition: fsdclient.h:50
MessageType
Message type.
Definition: messagebase.h:19
Free functions in swift::misc.
auto makeKeysRange(const T &container)
Returns a const CRange for iterating over the keys of a Qt associative container.
Definition: range.h:353
bool is09OnlyString(const QString &testString)
String with 0-9 only.
Definition: stringutils.h:173
StatusSeverity
Status severities.
Definition: statusmessage.h:35
auto makePairsRange(const T &container)
Returns a const CRange for iterating over the keys and values of a Qt associative container.
Definition: range.h:374
SWIFT_MISC_EXPORT QDateTime fromStringUtc(const QString &dateTimeString, const QString &format)
Same as QDateTime::fromString but QDateTime will be set to UTC.
void errorOccurred(QAbstractSocket::SocketError socketError)
bool isEmpty() const const
QDateTime currentDateTime()
QDateTime currentDateTimeUtc()
qint64 currentMSecsSinceEpoch()
QString toString(QStringView format) const const
QDateTime toUTC() const const
QHash< Key, T >::iterator insert(Key &&key, T &&value)
Key key(const T &value) const const
T value(const Key &key) const const
void readyRead()
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
QJsonObject object() const const
QByteArray toJson(QJsonDocument::JsonFormat format) const const
QJsonObject::iterator insert(QLatin1StringView key, const QJsonValue &value)
bool isEmpty() const const
QJsonValue value(QLatin1StringView key) const const
QString errorString() const const
bool isString() const const
bool toBool(bool defaultValue) const const
QJsonObject toObject() const const
QString toString() const const
QList< T >::const_reference at(qsizetype i) const const
QList< T >::iterator begin()
void clear()
QList< T >::iterator end()
T & first()
QList< T >::reference front()
bool isEmpty() const const
QList< T > mid(qsizetype pos, qsizetype length) const const
void push_back(QList< T >::parameter_type value)
void push_front(QList< T >::parameter_type value)
void removeLast()
qsizetype size() const const
bool isEmpty() const const
const char * valueToKey(quint64 value) const const
bool invokeMethod(QObject *context, Functor &&function, Args &&... arguments)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
int receivers(const char *signal) const const
QObject * sender() const const
void setObjectName(QAnyStringView name)
T dequeue()
QString & append(QChar ch)
QString arg(Args &&... args) const const
const QChar at(qsizetype position) const const
QString::const_iterator constBegin() const const
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
QChar * data()
bool isEmpty() const const
qsizetype length() const const
QString mid(qsizetype position, qsizetype n) &&
QString number(double n, char format, int precision)
void push_back(QChar ch)
void push_front(QChar ch)
QString & remove(QChar ch, Qt::CaseSensitivity cs)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
QString right(qsizetype n) &&
QString rightJustified(qsizetype width, QChar fill, bool truncate) const const
qsizetype size() const const
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
double toDouble(bool *ok) const const
int toInt(bool *ok, int base) const const
QByteArray toUtf8() const const
QString trimmed() const const
const QChar * unicode() const const
std::optional< QStringConverter::Encoding > encodingForName(QAnyStringView name)
QString join(QChar separator) const const
CaseInsensitive
QueuedConnection
QTextStream & endl(QTextStream &stream)
QTextStream & fixed(QTextStream &stream)
char * toString(QSizePolicy sp)
std::vector< std::string > split(const std::string &str, size_t maxSplitCount=0, const std::string &delimiter=" ")
Split string by delimiter and maxSplitCount times.
Definition: qtfreeutils.h:55
QFuture< QtFuture::ArgsType< Signal >> connect(Sender *sender, Signal signal)
void timeout()
#define SWIFT_AUDIT_X(COND, WHERE, WHAT)
A weaker kind of verify.
Definition: verify.h:38
#define SWIFT_VERIFY_X(COND, WHERE, WHAT)
A weaker kind of assert.
Definition: verify.h:26