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