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