6 #include <QHostAddress>
8 #include <QNetworkReply>
53 using namespace swift::config;
54 using namespace swift::core::vatsim;
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;
63 namespace swift::core::fsd
65 QString convertToUnicodeEscaped(
const QString &str)
68 for (
const auto &ch : str)
70 const ushort code = ch.unicode();
71 if (code < 0x80) { escaped += ch; }
72 else { escaped +=
"\\u" % QString::number(code, 16).rightJustified(4,
'0'); }
77 const QStringList &CFSDClient::getLogCategories()
79 static const QStringList cats = [] {
80 QStringList cl = CContinuousWorker::getLogCategories();
81 cl.push_back(CLogCategories::network());
82 cl.push_back(CLogCategories::fsd());
96 initializeMessageTypes();
97 connectSocketSignals();
99 m_positionUpdateTimer.setObjectName(this->objectName().append(
":m_positionUpdateTimer"));
100 connect(&m_positionUpdateTimer, &QTimer::timeout,
this, &CFSDClient::sendPilotDataUpdate);
102 m_interimPositionUpdateTimer.setObjectName(this->objectName().append(
":m_interimPositionUpdateTimer"));
103 connect(&m_interimPositionUpdateTimer, &QTimer::timeout,
this, &CFSDClient::sendInterimPilotDataUpdate);
105 m_visualPositionUpdateTimer.setObjectName(this->objectName().append(
":m_visualPositionUpdateTimer"));
106 connect(&m_visualPositionUpdateTimer, &QTimer::timeout,
this, [
this] { sendVisualPilotDataUpdate(); });
108 m_scheduledConfigUpdate.setObjectName(this->objectName().append(
":m_scheduledConfigUpdate"));
109 connect(&m_scheduledConfigUpdate, &QTimer::timeout,
this, &CFSDClient::sendIncrementalAircraftConfig);
111 m_fsdSendMessageTimer.setObjectName(this->objectName().append(
":m_fsdSendMessageTimer"));
112 connect(&m_fsdSendMessageTimer, &QTimer::timeout,
this, [
this]() { this->sendQueuedMessage(); });
114 fsdMessageSettingsChanged();
124 void CFSDClient::connectSocketSignals()
126 connect(m_socket.get(), &QTcpSocket::readyRead,
this, &CFSDClient::readDataFromSocket, Qt::QueuedConnection);
127 connect(m_socket.get(), &QTcpSocket::connected,
this, &CFSDClient::handleSocketConnected);
128 connect(m_socket.get(), &QTcpSocket::errorOccurred,
this, &CFSDClient::printSocketError, Qt::QueuedConnection);
129 connect(m_socket.get(), &QTcpSocket::errorOccurred,
this, &CFSDClient::handleSocketError, Qt::QueuedConnection);
132 #ifdef SWIFT_VATSIM_SUPPORT
133 void CFSDClient::setClientIdAndKey(quint16
id,
const QByteArray &key)
135 QWriteLocker l(&m_lockUserClientBuffered);
136 m_clientAuth = vatsim_auth_create(
id, qPrintable(key));
137 m_serverAuth = vatsim_auth_create(
id, qPrintable(key));
144 "Can't change server details while still connected");
147 auto codec = QStringDecoder::encodingForName(codecName);
148 if (!codec.has_value()) { codec = QStringConverter::Utf8; }
149 const int protocolRev = (server.
getServerType() == CServer::FSDServerVatsim) ?
153 QWriteLocker l(&m_lockUserClientBuffered);
155 m_protocolRevision = protocolRev;
156 m_encoder = QStringEncoder(codec.value_or(QStringConverter::Utf8));
157 m_decoder = QStringDecoder(codec.value_or(QStringConverter::Utf8));
163 "Can't change callsign while still connected");
166 QWriteLocker l(&m_lockUserClientBuffered);
167 m_ownCallsign = callsign;
173 "Can't change ICAO codes while still connected");
176 QWriteLocker l(&m_lockUserClientBuffered);
190 bool sendModelString)
192 QWriteLocker l(&m_lockUserClientBuffered);
193 m_ownLivery = livery;
194 m_ownModelString = modelString;
195 m_sendLiveryString = sendLiveryString;
196 m_sendModelString = sendModelString;
203 QWriteLocker l(&m_lockUserClientBuffered);
206 case CSimulatorInfo::FSX: m_simType = SimType::MSFSX;
break;
207 case CSimulatorInfo::P3D: m_simType = SimType::P3Dv4;
break;
208 case CSimulatorInfo::FS9: m_simType = SimType::MSFS2004;
break;
209 case CSimulatorInfo::FG: m_simType = SimType::FlightGear;
break;
210 case CSimulatorInfo::XPLANE: m_simType = SimType::XPLANE11;
break;
211 case CSimulatorInfo::MSFS: m_simType = SimType::MSFS;
break;
212 case CSimulatorInfo::MSFS2024: m_simType = SimType::MSFS2024;
break;
213 default: m_simType = SimType::Unknown;
break;
220 QReadLocker l(&m_lockUserClientBuffered);
221 const QStringList v = { m_ownModelString,
234 QMetaObject::invokeMethod(
this, [=] {
240 if (m_socket->isOpen()) {
return; }
241 Q_ASSERT(!m_clientName.isEmpty());
242 Q_ASSERT((m_versionMajor + m_versionMinor) > 0);
243 Q_ASSERT(m_capabilities != Capabilities::None);
248 m_filterPasswordFromLogin =
true;
250 m_loginSince = QDateTime::currentMSecsSinceEpoch();
251 const qint64 timerMs = qRound(PendingConnectionTimeoutMs * 1.25);
253 const QPointer<CFSDClient> myself(
this);
256 this->pendingTimeoutCheck();
259 this->updateConnectionStatus(CConnectionStatus::Connecting);
261 initiateConnection();
268 QMetaObject::invokeMethod(
this, [=] {
274 this->stopPositionTimers();
275 this->updateConnectionStatus(CConnectionStatus::Disconnecting);
279 if (m_socket->isOpen())
281 if (mode.
isPilot()) { this->sendDeletePilot(); }
282 else if (mode.
isObserver()) { this->sendDeleteAtc(); }
286 this->updateConnectionStatus(CConnectionStatus::Disconnected);
290 void CFSDClient::sendLogin(
const QString &token)
296 const QString callsign = m_ownCallsign.
asString();
301 const AddPilot pilotLogin(callsign, cid, password, m_pilotRating, m_protocolRevision, m_simType, name);
302 sendQueuedMessage(pilotLogin);
304 << callsign << cid << toQString(m_pilotRating) << m_protocolRevision << toQString(m_simType) << name;
308 const AddAtc addAtc(callsign, name, cid, password, m_atcRating, m_protocolRevision);
309 sendQueuedMessage(addAtc);
311 << callsign << cid << toQString(m_atcRating) << m_protocolRevision << name;
316 this->sendClientQuery(ClientQueryType::EuroscopeSimData, {}, {});
320 void CFSDClient::sendDeletePilot()
324 sendQueuedMessage(deletePilot);
327 void CFSDClient::sendDeleteAtc()
330 const DeleteAtc deleteAtc(getOwnCallsignAsString(), cid);
331 sendQueuedMessage(deleteAtc);
334 void CFSDClient::sendPilotDataUpdate()
338 if (m_loginMode == CLoginMode::Observer)
340 sendAtcDataUpdate(myAircraft.latitude().value(CAngleUnit::deg()),
341 myAircraft.longitude().value(CAngleUnit::deg()));
345 if (this->isVisualPositionSendingEnabledForServer())
350 sendVisualPilotDataUpdate(
true);
354 PilotDataUpdate pilotDataUpdate(
355 myAircraft.getTransponderMode(), getOwnCallsignAsString(),
356 static_cast<qint16
>(myAircraft.getTransponderCode()), r, myAircraft.latitude().value(CAngleUnit::deg()),
357 myAircraft.longitude().value(CAngleUnit::deg()),
358 myAircraft.getAltitude().valueInteger(CLengthUnit::ft()),
359 myAircraft.getPressureAltitude().valueInteger(CLengthUnit::ft()),
360 myAircraft.getGroundSpeed().valueInteger(CSpeedUnit::kts()),
361 myAircraft.getPitch().value(CAngleUnit::deg()), myAircraft.getBank().value(CAngleUnit::deg()),
362 myAircraft.getHeading().normalizedTo360Degrees().value(CAngleUnit::deg()),
363 myAircraft.getParts().isOnGround());
364 sendQueuedMessage(pilotDataUpdate);
368 void CFSDClient::sendInterimPilotDataUpdate()
372 InterimPilotDataUpdate interimPilotDataUpdate(
373 getOwnCallsignAsString(), QString(), myAircraft.latitude().value(CAngleUnit::deg()),
374 myAircraft.longitude().value(CAngleUnit::deg()), myAircraft.getAltitude().valueInteger(CLengthUnit::ft()),
375 myAircraft.getGroundSpeed().valueInteger(CSpeedUnit::kts()), myAircraft.getPitch().value(CAngleUnit::deg()),
376 myAircraft.getBank().value(CAngleUnit::deg()),
377 myAircraft.getHeading().normalizedTo360Degrees().value(CAngleUnit::deg()),
378 myAircraft.getParts().isOnGround());
380 for (
const auto &receiver : std::as_const(m_interimPositionReceivers))
382 interimPilotDataUpdate.setReceiver(receiver.asString());
383 sendQueuedMessage(interimPilotDataUpdate);
388 void CFSDClient::sendVisualPilotDataUpdate(
bool slowUpdate)
391 if (m_loginMode == CLoginMode::Observer || !isVisualPositionSendingEnabledForServer()) {
return; }
396 static constexpr
double minVelocity = 0.00005;
397 if (std::abs(myAircraft.getVelocity().getVelocityX(CSpeedUnit::m_s())) < minVelocity &&
398 std::abs(myAircraft.getVelocity().getVelocityY(CSpeedUnit::m_s())) < minVelocity &&
399 std::abs(myAircraft.getVelocity().getVelocityZ(CSpeedUnit::m_s())) < minVelocity &&
400 std::abs(myAircraft.getVelocity().getPitchVelocity(CAngleUnit::rad(), CTimeUnit::s())) < minVelocity &&
401 std::abs(myAircraft.getVelocity().getRollVelocity(CAngleUnit::rad(), CTimeUnit::s())) < minVelocity &&
402 std::abs(myAircraft.getVelocity().getHeadingVelocity(CAngleUnit::rad(), CTimeUnit::s())) < minVelocity)
404 if (m_stoppedSendingVisualPositions) {
return; }
405 m_stoppedSendingVisualPositions =
true;
406 m_visualPositionUpdateSentCount = 0;
408 else { m_stoppedSendingVisualPositions =
false; }
410 if (!m_serverWantsVisualPositions) {
return; }
412 VisualPilotDataUpdate visualPilotDataUpdate(
413 getOwnCallsignAsString(), myAircraft.latitude().value(CAngleUnit::deg()),
414 myAircraft.longitude().value(CAngleUnit::deg()), myAircraft.getAltitude().value(CLengthUnit::ft()),
415 myAircraft.getAltitude().value(CLengthUnit::ft()) -
416 myAircraft.getGroundElevation().value(CLengthUnit::ft()),
417 myAircraft.getPitch().value(CAngleUnit::deg()), myAircraft.getBank().value(CAngleUnit::deg()),
418 myAircraft.getHeading().normalizedTo360Degrees().value(CAngleUnit::deg()),
419 myAircraft.getVelocity().getVelocityX(CSpeedUnit::m_s()),
420 myAircraft.getVelocity().getVelocityY(CSpeedUnit::m_s()),
421 myAircraft.getVelocity().getVelocityZ(CSpeedUnit::m_s()),
422 myAircraft.getVelocity().getPitchVelocity(CAngleUnit::rad(), CTimeUnit::s()),
423 myAircraft.getVelocity().getRollVelocity(CAngleUnit::rad(), CTimeUnit::s()),
424 myAircraft.getVelocity().getHeadingVelocity(CAngleUnit::rad(), CTimeUnit::s()));
426 if (m_stoppedSendingVisualPositions) { sendQueuedMessage(visualPilotDataUpdate.toStopped()); }
427 else if (m_visualPositionUpdateSentCount++ % 25 == 0) { sendQueuedMessage(visualPilotDataUpdate.toPeriodic()); }
428 else { sendQueuedMessage(visualPilotDataUpdate); }
431 void CFSDClient::sendAtcDataUpdate(
double latitude,
double longitude)
433 const AtcDataUpdate atcDataUpdate(getOwnCallsignAsString(), 199998, CFacilityType::OBS, 300,
434 AtcRating::Observer, latitude, longitude, 0);
435 sendQueuedMessage(atcDataUpdate);
438 void CFSDClient::sendPing(
const QString &receiver)
440 const qint64 msecSinceEpoch = QDateTime::currentMSecsSinceEpoch();
441 const QString timeString = QString::number(msecSinceEpoch);
443 const Ping ping(getOwnCallsignAsString(), receiver, timeString);
444 sendQueuedMessage(ping);
447 increaseStatisticsValue(QStringLiteral(
"sendPing"));
450 void CFSDClient::sendClientQueryIsValidAtc(
const CCallsign &callsign)
452 sendClientQuery(ClientQueryType::IsValidATC, {}, { callsign.
asString() });
455 void CFSDClient::sendClientQueryCapabilities(
const CCallsign &callsign)
457 sendClientQuery(ClientQueryType::Capabilities, callsign);
462 sendClientQuery(ClientQueryType::Com1Freq, callsign);
467 sendClientQuery(ClientQueryType::RealName, callsign);
472 sendClientQuery(ClientQueryType::Server, callsign);
477 sendClientQuery(ClientQueryType::ATIS, callsign);
482 sendClientQuery(ClientQueryType::FP, {}, { callsign.
toQString() });
487 QString data = QJsonDocument(JsonPackets::aircraftConfigRequest()).toJson(QJsonDocument::Compact);
488 data = convertToUnicodeEscaped(data);
489 sendClientQuery(ClientQueryType::AircraftConfig, callsign, { data });
492 void CFSDClient::sendClientQuery(
ClientQueryType queryType,
const CCallsign &receiver,
const QStringList &queryData)
494 if (queryType == ClientQueryType::Unknown) {
return; }
497 QMetaObject::invokeMethod(
this, [=] {
504 if (queryType == ClientQueryType::IsValidATC)
506 const ClientQuery clientQuery(getOwnCallsignAsString(),
"SERVER", ClientQueryType::IsValidATC, queryData);
507 sendQueuedMessage(clientQuery);
509 else if (queryType == ClientQueryType::Capabilities)
511 const ClientQuery clientQuery(getOwnCallsignAsString(), receiverCallsign, ClientQueryType::Capabilities);
512 sendQueuedMessage(clientQuery);
514 else if (queryType == ClientQueryType::Com1Freq)
516 const ClientQuery clientQuery(getOwnCallsignAsString(), receiverCallsign, ClientQueryType::Com1Freq);
517 sendQueuedMessage(clientQuery);
519 else if (queryType == ClientQueryType::RealName)
521 const ClientQuery clientQuery(getOwnCallsignAsString(), receiverCallsign, ClientQueryType::RealName);
522 sendQueuedMessage(clientQuery);
524 else if (queryType == ClientQueryType::Server)
526 ClientQuery clientQuery(getOwnCallsignAsString(), receiverCallsign, ClientQueryType::Server);
527 sendQueuedMessage(clientQuery);
529 else if (queryType == ClientQueryType::ATIS)
531 const ClientQuery clientQuery(getOwnCallsignAsString(), receiverCallsign, ClientQueryType::ATIS);
532 sendQueuedMessage(clientQuery);
533 if (m_serverType != ServerType::Vatsim) { m_pendingAtisQueries.insert(receiver, {}); }
535 else if (queryType == ClientQueryType::PublicIP)
537 const ClientQuery clientQuery(getOwnCallsignAsString(), receiverCallsign, ClientQueryType::PublicIP);
538 sendQueuedMessage(clientQuery);
540 else if (queryType == ClientQueryType::INF)
542 const ClientQuery clientQuery(getOwnCallsignAsString(), receiverCallsign, ClientQueryType::INF);
543 sendQueuedMessage(clientQuery);
545 else if (queryType == ClientQueryType::FP)
547 if (queryData.isEmpty()) {
return; }
548 const ClientQuery clientQuery(getOwnCallsignAsString(),
"SERVER", ClientQueryType::FP, queryData);
549 sendQueuedMessage(clientQuery);
551 else if (queryType == ClientQueryType::AircraftConfig)
553 if (queryData.isEmpty()) {
return; }
554 const ClientQuery clientQuery(getOwnCallsignAsString(), receiverCallsign, ClientQueryType::AircraftConfig,
556 sendQueuedMessage(clientQuery);
558 else if (queryType == ClientQueryType::EuroscopeSimData)
560 const ClientQuery clientQuery(getOwnCallsignAsString(),
"@94835", ClientQueryType::EuroscopeSimData,
562 sendQueuedMessage(clientQuery);
565 increaseStatisticsValue(QStringLiteral(
"sendClientQuery"), toQString(queryType));
570 if (messages.
isEmpty()) {
return; }
573 QMetaObject::invokeMethod(
this, [=] {
580 const QString ownCallsign = getOwnCallsignAsString();
582 for (
const auto &message : privateMessages)
584 if (message.getRecipientCallsign().isEmpty()) {
continue; }
585 const TextMessage textMessage(ownCallsign, message.getRecipientCallsign().getFsdCallsignString(),
586 message.getMessage());
587 sendQueuedMessage(textMessage);
588 increaseStatisticsValue(QStringLiteral(
"sendTextMessages.PM"));
593 QVector<int> frequencies;
594 for (
const auto &message : radioMessages)
599 message.getFrequency() :
607 frequencies.push_back(freqkHz);
609 increaseStatisticsValue(QStringLiteral(
"sendTextMessages.FREQ"));
616 if (message.
isEmpty()) {
return; }
622 if (message.isEmpty()) {
return; }
625 QMetaObject::invokeMethod(
this, [=] {
632 if (receiverGroup == TextMessageGroups::AllClients) { receiver =
'*'; }
633 else if (receiverGroup == TextMessageGroups::AllAtcClients) { receiver = QStringLiteral(
"*A"); }
634 else if (receiverGroup == TextMessageGroups::AllPilotClients) { receiver = QStringLiteral(
"*P"); }
635 else if (receiverGroup == TextMessageGroups::AllSups) { receiver = QStringLiteral(
"*S"); }
637 const TextMessage textMessage(getOwnCallsignAsString(), receiver, message);
638 sendQueuedMessage(textMessage);
639 if (receiver == QStringLiteral(
"*S"))
641 const CCallsign sender(getOwnCallsignAsString());
647 increaseStatisticsValue(QStringLiteral(
"sendTextMessages"));
658 QStringList receivers;
659 for (
const int &frequency : frequencieskHz)
661 receivers.push_back(QStringLiteral(
"@%1").arg(frequency - 100000));
664 const TextMessage radioMessage(getOwnCallsignAsString(), receivers.join(
'&'), message);
665 sendQueuedMessage(radioMessage);
666 increaseStatisticsValue(QStringLiteral(
"sendTextMessages"));
673 QMetaObject::invokeMethod(
this, [=] {
682 QString route = flightPlan.
getRoute();
701 Q_ASSERT_X(!act.isEmpty(), Q_FUNC_INFO,
"Aircraft type must not be empty");
705 const FlightPlan fp(getOwnCallsignAsString(),
"SERVER", flightType, act,
711 timePartsEnroute[CTime::Minutes], timePartsFuel[CTime::Hours],
715 sendQueuedMessage(fp);
716 increaseStatisticsValue(QStringLiteral(
"sendFlightPlan"));
723 QMetaObject::invokeMethod(
this, [=] {
730 sendQueuedMessage(planeInfoRequest);
731 increaseStatisticsValue(QStringLiteral(
"sendPlaneInfoRequest"));
738 QMetaObject::invokeMethod(
this, [=] {
745 SWIFT_VERIFY_X(connected, Q_FUNC_INFO,
"Can't send to server when disconnected");
746 if (!connected) {
return; }
749 const QString modelString = this->getConfiguredModelString(myAircraft);
753 sendQueuedMessage(planeInfoRequestFsinn);
754 increaseStatisticsValue(QStringLiteral(
"sendPlaneInfoRequestFsinn"));
757 void CFSDClient::sendPlaneInformation(
const QString &receiver,
const QString &aircraft,
const QString &airline,
758 const QString &livery)
760 const PlaneInformation planeInformation(getOwnCallsignAsString(), receiver, aircraft, airline, livery);
761 sendQueuedMessage(planeInformation);
762 increaseStatisticsValue(QStringLiteral(
"sendPlaneInformation"));
765 void CFSDClient::sendPlaneInformationFsinn(
const CCallsign &callsign)
769 const QString modelString = this->getConfiguredModelString(myAircraft);
770 const PlaneInformationFsinn planeInformationFsinn(
771 getOwnCallsignAsString(), callsign.
toQString(), myAircraft.getAirlineIcaoCodeDesignator(),
772 myAircraft.getAircraftIcaoCodeDesignator(), myAircraft.getAircraftIcaoCombinedType(), modelString);
773 sendQueuedMessage(planeInformationFsinn);
774 increaseStatisticsValue(QStringLiteral(
"sendPlaneInformationFsinn"));
777 void CFSDClient::sendAircraftConfiguration(
const QString &receiver,
const QString &aircraftConfigJson)
779 if (aircraftConfigJson.size() == 0) {
return; }
780 const ClientQuery clientQuery(getOwnCallsignAsString(), receiver, ClientQueryType::AircraftConfig,
781 { aircraftConfigJson });
782 sendQueuedMessage(clientQuery);
785 void CFSDClient::sendMessageString(
const QString &message)
787 if (message.isEmpty()) {
return; }
788 const QByteArray bufferEncoded = m_encoder(message);
789 if (m_printToConsole) { qDebug() <<
"FSD Sent=>" << bufferEncoded; }
790 if (!m_unitTestMode) { m_socket->write(bufferEncoded); }
793 emitRawFsdMessage(message.trimmed(),
true);
796 void CFSDClient::sendQueuedMessage()
798 if (m_queuedFsdMessages.isEmpty()) {
return; }
799 const qsizetype s = m_queuedFsdMessages.size();
800 this->sendMessageString(m_queuedFsdMessages.dequeue());
803 if (s > 5) { this->sendMessageString(m_queuedFsdMessages.dequeue()); }
804 if (s > 10) { this->sendMessageString(m_queuedFsdMessages.dequeue()); }
805 if (s > 20) { this->sendMessageString(m_queuedFsdMessages.dequeue()); }
806 if (s > 30) { this->sendMessageString(m_queuedFsdMessages.dequeue()); }
812 const StatusSeverity severity = s > 75 ? SeverityWarning : SeverityInfo;
813 CLogMessage(
this).
log(severity, u
"Too many queued messages (%1), bulk send!") << s;
815 if (s > 75) { sendNo = 20; }
816 if (s > 100) { sendNo = 30; }
818 for (
int i = 0; i < sendNo; i++) { this->sendMessageString(m_queuedFsdMessages.dequeue()); }
822 void CFSDClient::sendFsdMessage(
const QString &message)
825 parseMessage(message);
828 QString CFSDClient::getConfiguredModelString(
const CSimulatedAircraft &myAircraft)
const
830 if (!m_sendModelString) {
return noModelString(); }
831 QReadLocker l(&m_lockUserClientBuffered);
832 const QString ms = m_ownModelString.isEmpty() ? myAircraft.
getModelString() : m_ownModelString;
833 return ms.isEmpty() ? noModelString() : ms;
836 QString CFSDClient::getConfiguredLiveryString(
const CSimulatedAircraft &myAircraft)
const
838 if (!m_sendLiveryString) {
return {}; }
839 QReadLocker l(&m_lockUserClientBuffered);
844 void CFSDClient::sendAuthChallenge(
const QString &challenge)
846 const AuthChallenge pduAuthChallenge(getOwnCallsignAsString(),
"SERVER", challenge);
847 sendDirectMessage(pduAuthChallenge);
848 increaseStatisticsValue(QStringLiteral(
"sendAuthChallenge"));
851 void CFSDClient::sendAuthResponse(
const QString &response)
853 const AuthResponse pduAuthResponse(getOwnCallsignAsString(),
"SERVER", response);
854 sendDirectMessage(pduAuthResponse);
855 increaseStatisticsValue(QStringLiteral(
"sendAuthResponse"));
858 void CFSDClient::sendPong(
const QString &receiver,
const QString ×tamp)
860 const Pong pong(getOwnCallsignAsString(), receiver, timestamp);
861 sendQueuedMessage(pong);
862 increaseStatisticsValue(QStringLiteral(
"sendPong"));
865 void CFSDClient::sendClientResponse(ClientQueryType queryType,
const QString &receiver)
867 if (queryType == ClientQueryType::Unknown) {
return; }
868 if (queryType == ClientQueryType::IsValidATC)
870 this->handleIllegalFsdState(
"Never use sendClientResponse with IsValidATC from the client");
874 increaseStatisticsValue(QStringLiteral(
"sendClientResponse"), toQString(queryType));
876 QStringList responseData;
877 const QString ownCallsign = getOwnCallsignAsString();
879 if (queryType == ClientQueryType::Capabilities)
881 responseData.clear();
882 if (m_capabilities & Capabilities::AtcInfo) responseData.push_back(toQString(Capabilities::AtcInfo) %
"=1");
883 if (m_capabilities & Capabilities::SecondaryPos)
884 responseData.push_back(toQString(Capabilities::SecondaryPos) %
"=1");
885 if (m_capabilities & Capabilities::AircraftInfo)
886 responseData.push_back(toQString(Capabilities::AircraftInfo) %
"=1");
887 if (m_capabilities & Capabilities::OngoingCoord)
888 responseData.push_back(toQString(Capabilities::OngoingCoord) %
"=1");
889 if (m_capabilities & Capabilities::InterminPos)
890 responseData.push_back(toQString(Capabilities::InterminPos) %
"=1");
891 if (m_capabilities & Capabilities::FastPos) responseData.push_back(toQString(Capabilities::FastPos) %
"=1");
892 if (m_capabilities & Capabilities::VisPos) responseData.push_back(toQString(Capabilities::VisPos) %
"=1");
893 if (m_capabilities & Capabilities::Stealth) responseData.push_back(toQString(Capabilities::Stealth) %
"=1");
894 if (m_capabilities & Capabilities::AircraftConfig)
895 responseData.push_back(toQString(Capabilities::AircraftConfig) %
"=1");
896 if (m_capabilities & Capabilities::IcaoEquipment)
897 responseData.push_back(toQString(Capabilities::IcaoEquipment) %
"=1");
898 const ClientResponse clientResponse(ownCallsign, receiver, ClientQueryType::Capabilities, responseData);
899 sendQueuedMessage(clientResponse);
901 else if (queryType == ClientQueryType::Com1Freq)
903 const QString com1Frequency = QString::number(
904 getOwnAircraft().getCom1System().getFrequencyActive().value(CFrequencyUnit::MHz()),
'f', 3);
905 responseData.push_back(com1Frequency);
906 const ClientResponse pduClientResponse(ownCallsign, receiver, ClientQueryType::Com1Freq, responseData);
907 sendQueuedMessage(pduClientResponse);
909 else if (queryType == ClientQueryType::RealName)
915 responseData.push_back({});
917 if (m_loginMode.
isObserver()) { responseData.push_back(toQString(m_atcRating)); }
918 else { responseData.push_back(toQString(m_pilotRating)); }
920 const ClientResponse pduClientQueryResponse(ownCallsign, receiver, ClientQueryType::RealName, responseData);
921 sendQueuedMessage(pduClientQueryResponse);
923 else if (queryType == ClientQueryType::Server)
925 responseData.push_back(m_server.
getAddress());
926 const ClientResponse pduClientQueryResponse(ownCallsign, receiver, ClientQueryType::Server, responseData);
927 sendQueuedMessage(pduClientQueryResponse);
929 else if (queryType == ClientQueryType::ATIS)
931 this->handleIllegalFsdState(
932 QStringLiteral(
"Dont send '%1' as pilot client!").arg(toQString(ClientQueryType::ATIS)));
934 else if (queryType == ClientQueryType::PublicIP)
936 this->handleIllegalFsdState(
937 QStringLiteral(
"Dont send '%1' as pilot client!").arg(toQString(ClientQueryType::PublicIP)));
939 else if (queryType == ClientQueryType::INF)
945 const double latitude = situation.
latitude().
value(CAngleUnit::deg());
946 const double longitude = situation.
longitude().
value(CAngleUnit::deg());
949 std::array<char, 50> sysuid = {};
950 #ifdef SWIFT_VATSIM_SUPPORT
951 vatsim_get_system_unique_id(sysuid.data());
954 const QString userInfo = QStringLiteral(
"CID=") % cid %
" " % m_clientName %
" IP=" %
955 m_socket->localAddress().toString() %
" SYS_UID=" % sysuid.data() %
" FSVER=" %
956 m_hostApplication %
" LT=" % QString::number(latitude) %
" LO=" %
957 QString::number(longitude) %
" AL=" % QString::number(altitude) %
" " % realName;
959 const TextMessage textMessage(ownCallsign, receiver, userInfo);
960 sendQueuedMessage(textMessage);
962 else if (queryType == ClientQueryType::FP)
964 this->handleIllegalFsdState(
965 QStringLiteral(
"Dont send '%1' as pilot client!").arg(toQString(ClientQueryType::FP)));
967 else if (queryType == ClientQueryType::AircraftConfig)
969 this->handleIllegalFsdState(
970 QStringLiteral(
"Dont send '%1' as pilot client!").arg(toQString(ClientQueryType::AircraftConfig)));
974 #ifdef SWIFT_VATSIM_SUPPORT
975 void CFSDClient::sendClientIdentification(
const QString &fsdChallenge)
977 std::array<char, 50> sysuid = {};
978 vatsim_get_system_unique_id(sysuid.data());
980 const ClientIdentification clientIdentification(
981 getOwnCallsignAsString(), vatsim_auth_get_client_id(m_clientAuth), m_clientName, m_versionMajor,
982 m_versionMinor, cid, sysuid.data(), fsdChallenge);
983 this->sendQueuedMessage(clientIdentification);
985 if (
getServer().getEcosystem().isSystem(CEcosystem::VATSIM))
988 { this, [this](const QString &token) {
989 this->sendLogin(token);
990 this->updateConnectionStatus(CConnectionStatus::Connected);
996 this->updateConnectionStatus(CConnectionStatus::Connected);
998 increaseStatisticsValue(QStringLiteral(
"sendClientIdentification"));
1002 void CFSDClient::getVatsimAuthToken(
const QString &cid,
const QString &password,
1005 Q_ASSERT_X(
sApp, Q_FUNC_INFO,
"Need app");
1007 nwRequest.setHeader(QNetworkRequest::ContentTypeHeader,
"application/json");
1008 const QJsonObject jsonRequest { {
"cid", cid }, {
"password", password } };
1011 {
this, [=](QNetworkReply *nwReply) {
1012 const QByteArray data = nwReply->readAll();
1013 const QJsonObject json = QJsonDocument::fromJson(data).object();
1015 if (json.value(
"success").toBool()) { callback(json.value(
"token").toString()); }
1018 const QString error = json.value(
"error_msg").isString() ?
1019 json.value(
"error_msg").toString() :
1020 nwReply->errorString();
1022 disconnectFromServer();
1024 nwReply->deleteLater();
1028 void CFSDClient::sendIncrementalAircraftConfig()
1030 if (!m_unitTestMode && (!this->isConnected() || !this->getSetupForServer().sendAircraftParts())) {
return; }
1034 if (m_sentAircraftConfig == currentParts) {
return; }
1036 if (!m_tokenBucket.tryConsume()) {
return; }
1038 const QJsonObject previousConfig = m_sentAircraftConfig.toJson();
1039 const QJsonObject currentConfig = currentParts.toJson();
1040 const QJsonObject incrementalConfig = getIncrementalObject(previousConfig, currentConfig);
1042 const QString dataStr = convertToUnicodeEscaped(
1043 QJsonDocument(QJsonObject { {
"config", incrementalConfig } }).toJson(QJsonDocument::Compact));
1045 sendAircraftConfiguration(
"@94836", dataStr);
1046 m_sentAircraftConfig = currentParts;
1049 void CFSDClient::initializeMessageTypes()
1051 m_messageTypeMapping[
"#AA"] = MessageType::AddAtc;
1052 m_messageTypeMapping[
"#AP"] = MessageType::AddPilot;
1053 m_messageTypeMapping[
"%"] = MessageType::AtcDataUpdate;
1054 m_messageTypeMapping[
"$ZC"] = MessageType::AuthChallenge;
1055 m_messageTypeMapping[
"$ZR"] = MessageType::AuthResponse;
1056 m_messageTypeMapping[
"$ID"] = MessageType::ClientIdentification;
1057 m_messageTypeMapping[
"$CQ"] = MessageType::ClientQuery;
1058 m_messageTypeMapping[
"$CR"] = MessageType::ClientResponse;
1059 m_messageTypeMapping[
"#DA"] = MessageType::DeleteATC;
1060 m_messageTypeMapping[
"#DP"] = MessageType::DeletePilot;
1061 m_messageTypeMapping[
"$FP"] = MessageType::FlightPlan;
1062 m_messageTypeMapping[
"#PC"] = MessageType::ProController;
1063 m_messageTypeMapping[
"$DI"] = MessageType::FsdIdentification;
1064 m_messageTypeMapping[
"$!!"] = MessageType::KillRequest;
1065 m_messageTypeMapping[
"@"] = MessageType::PilotDataUpdate;
1066 m_messageTypeMapping[
"^"] = MessageType::VisualPilotDataUpdate;
1067 m_messageTypeMapping[
"#SL"] = MessageType::VisualPilotDataPeriodic;
1068 m_messageTypeMapping[
"#ST"] = MessageType::VisualPilotDataStopped;
1069 m_messageTypeMapping[
"$SF"] = MessageType::VisualPilotDataToggle;
1070 m_messageTypeMapping[
"$PI"] = MessageType::Ping;
1071 m_messageTypeMapping[
"$PO"] = MessageType::Pong;
1072 m_messageTypeMapping[
"$ER"] = MessageType::ServerError;
1073 m_messageTypeMapping[
"#DL"] = MessageType::ServerHeartbeat;
1074 m_messageTypeMapping[
"#TM"] = MessageType::TextMessage;
1075 m_messageTypeMapping[
"#SB"] = MessageType::PilotClientCom;
1076 m_messageTypeMapping[
"$XX"] = MessageType::Rehost;
1077 m_messageTypeMapping[
"#MU"] = MessageType::Mute;
1080 m_messageTypeMapping[
"SIMDATA"] = MessageType::EuroscopeSimData;
1084 m_messageTypeMapping[
"!R"] = MessageType::RegistrationInfo;
1085 m_messageTypeMapping[
"-MD"] = MessageType::RevBClientParts;
1086 m_messageTypeMapping[
"-PD"] = MessageType::RevBPilotDescription;
1093 void CFSDClient::handleAtcDataUpdate(
const QStringList &tokens)
1095 const AtcDataUpdate atcDataUpdate = AtcDataUpdate::fromTokens(tokens);
1096 const QString senderCs = atcDataUpdate.sender();
1097 const CCallsign cs(senderCs, CCallsign::Atc);
1100 if (atcDataUpdate.m_facility == CFacilityType::Unknown && !cs.isObserverCallsign())
1104 if (atcDataUpdate.m_facility == CFacilityType::OBS && !cs.hasSuffix()) {
return; }
1106 CFrequency freq(atcDataUpdate.m_frequencykHz, CFrequencyUnit::kHz());
1107 freq.switchUnit(CFrequencyUnit::MHz());
1113 const CLength networkRange(atcDataUpdate.m_visibleRange, CLengthUnit::NM());
1114 const CLength range = fixAtcRange(networkRange, cs);
1115 const CCoordinateGeodetic position(atcDataUpdate.m_latitude, atcDataUpdate.m_longitude, 0);
1117 emit this->atcDataUpdateReceived(cs, freq, position, range);
1119 m_atcStations.replaceOrAddObjectByCallsign({ cs, {}, freq, position, range });
1122 #ifdef SWIFT_VATSIM_SUPPORT
1123 void CFSDClient::handleAuthChallenge(
const QStringList &tokens)
1125 const AuthChallenge authChallenge = AuthChallenge::fromTokens(tokens);
1127 vatsim_auth_generate_response(m_clientAuth, qPrintable(authChallenge.m_challengeKey), response);
1128 sendAuthResponse(QString(response));
1131 vatsim_auth_generate_challenge(m_serverAuth, challenge);
1132 m_lastServerAuthChallenge = QString(challenge);
1133 sendAuthChallenge(m_lastServerAuthChallenge);
1137 #ifdef SWIFT_VATSIM_SUPPORT
1138 void CFSDClient::handleAuthResponse(
const QStringList &tokens)
1140 const AuthResponse authResponse = AuthResponse::fromTokens(tokens);
1142 char expectedResponse[33];
1143 vatsim_auth_generate_response(m_serverAuth, qPrintable(m_lastServerAuthChallenge), expectedResponse);
1144 if (authResponse.m_response != QString(expectedResponse))
1146 CLogMessage().
error(u
"The server you are connected to is not a VATSIM server. Disconnecting!");
1147 disconnectFromServer();
1152 void CFSDClient::handleDeleteATC(
const QStringList &tokens)
1154 const DeleteAtc deleteAtc = DeleteAtc::fromTokens(tokens);
1155 emit deleteAtcReceived(deleteAtc.m_cid);
1157 m_atcStations.removeByCallsign(deleteAtc.m_cid);
1160 void CFSDClient::handleDeletePilot(
const QStringList &tokens)
1162 const DeletePilot deletePilot = DeletePilot::fromTokens(tokens);
1163 const CCallsign cs(deletePilot.sender(), CCallsign::Aircraft);
1165 emit deletePilotReceived(deletePilot.m_cid);
1168 void CFSDClient::handleTextMessage(
const QStringList &tokens)
1170 const TextMessage textMessage = TextMessage::fromTokens(tokens);
1172 const CCallsign sender(textMessage.sender());
1173 const CCallsign receiver(textMessage.receiver());
1175 if (textMessage.m_type == TextMessage::PrivateMessage)
1181 if (m_server.getServerType() != CServer::FSDServerVatsim &&
1182 m_ownCallsign.asString() == textMessage.receiver() && m_pendingAtisQueries.contains(sender))
1184 maybeHandleAtisReply(sender, receiver, textMessage.m_message);
1188 CTextMessage tm(textMessage.m_message, sender, receiver);
1189 tm.setCurrentUtcTime();
1190 this->consolidateTextMessage(tm);
1192 else if (textMessage.m_type == TextMessage::RadioMessage)
1194 const CFrequency com1 = getOwnAircraft().getCom1System().getFrequencyActive();
1195 const CFrequency com2 = getOwnAircraft().getCom2System().getFrequencyActive();
1196 QList<CFrequency> frequencies;
1198 for (
int freqKhz : textMessage.m_frequencies)
1200 CFrequency f(freqKhz, CFrequencyUnit::kHz());
1201 CComSystem::roundToChannelSpacing(f, CComSystem::ChannelSpacing8_33KHz);
1202 if (f == com1 || f == com2) { frequencies.push_back(f); }
1204 if (frequencies.isEmpty()) {
return; }
1206 messages.setCurrentUtcTime();
1207 emit textMessagesReceived(messages);
1211 void CFSDClient::handlePilotDataUpdate(
const QStringList &tokens)
1213 const PilotDataUpdate dataUpdate = PilotDataUpdate::fromTokens(tokens);
1214 const CCallsign callsign(dataUpdate.sender(), CCallsign::Aircraft);
1217 callsign,
CCoordinateGeodetic(dataUpdate.m_latitude, dataUpdate.m_longitude, dataUpdate.m_altitudeTrue),
1218 CHeading(dataUpdate.m_heading, CHeading::True, CAngleUnit::deg()),
1219 CAngle(dataUpdate.m_pitch, CAngleUnit::deg()),
CAngle(dataUpdate.m_bank, CAngleUnit::deg()),
1220 CSpeed(dataUpdate.m_groundSpeed, CSpeedUnit::kts()));
1222 CAltitude::PressureAltitude, CLengthUnit::ft()));
1224 const COnGroundInfo og(dataUpdate.m_onGround ? COnGroundInfo::OnGround : COnGroundInfo::NotOnGround,
1225 COnGroundInfo::NotSetGroundDetails);
1230 const qint64 offsetTimeMs =
1237 if (CTransponder::isValidTransponderCode(dataUpdate.m_transponderCode))
1239 transponder =
CTransponder(dataUpdate.m_transponderCode, dataUpdate.m_transponderMode);
1243 if (CBuildConfig::isLocalDeveloperDebugBuild())
1246 << dataUpdate.m_transponderCode << callsign;
1250 transponder =
CTransponder(2000, CTransponder::StateStandby);
1252 emit pilotDataUpdateReceived(situation, transponder);
1255 void CFSDClient::handleEuroscopeSimData(
const QStringList &tokens)
1261 CHeading(data.m_heading, CAngleUnit::deg()),
1262 CAngle(-data.m_pitch, CAngleUnit::deg()),
CAngle(-data.m_bank, CAngleUnit::deg()),
1263 CSpeed(data.m_groundSpeed, CSpeedUnit::kts()));
1265 const COnGroundInfo og(data.m_onGround ? COnGroundInfo::OnGround : COnGroundInfo::NotOnGround,
1266 COnGroundInfo::NotSetGroundDetails);
1271 const qint64 offsetTimeMs =
1280 emit euroscopeSimDataUpdatedReceived(situation, parts, currentOffsetTime(data.sender()), data.m_model,
1284 void CFSDClient::handleVisualPilotDataUpdate(
const QStringList & ,
MessageType )
1287 VisualPilotDataUpdate dataUpdate;
1288 switch (messageType)
1290 case MessageType::VisualPilotDataUpdate: dataUpdate = VisualPilotDataUpdate::fromTokens(tokens);
break;
1291 case MessageType::VisualPilotDataPeriodic: dataUpdate = VisualPilotDataPeriodic::fromTokens(tokens).toUpdate();
break;
1292 case MessageType::VisualPilotDataStopped: dataUpdate = VisualPilotDataStopped::fromTokens(tokens).toUpdate();
break;
1293 default: qFatal(
"Precondition violated");
break;
1295 const CCallsign callsign(dataUpdate.sender(), CCallsign::Aircraft);
1299 CCoordinateGeodetic(dataUpdate.m_latitude, dataUpdate.m_longitude, dataUpdate.m_altitudeTrue),
1300 CHeading(dataUpdate.m_heading, CHeading::True, CAngleUnit::deg()),
1301 CAngle(dataUpdate.m_pitch, CAngleUnit::deg()),
1302 CAngle(dataUpdate.m_bank, CAngleUnit::deg()));
1314 emit visualPilotDataUpdateReceived(situation);
1318 void CFSDClient::handleVisualPilotDataToggle(
const QStringList &tokens)
1320 const VisualPilotDataToggle toggle = VisualPilotDataToggle::fromTokens(tokens);
1321 m_serverWantsVisualPositions = toggle.m_active;
1324 void CFSDClient::handlePing(
const QStringList &tokens)
1326 const Ping ping = Ping::fromTokens(tokens);
1327 sendPong(ping.sender(), ping.m_timestamp);
1330 void CFSDClient::handlePong(
const QStringList &tokens)
1332 const Pong pong = Pong::fromTokens(tokens);
1333 const qint64 msecSinceEpoch = QDateTime::currentMSecsSinceEpoch();
1334 const qint64 elapsedTime = msecSinceEpoch - pong.m_timestamp.toLongLong();
1335 emit pongReceived(pong.sender(),
static_cast<double>(elapsedTime));
1338 void CFSDClient::handleKillRequest(
const QStringList &tokens)
1340 KillRequest killRequest = KillRequest::fromTokens(tokens);
1341 emit killRequestReceived(killRequest.m_reason);
1342 disconnectFromServer();
1345 void CFSDClient::handleFlightPlan(
const QStringList &tokens)
1347 FlightPlan fp = FlightPlan::fromTokens(tokens);
1350 switch (fp.m_flightType)
1352 case FlightType::VFR: rules = CFlightPlan::VFR;
break;
1353 case FlightType::IFR: rules = CFlightPlan::IFR;
break;
1354 case FlightType::DVFR: rules = CFlightPlan::DVFR;
break;
1355 case FlightType::SVFR: rules = CFlightPlan::SVFR;
break;
1358 QString cruiseAltString = fp.m_cruiseAlt.trimmed();
1359 if (!cruiseAltString.isEmpty() &&
is09OnlyString(cruiseAltString))
1361 int ca = cruiseAltString.toInt();
1365 if (rules == CFlightPlan::IFR)
1367 if (ca >= 1000) { cruiseAltString = u
"FL" % QString::number(ca / 100); }
1368 else { cruiseAltString = u
"FL" % cruiseAltString; }
1372 if (ca >= 5000) { cruiseAltString = u
"FL" % QString::number(ca / 100); }
1373 else { cruiseAltString = cruiseAltString % u
"ft"; }
1377 cruiseAlt.
parseFromString(cruiseAltString, CPqString::SeparatorBestGuess);
1379 const QString depTimePlanned = QStringLiteral(
"0000").append(QString::number(fp.m_estimatedDepTime)).right(4);
1380 const QString depTimeActual = QStringLiteral(
"0000").append(QString::number(fp.m_actualDepTime)).right(4);
1382 const CCallsign callsign(fp.sender(), CCallsign::Aircraft);
1386 CTime(fp.m_hoursEnroute * 60 + fp.m_minutesEnroute, CTimeUnit::min()),
1387 CTime(fp.m_fuelAvailHours * 60 + fp.m_fuelAvailMinutes, CTimeUnit::min()), cruiseAlt,
1388 CSpeed(fp.m_trueCruisingSpeed, CSpeedUnit::kts()), rules, fp.m_route, fp.m_remarks);
1390 emit flightPlanReceived(callsign, flightPlan);
1393 void CFSDClient::handleClientQuery(
const QStringList &tokens)
1395 const ClientQuery clientQuery = ClientQuery::fromTokens(tokens);
1397 if (clientQuery.m_queryType == ClientQueryType::Unknown) {
return; }
1398 if (clientQuery.m_queryType == ClientQueryType::IsValidATC)
1402 else if (clientQuery.m_queryType == ClientQueryType::Capabilities)
1404 sendClientResponse(ClientQueryType::Capabilities, clientQuery.sender());
1406 else if (clientQuery.m_queryType == ClientQueryType::Com1Freq)
1408 sendClientResponse(ClientQueryType::Com1Freq, clientQuery.sender());
1410 else if (clientQuery.m_queryType == ClientQueryType::RealName)
1412 sendClientResponse(ClientQueryType::RealName, clientQuery.sender());
1414 else if (clientQuery.m_queryType == ClientQueryType::Server)
1416 sendClientResponse(ClientQueryType::Server, clientQuery.sender());
1418 else if (clientQuery.m_queryType == ClientQueryType::ATIS)
1422 else if (clientQuery.m_queryType == ClientQueryType::PublicIP)
1426 else if (clientQuery.m_queryType == ClientQueryType::INF)
1428 sendClientResponse(ClientQueryType::INF, clientQuery.sender());
1430 else if (clientQuery.m_queryType == ClientQueryType::FP)
1434 else if (clientQuery.m_queryType == ClientQueryType::AircraftConfig)
1436 QStringList aircraftConfigTokens = tokens.mid(3);
1437 QString aircraftConfigJson = aircraftConfigTokens.join(
":");
1439 const CCallsign callsign(clientQuery.sender(), CCallsign::Aircraft);
1441 QJsonParseError parserError;
1442 const QByteArray json = aircraftConfigJson.toUtf8();
1443 const QJsonDocument doc = QJsonDocument::fromJson(json, &parserError);
1445 if (parserError.error != QJsonParseError::NoError)
1447 CLogMessage(
this).
warning(u
"Failed to parse aircraft config packet: '%1' packet: '%2'")
1448 << parserError.errorString() << QString(json);
1452 const QJsonObject packet = doc.object();
1453 if (packet == JsonPackets::aircraftConfigRequest())
1457 QJsonObject config = this->getOwnAircraftParts().toJson();
1458 config.insert(CAircraftParts::attributeNameIsFullJson(),
true);
1459 QString data = QJsonDocument(QJsonObject { {
"config", config } }).toJson(QJsonDocument::Compact);
1460 data = convertToUnicodeEscaped(data);
1461 sendAircraftConfiguration(clientQuery.sender(), data);
1465 const bool inRange = isAircraftInRange(callsign);
1466 if (!inRange) {
return; }
1467 if (!getSetupForServer().receiveAircraftParts()) {
return; }
1468 const QJsonObject config = doc.object().value(
"config").toObject();
1469 if (config.isEmpty()) {
return; }
1471 const qint64 offsetTimeMs = currentOffsetTime(callsign);
1472 emit aircraftConfigReceived(clientQuery.sender(), config, offsetTimeMs);
1477 void CFSDClient::handleClientResponse(
const QStringList &tokens)
1479 const ClientResponse clientResponse = ClientResponse::fromTokens(tokens);
1480 if (clientResponse.isUnknownQuery()) {
return; }
1481 const QString sender = clientResponse.sender();
1483 QString responseData1;
1484 QString responseData2;
1486 if (!clientResponse.m_responseData.empty()) { responseData1 = clientResponse.m_responseData.at(0); }
1488 if (clientResponse.m_responseData.size() > 1) { responseData2 = clientResponse.m_responseData.at(1); }
1490 if (clientResponse.m_queryType == ClientQueryType::IsValidATC)
1492 emit validAtcResponseReceived(responseData2, responseData1 == u
"Y");
1494 else if (clientResponse.m_queryType == ClientQueryType::Capabilities)
1497 for (
auto keyValuePair : clientResponse.m_responseData)
1499 if (keyValuePair.count(
'=') != 1) {
continue; }
1501 const QStringList
split = keyValuePair.split(
'=');
1502 if (
split.size() < 2) {
continue; }
1503 const QString key =
split.at(0);
1504 const QString value =
split.at(1);
1506 if (value ==
"1") { capabilities |= fromQString<Capabilities>(key); }
1509 CClient::Capabilities caps = CClient::None;
1510 if (capabilities & Capabilities::AtcInfo) { caps |= CClient::FsdAtisCanBeReceived; }
1511 if (capabilities & Capabilities::FastPos) { caps |= CClient::FsdWithInterimPositions; }
1512 if (capabilities & Capabilities::VisPos) { caps |= CClient::FsdWithVisualPositions; }
1513 if (capabilities & Capabilities::AircraftInfo) { caps |= CClient::FsdWithIcaoCodes; }
1514 if (capabilities & Capabilities::AircraftConfig) { caps |= CClient::FsdWithAircraftConfig; }
1516 emit capabilityResponseReceived(clientResponse.sender(), caps);
1518 else if (clientResponse.m_queryType == ClientQueryType::Com1Freq)
1520 if (responseData1.isEmpty()) {
return; }
1522 const double freqMHz = responseData1.toDouble(&ok);
1523 if (!ok) {
return; }
1524 emit com1FrequencyResponseReceived(clientResponse.sender(),
CFrequency(freqMHz, CFrequencyUnit::MHz()));
1526 else if (clientResponse.m_queryType == ClientQueryType::RealName)
1529 emit realNameResponseReceived(clientResponse.sender(), responseData1);
1531 else if (clientResponse.m_queryType == ClientQueryType::Server)
1533 emit serverResponseReceived(clientResponse.sender(), responseData1);
1535 else if (clientResponse.m_queryType == ClientQueryType::ATIS)
1537 if (responseData1.isEmpty())
1542 updateAtisMap(clientResponse.sender(), fromQString<AtisLineType>(responseData1), responseData2);
1544 else if (clientResponse.m_queryType == ClientQueryType::PublicIP)
1548 else if (clientResponse.m_queryType == ClientQueryType::INF)
1552 else if (clientResponse.m_queryType == ClientQueryType::FP)
1556 else if (clientResponse.m_queryType == ClientQueryType::AircraftConfig)
1563 void CFSDClient::handleServerError(
const QStringList &tokens)
1565 const ServerError serverError = ServerError::fromTokens(tokens);
1566 switch (serverError.m_errorNumber)
1568 case ServerErrorCode::CallsignInUse:
CLogMessage(
this).
error(u
"The requested callsign is already taken");
break;
1569 case ServerErrorCode::InvalidCallsign:
CLogMessage(
this).
error(u
"The requested callsign is not valid");
break;
1570 case ServerErrorCode::InvalidCidPassword:
1573 case ServerErrorCode::InvalidRevision:
1574 CLogMessage(
this).
error(u
"This server does not support our protocol version");
1576 case ServerErrorCode::ServerFull:
CLogMessage(
this).
error(u
"The server is full");
break;
1577 case ServerErrorCode::CidSuspended:
CLogMessage(
this).
error(u
"Your user account is suspended");
break;
1578 case ServerErrorCode::RatingTooLow:
1579 CLogMessage(
this).
error(u
"You are not authorized to use the requested rating");
1581 case ServerErrorCode::InvalidClient:
1582 CLogMessage(
this).
error(u
"This software is not authorized for use on this network");
1584 case ServerErrorCode::RequestedLevelTooHigh:
1585 CLogMessage(
this).
error(u
"You are not authorized to use the requested pilot rating");
1589 case ServerErrorCode::SyntaxError:
1591 u
"Malformed packet, syntax error: '%1'. This can also occur if an OBS sends frequency text messages.")
1592 << serverError.getCausingParameter();
1594 case ServerErrorCode::InvalidSrcCallsign:
1595 CLogMessage(
this).
info(u
"FSD message was using an invalid callsign: %1 (%2)")
1596 << serverError.getCausingParameter() << serverError.getDescription();
1598 case ServerErrorCode::NoSuchCallsign:
1600 << serverError.getCausingParameter() << serverError.getDescription();
1602 case ServerErrorCode::NoFlightPlan:
CLogMessage(
this).
info(u
"FSD Server: no flight plan");
break;
1603 case ServerErrorCode::NoWeatherProfile:
1604 CLogMessage(
this).
info(u
"FSD Server: requested weather profile does not exist");
1608 case ServerErrorCode::AlreadyRegistered:
1609 CLogMessage(
this).
warning(u
"Server says already registered: %1") << serverError.getDescription();
1611 case ServerErrorCode::InvalidCtrl:
1612 CLogMessage(
this).
warning(u
"Server invalid control: %1") << serverError.getDescription();
1614 case ServerErrorCode::Unknown:
1616 << serverError.getCausingParameter() << serverError.getDescription();
1618 case ServerErrorCode::AuthTimeout:
CLogMessage(
this).
warning(u
"Client did not authenticate in time");
break;
1620 if (serverError.isFatalError()) { disconnectFromServer(); }
1623 void CFSDClient::handleRevBClientPartsPacket(
const QStringList &tokens)
1627 const RevBClientParts RevBClientParts = RevBClientParts::fromTokens(tokens);
1628 const CCallsign callsign(RevBClientParts.sender(), CCallsign::Aircraft);
1630 const bool inRange = isAircraftInRange(callsign);
1632 if (!inRange) {
return; }
1633 if (!getSetupForServer().receiveAircraftParts()) {
return; }
1635 const qint64 offsetTimeMs = currentOffsetTime(callsign);
1636 emit revbAircraftConfigReceived(RevBClientParts.sender(), RevBClientParts.m_partsval1, offsetTimeMs);
1640 void CFSDClient::handleRehost(
const QStringList &tokens)
1642 const Rehost rehost = Rehost::fromTokens(tokens);
1644 CLogMessage(
this).
info(u
"Server requested we switch server to %1") << rehost.m_hostname;
1646 SWIFT_AUDIT_X(!m_rehosting, Q_FUNC_INFO,
"Rehosting already in progress");
1649 auto rehostingSocket = std::make_shared<QTcpSocket>();
1650 connect(rehostingSocket.get(), &QTcpSocket::connected,
this, [
this, rehostingSocket] {
1651 readDataFromSocket();
1652 CLogMessage(this).debug(u
"Successfully switched server");
1653 m_socket = rehostingSocket;
1654 m_rehosting = false;
1655 rehostingSocket->disconnect(this);
1656 connectSocketSignals();
1657 readDataFromSocket();
1659 connect(rehostingSocket.get(), &QTcpSocket::errorOccurred,
this, [
this, rehostingSocket] {
1660 CLogMessage(this).warning(u
"Failed to switch server: %1") << rehostingSocket->errorString();
1661 m_rehosting = false;
1662 rehostingSocket->disconnect(this);
1663 if (m_socket->state() != QAbstractSocket::ConnectedState)
1665 updateConnectionStatus(CConnectionStatus::Disconnected);
1669 initiateConnection(rehostingSocket, rehost.m_hostname);
1672 void CFSDClient::handleMute(
const QStringList &tokens)
1674 const Mute mute = Mute::fromTokens(tokens);
1675 if (mute.receiver() != m_ownCallsign.asString()) {
return; }
1676 emit muteRequestReceived(mute.m_mute);
1679 void CFSDClient::initiateConnection(std::shared_ptr<QTcpSocket> rehostingSocket,
const QString &rehostingHost)
1681 const CServer server = this->getServer();
1682 const auto socket = rehostingSocket ? rehostingSocket : m_socket;
1685 const QString host = rehostingSocket ? rehostingHost : server.
getAddress();
1686 const quint16 port = rehostingSocket ? m_socket->peerPort() :
static_cast<quint16
>(getServer().getPort());
1689 resolveLoadBalancing(host, [=](
const QString &host) {
1690 socket->connectToHost(host, port);
1691 if (!rehostingSocket) { this->startPositionTimers(); }
1695 void CFSDClient::resolveLoadBalancing(
const QString &host, std::function<
void(
const QString &)> callback)
1697 if (QHostAddress(host).isNull() && (getServer().getName() ==
"AUTOMATIC" || m_rehosting) &&
1698 getServer().getEcosystem() == CEcosystem::VATSIM)
1701 Q_ASSERT_X(
sApp, Q_FUNC_INFO,
"Need app");
1704 QScopedPointer<QNetworkReply, QScopedPointerDeleteLater> nwReply(nwReplyPtr);
1706 if (nwReply->error() == QNetworkReply::NoError)
1708 QHostAddress addr(
static_cast<QString
>(nwReply->readAll()));
1711 callback(addr.toString());
1718 else { callback(host); }
1721 void CFSDClient::handleCustomPilotPacket(
const QStringList &tokens)
1723 const QString subType = tokens.at(2);
1725 if (subType == u
"PIR")
1727 PlaneInfoRequest planeInfoRequest = PlaneInfoRequest::fromTokens(tokens);
1730 const QString airlineIcao = m_server.getFsdSetup().force3LetterAirlineCodes() ?
1734 const QString livery = this->getConfiguredLiveryString(myAircraft);
1736 sendPlaneInformation(planeInfoRequest.sender(), acTypeICAO, airlineIcao, livery);
1738 else if (subType ==
"PI")
1740 if (tokens.size() > 6 && tokens.at(3) ==
"X")
1744 else if (tokens.size() > 4 && tokens.at(3) ==
"GEN")
1746 const PlaneInformation planeInformation = PlaneInformation::fromTokens(tokens);
1747 emit planeInformationReceived(planeInformation.sender(), planeInformation.m_aircraft,
1748 planeInformation.m_airline, planeInformation.m_livery);
1751 else if (subType ==
"I")
1755 else if (subType ==
"VI")
1758 if (!isInterimPositionReceivingEnabledForServer()) {
return; }
1760 const InterimPilotDataUpdate interimPilotDataUpdate = InterimPilotDataUpdate::fromTokens(tokens);
1761 const CCallsign callsign(interimPilotDataUpdate.sender(), CCallsign::Aircraft);
1765 interimPilotDataUpdate.m_longitude,
1766 interimPilotDataUpdate.m_altitudeTrue),
1767 CHeading(interimPilotDataUpdate.m_heading, CHeading::True, CAngleUnit::deg()),
1768 CAngle(interimPilotDataUpdate.m_pitch, CAngleUnit::deg()),
1769 CAngle(interimPilotDataUpdate.m_bank, CAngleUnit::deg()),
1770 CSpeed(interimPilotDataUpdate.m_groundSpeed, CSpeedUnit::kts()));
1772 const COnGroundInfo og(interimPilotDataUpdate.m_onGround ? COnGroundInfo::OnGround :
1773 COnGroundInfo::NotOnGround,
1774 COnGroundInfo::NotSetGroundDetails);
1779 const qint64 offsetTimeMs =
1783 emit interimPilotDataUpdatedReceived(situation);
1785 else if (subType ==
"FSIPI")
1787 const PlaneInformationFsinn planeInformationFsinn = PlaneInformationFsinn::fromTokens(tokens);
1788 emit planeInformationFsinnReceived(planeInformationFsinn.sender(), planeInformationFsinn.m_airlineIcao,
1789 planeInformationFsinn.m_aircraftIcao,
1790 planeInformationFsinn.m_aircraftIcaoCombinedType,
1791 planeInformationFsinn.m_sendMModelString);
1793 else if (subType ==
"FSIPIR")
1795 const PlaneInfoRequestFsinn planeInfoRequestFsinn = PlaneInfoRequestFsinn::fromTokens(tokens);
1796 sendPlaneInformationFsinn(planeInfoRequestFsinn.sender());
1797 emit planeInformationFsinnReceived(planeInfoRequestFsinn.sender(), planeInfoRequestFsinn.m_airlineIcao,
1798 planeInfoRequestFsinn.m_aircraftIcao,
1799 planeInfoRequestFsinn.m_aircraftIcaoCombinedType,
1800 planeInfoRequestFsinn.m_sendMModelString);
1805 const QString sender = tokens.at(0);
1806 const QStringList data = tokens.mid(3);
1807 emit customPilotPacketReceived(sender, data);
1811 #ifdef SWIFT_VATSIM_SUPPORT
1812 void CFSDClient::handleFsdIdentification(
const QStringList &tokens)
1816 const FSDIdentification fsdIdentification = FSDIdentification::fromTokens(tokens);
1817 vatsim_auth_set_initial_challenge(m_clientAuth, qPrintable(fsdIdentification.m_initialChallenge));
1819 char fsdChallenge[33];
1820 vatsim_auth_generate_challenge(m_serverAuth, fsdChallenge);
1821 vatsim_auth_set_initial_challenge(m_serverAuth, fsdChallenge);
1822 sendClientIdentification(QString(fsdChallenge));
1827 u
"You tried to connect to a VATSIM server without using VATSIM protocol, disconnecting!");
1828 disconnectFromServer();
1833 void CFSDClient::handleUnknownPacket(
const QString &line)
1838 void CFSDClient::handleUnknownPacket(
const QStringList &tokens) { this->handleUnknownPacket(tokens.join(
", ")); }
1840 void CFSDClient::printSocketError(QAbstractSocket::SocketError socketError)
1842 if (m_rehosting) {
return; }
1844 CLogMessage(
this).
error(u
"FSD socket error: %1") << this->socketErrorString(socketError);
1847 void CFSDClient::handleSocketError(QAbstractSocket::SocketError socketError)
1849 if (m_rehosting) {
return; }
1851 const QString error = this->socketErrorString(socketError);
1852 switch (socketError)
1855 case QAbstractSocket::RemoteHostClosedError:
1856 emit this->severeNetworkError(error);
1857 this->disconnectFromServer();
1863 void CFSDClient::handleSocketConnected()
1868 this->updateConnectionStatus(CConnectionStatus::Connected);
1874 if (this->getConnectionStatus() == newStatus) {
return; }
1879 QWriteLocker l(&m_lockUserClientBuffered);
1880 m_server.setConnectedSinceNow();
1881 ecoSystem = m_server.getEcosystem();
1883 this->setCurrentEcosystem(ecoSystem);
1887 QWriteLocker l(&m_lockUserClientBuffered);
1888 m_server.markAsDisconnected();
1893 this->stopPositionTimers();
1895 this->setLastEcosystem(m_server.getEcosystem());
1896 this->setCurrentEcosystem(CEcosystem::NoSystem);
1897 this->saveNetworkStatistics(m_server.getName());
1902 QWriteLocker l(&m_lockConnectionStatus);
1903 oldStatus = m_connectionStatus;
1904 m_connectionStatus = newStatus;
1907 emit this->connectionStatusChanged(oldStatus, newStatus);
1910 void CFSDClient::consolidateTextMessage(
const CTextMessage &textMessage)
1915 m_textMessagesToConsolidate.addConsolidatedTextMessage(textMessage);
1916 m_dsSendTextMessage.inputSignal();
1920 void CFSDClient::emitConsolidatedTextMessages()
1922 emit this->textMessagesReceived(m_textMessagesToConsolidate);
1923 m_textMessagesToConsolidate.clear();
1926 qint64 CFSDClient::receivedPositionFixTsAndGetOffsetTime(
const CCallsign &callsign, qint64 markerTs)
1928 Q_ASSERT_X(!callsign.
isEmpty(), Q_FUNC_INFO,
"Need callsign");
1930 if (markerTs < 0) { markerTs = QDateTime::currentMSecsSinceEpoch(); }
1931 if (!m_lastPositionUpdate.contains(callsign))
1933 m_lastPositionUpdate.insert(callsign, markerTs);
1934 return CFsdSetup::c_positionTimeOffsetMsec;
1936 const qint64 oldTs = m_lastPositionUpdate.value(callsign);
1937 m_lastPositionUpdate[callsign] = markerTs;
1940 const qint64 diff = qAbs(markerTs - oldTs);
1941 this->insertLatestOffsetTime(callsign, diff);
1944 const qint64 avgTimeMs = this->averageOffsetTimeMs(callsign, count, 3);
1945 qint64 offsetTime = CFsdSetup::c_positionTimeOffsetMsec;
1947 if (avgTimeMs < CFsdSetup::c_interimPositionTimeOffsetMsec && count >= 3)
1949 offsetTime = CFsdSetup::c_interimPositionTimeOffsetMsec;
1952 return m_additionalOffsetTime + offsetTime;
1955 qint64 CFSDClient::currentOffsetTime(
const CCallsign &callsign)
const
1957 Q_ASSERT_X(!callsign.
isEmpty(), Q_FUNC_INFO,
"Need callsign");
1959 if (!m_lastOffsetTimes.contains(callsign) || m_lastOffsetTimes[callsign].isEmpty())
1961 return CFsdSetup::c_positionTimeOffsetMsec;
1963 return m_lastOffsetTimes[callsign].front();
1966 void CFSDClient::clearState()
1968 m_rehosting =
false;
1969 m_stoppedSendingVisualPositions =
false;
1970 m_serverWantsVisualPositions =
false;
1971 m_visualPositionUpdateSentCount = 0;
1972 m_textMessagesToConsolidate.clear();
1973 m_pendingAtisQueries.clear();
1974 m_lastPositionUpdate.clear();
1975 m_lastOffsetTimes.clear();
1976 m_atcStations.clear();
1977 m_queuedFsdMessages.clear();
1978 m_sentAircraftConfig = CAircraftParts::null();
1982 void CFSDClient::clearState(
const CCallsign &callsign)
1984 if (callsign.
isEmpty()) {
return; }
1985 m_pendingAtisQueries.remove(callsign);
1986 m_lastPositionUpdate.remove(callsign);
1987 m_interimPositionReceivers.remove(callsign);
1988 m_lastOffsetTimes.remove(callsign);
1991 void CFSDClient::insertLatestOffsetTime(
const CCallsign &callsign, qint64 offsetMs)
1993 QList<qint64> &offsets = m_lastOffsetTimes[callsign];
1994 offsets.push_front(offsetMs);
1995 if (offsets.size() > c_maxOffsetTimes) { offsets.removeLast(); }
1998 qint64 CFSDClient::averageOffsetTimeMs(
const CCallsign &callsign,
int &count,
int maxLastValues)
const
2000 const QList<qint64> &offsets = m_lastOffsetTimes[callsign];
2001 if (offsets.size() < 1) {
return -1; }
2004 for (qint64 v : offsets)
2008 if (count > maxLastValues) {
break; }
2010 return qRound(
static_cast<double>(sum) / count);
2013 bool CFSDClient::isInterimPositionSendingEnabledForServer()
const
2015 const CFsdSetup::SendReceiveDetails d = this->getSetupForServer().getSendReceiveDetails();
2016 return (d & CFsdSetup::SendInterimPositions);
2019 bool CFSDClient::isInterimPositionReceivingEnabledForServer()
const
2021 const CFsdSetup::SendReceiveDetails d = this->getSetupForServer().getSendReceiveDetails();
2022 return (d & CFsdSetup::ReceiveInterimPositions);
2025 bool CFSDClient::isVisualPositionSendingEnabledForServer()
const
2027 const CFsdSetup::SendReceiveDetails d = this->getSetupForServer().getSendReceiveDetails();
2028 return (d & CFsdSetup::SendVisualPositions);
2031 const CFsdSetup &CFSDClient::getSetupForServer()
const {
return m_server.getFsdSetup(); }
2033 void CFSDClient::maybeHandleAtisReply(
const CCallsign &sender,
const CCallsign &receiver,
const QString &message)
2035 Q_ASSERT(m_pendingAtisQueries.contains(sender));
2036 PendingAtisQuery &pendingQuery = m_pendingAtisQueries[sender];
2037 pendingQuery.m_atisMessage.push_back(message);
2040 if (pendingQuery.m_queryTime.secsTo(QDateTime::currentDateTimeUtc()) > 5)
2042 const QString atisMessage(pendingQuery.m_atisMessage.join(QChar::LineFeed));
2044 tm.setCurrentUtcTime();
2045 this->consolidateTextMessage(tm);
2046 m_pendingAtisQueries.remove(sender);
2053 thread_local
const QRegularExpression reLogoff(
"^\\d{0,4}z$");
2054 if (reLogoff.match(message).hasMatch())
2056 emit atisLogoffTimeReplyReceived(sender, message);
2058 for (
const auto &line : std::as_const(pendingQuery.m_atisMessage))
2060 if (!atisMessage.isEmpty()) atisMessage.appendMessage(
"\n");
2061 atisMessage.appendMessage(line);
2064 m_pendingAtisQueries.remove(sender);
2069 void CFSDClient::fsdMessageSettingsChanged()
2071 if (m_rawFsdMessageLogFile.isOpen()) { m_rawFsdMessageLogFile.close(); }
2078 const QString filePath = CFileUtils::appendFilePaths(setting.
getFileDir(),
"rawfsdmessages.log");
2079 m_rawFsdMessageLogFile.setFileName(filePath);
2080 m_rawFsdMessageLogFile.open(QIODevice::Text | QIODevice::WriteOnly);
2084 const QString filePath = CFileUtils::appendFilePaths(setting.
getFileDir(),
"rawfsdmessages.log");
2085 m_rawFsdMessageLogFile.setFileName(filePath);
2086 m_rawFsdMessageLogFile.open(QIODevice::Text | QIODevice::WriteOnly | QIODevice::Append);
2088 else if (setting.
getFileWriteMode() == CRawFsdMessageSettings::Timestamped)
2090 QString filename(
"rawfsdmessages");
2091 filename += QLatin1String(
"_");
2092 filename += QDateTime::currentDateTime().toString(QStringLiteral(
"yyMMddhhmmss"));
2093 filename += QLatin1String(
".log");
2094 const QString filePath = CFileUtils::appendFilePaths(setting.
getFileDir(), filename);
2095 m_rawFsdMessageLogFile.setFileName(filePath);
2096 m_rawFsdMessageLogFile.open(QIODevice::Text | QIODevice::WriteOnly);
2102 return m_interimPositionReceivers;
2107 m_interimPositionReceivers = interimPositionReceivers;
2110 bool CFSDClient::isPendingConnection()
const
2112 return m_connectionStatus.isConnecting() || m_connectionStatus.isDisconnecting();
2115 int CFSDClient::increaseStatisticsValue(
const QString &identifier,
const QString &appendix)
2117 if (identifier.isEmpty() || !m_statistics) {
return -1; }
2119 QWriteLocker l(&m_lockStatistics);
2120 const QString i = appendix.isEmpty() ? identifier : identifier % u
"." % appendix;
2121 int &v = m_callStatistics[i];
2124 constexpr
int MaxTimeValues = 50;
2125 m_callByTime.push_front(QPair<qint64, QString>(QDateTime::currentMSecsSinceEpoch(), i));
2126 if (m_callByTime.size() > MaxTimeValues) { m_callByTime.removeLast(); }
2130 int CFSDClient::increaseStatisticsValue(
const QString &identifier,
int value)
2132 return increaseStatisticsValue(identifier, QString::number(value));
2135 void CFSDClient::clearStatistics()
2137 QWriteLocker l(&m_lockStatistics);
2138 m_callStatistics.clear();
2139 m_callByTime.clear();
2142 QString CFSDClient::getNetworkStatisticsAsText(
bool reset,
const QString &separator)
2144 QVector<std::pair<int, QString>> transformed;
2146 QVector<QPair<qint64, QString>> callByTime;
2149 QReadLocker l(&m_lockStatistics);
2150 callStatistics = m_callStatistics;
2151 callByTime = m_callByTime;
2154 if (callStatistics.isEmpty()) {
return {}; }
2155 for (
const auto [key, value] :
makePairsRange(std::as_const(callStatistics)))
2158 transformed.push_back({ value, key });
2162 std::sort(transformed.begin(), transformed.end(), std::greater<>());
2164 for (
const auto &pair : transformed)
2166 stats += (stats.isEmpty() ? QString() : separator) % pair.second % u
": " % QString::number(pair.first);
2169 for (
const auto &pair : transformed)
2171 stats += (stats.isEmpty() ? QString() : separator) % pair.second % u
": " % QString::number(pair.first);
2174 if (!callByTime.isEmpty())
2176 const qint64 lastTs = callByTime.front().first;
2177 for (
const auto &pair : std::as_const(callByTime))
2179 const qint64 deltaTs = lastTs - pair.first;
2180 stats += separator % QStringLiteral(
"%1").arg(deltaTs, 5, 10, QChar(
'0')) % u
": " % pair.second;
2184 if (reset) { this->clearStatistics(); }
2188 void CFSDClient::gracefulShutdown()
2190 disconnectFromServer();
2194 void CFSDClient::readDataFromSocketMaxLines(
int maxLines)
2196 if (m_socket->bytesAvailable() < 1) {
return; }
2201 while (m_socket->canReadLine())
2203 const QByteArray dataEncoded = m_socket->readLine();
2204 if (dataEncoded.isEmpty()) {
continue; }
2205 const QString data = m_decoder(dataEncoded);
2206 this->parseMessage(data);
2209 static constexpr
int MaxLines = 75 - 1;
2210 if (maxLines < 0) { maxLines = MaxLines; }
2212 if (lines > maxLines)
2214 static constexpr
int DelayMs = 10;
2215 const int newMax = qRound(1.2 * lines);
2217 CLogMessage(
this).
debug(u
"ReadDataFromSocket has too many lines (>%1), will read again in %2ms")
2218 << MaxLines << DelayMs;
2219 QPointer<CFSDClient> myself(
this);
2222 if (myself) { myself->readDataFromSocketMaxLines(newMax); }
2229 QString CFSDClient::socketErrorString(QAbstractSocket::SocketError error)
const
2231 QString e = CFSDClient::socketErrorToQString(error);
2232 if (!m_socket->errorString().isEmpty()) { e += QStringLiteral(
": ") % m_socket->errorString(); }
2236 QString CFSDClient::socketErrorToQString(QAbstractSocket::SocketError error)
2238 static const QMetaEnum metaEnum = QMetaEnum::fromType<QAbstractSocket::SocketError>();
2239 return metaEnum.valueToKey(error);
2242 void CFSDClient::parseMessage(
const QString &lineRaw)
2246 const QString line = lineRaw.trimmed();
2248 if (m_printToConsole) { qDebug() <<
"FSD Recv=>" << line; }
2249 emitRawFsdMessage(line,
false);
2251 for (
const QString &str :
makeKeysRange(std::as_const(m_messageTypeMapping)))
2253 if (line.startsWith(str))
2256 messageType = m_messageTypeMapping.value(str, MessageType::Unknown);
2264 increaseStatisticsValue(QStringLiteral(
"parseMessage"), this->messageTypeToString(messageType));
2267 if (messageType != MessageType::Unknown)
2270 const QString payload = line.mid(cmd.size()).trimmed();
2273 if (payload.length() == 0) {
return; }
2275 const QStringList tokens = payload.split(
':');
2276 switch (messageType)
2279 case MessageType::AddAtc:
2280 case MessageType::AddPilot:
2281 case MessageType::ServerHeartbeat:
2282 case MessageType::ProController:
2283 case MessageType::ClientIdentification:
2284 case MessageType::RegistrationInfo:
2285 case MessageType::RevBPilotDescription:
break;
2288 case MessageType::AtcDataUpdate: handleAtcDataUpdate(tokens);
break;
2289 #ifdef SWIFT_VATSIM_SUPPORT
2290 case MessageType::AuthChallenge: handleAuthChallenge(tokens);
break;
2291 case MessageType::AuthResponse: handleAuthResponse(tokens);
break;
2293 case MessageType::ClientQuery: handleClientQuery(tokens);
break;
2294 case MessageType::ClientResponse: handleClientResponse(tokens);
break;
2295 case MessageType::DeleteATC: handleDeleteATC(tokens);
break;
2296 case MessageType::DeletePilot: handleDeletePilot(tokens);
break;
2297 case MessageType::FlightPlan: handleFlightPlan(tokens);
break;
2298 #ifdef SWIFT_VATSIM_SUPPORT
2299 case MessageType::FsdIdentification: handleFsdIdentification(tokens);
break;
2301 case MessageType::KillRequest: handleKillRequest(tokens);
break;
2302 case MessageType::PilotDataUpdate: handlePilotDataUpdate(tokens);
break;
2303 case MessageType::Ping: handlePing(tokens);
break;
2304 case MessageType::Pong: handlePong(tokens);
break;
2305 case MessageType::ServerError: handleServerError(tokens);
break;
2306 case MessageType::TextMessage: handleTextMessage(tokens);
break;
2307 case MessageType::PilotClientCom: handleCustomPilotPacket(tokens);
break;
2308 case MessageType::RevBClientParts: handleRevBClientPartsPacket(tokens);
break;
2309 case MessageType::VisualPilotDataUpdate:
2310 case MessageType::VisualPilotDataPeriodic:
2311 case MessageType::VisualPilotDataStopped: handleVisualPilotDataUpdate(tokens, messageType);
break;
2312 case MessageType::VisualPilotDataToggle: handleVisualPilotDataToggle(tokens);
break;
2313 case MessageType::EuroscopeSimData: handleEuroscopeSimData(tokens);
break;
2314 case MessageType::Rehost: handleRehost(tokens);
break;
2315 case MessageType::Mute: handleMute(tokens);
break;
2319 case MessageType::Unknown: handleUnknownPacket(tokens);
break;
2322 else { handleUnknownPacket(line); }
2325 void CFSDClient::emitRawFsdMessage(
const QString &fsdMessage,
bool isSent)
2327 if (!m_unitTestMode && !m_rawFsdMessagesEnabled) {
return; }
2328 QString fsdMessageFiltered(fsdMessage);
2329 if (m_filterPasswordFromLogin)
2331 if (fsdMessageFiltered.startsWith(
"#AP"))
2333 thread_local
const QRegularExpression re(R
"(^(#AP\w+:SERVER:\d+:)[^:]+(:\d+:\d+:\d+:.+)$)");
2334 fsdMessageFiltered.replace(re, "\\1<password>\\2");
2335 m_filterPasswordFromLogin =
false;
2339 const QString prefix = isSent ?
"FSD Sent=>" :
"FSD Recv=>";
2341 rawMessage.setCurrentUtcTime();
2342 if (m_rawFsdMessageLogFile.isOpen())
2344 QTextStream stream(&m_rawFsdMessageLogFile);
2345 stream << rawMessage.toQString().trimmed() << Qt::endl;
2347 emit rawFsdMessage(rawMessage);
2350 bool CFSDClient::saveNetworkStatistics(
const QString &server)
2352 if (m_callStatistics.isEmpty()) {
return false; }
2354 const QString s = this->getNetworkStatisticsAsText(
false,
"\n");
2355 if (s.isEmpty()) {
return false; }
2356 const QString fn = QStringLiteral(
"networkstatistics_%1_%2.log")
2357 .arg(QDateTime::currentDateTimeUtc().toString(
"yyMMddhhmmss"), server);
2358 const QString fp = CFileUtils::appendFilePaths(CSwiftDirectories::logDirectory(), fn);
2359 return CFileUtils::writeStringToFile(s, fp);
2362 void CFSDClient::startPositionTimers()
2364 m_positionUpdateTimer.start(c_updatePositionIntervalMsec);
2365 m_scheduledConfigUpdate.start(c_processingIntervalMsec);
2366 m_fsdSendMessageTimer.start(c_sendFsdMsgIntervalMsec);
2367 m_queuedFsdMessages.clear();
2371 if (this->isInterimPositionSendingEnabledForServer())
2373 m_interimPositionUpdateTimer.start(c_updateInterimPositionIntervalMsec);
2375 else { m_interimPositionUpdateTimer.stop(); }
2376 if (this->isVisualPositionSendingEnabledForServer())
2378 m_visualPositionUpdateTimer.start(c_updateVisualPositionIntervalMsec);
2380 else { m_visualPositionUpdateTimer.stop(); }
2384 void CFSDClient::stopPositionTimers()
2386 m_positionUpdateTimer.stop();
2387 m_interimPositionUpdateTimer.stop();
2388 m_visualPositionUpdateTimer.stop();
2389 m_scheduledConfigUpdate.stop();
2390 m_fsdSendMessageTimer.stop();
2393 void CFSDClient::updateAtisMap(
const QString &callsign, AtisLineType type,
const QString &line)
2396 if (type == AtisLineType::VoiceRoom)
2398 m_mapAtisMessages[callsign].m_voiceRoom = line;
2399 m_mapAtisMessages[callsign].m_lineCount++;
2402 if (type == AtisLineType::TextMessage)
2404 m_mapAtisMessages[callsign].m_textLines.push_back(line);
2405 m_mapAtisMessages[callsign].m_lineCount++;
2408 if (type == AtisLineType::ZuluLogoff)
2410 m_mapAtisMessages[callsign].m_zuluLogoff = line;
2411 m_mapAtisMessages[callsign].m_lineCount++;
2414 if (!m_mapAtisMessages.contains(callsign)) {
return; }
2417 m_mapAtisMessages[callsign].m_lineCount++;
2419 const CCallsign cs(callsign, CCallsign::Atc);
2421 emit atisLogoffTimeReplyReceived(cs, m_mapAtisMessages[callsign].m_zuluLogoff);
2424 for (
const QString &tm : std::as_const(m_mapAtisMessages[callsign].m_textLines))
2426 const QString fixed = tm.trimmed();
2427 if (!fixed.isEmpty())
2431 thread_local
const QRegularExpression RegExp(R
"([\n\t\r])");
2432 const QString test = fixed.toLower().remove(RegExp);
2433 if (test ==
"z")
return;
2434 if (test.startsWith(
"z") && test.length() == 2)
return;
2435 if (test.length() == 1)
return;
2438 if (!atisMessage.isEmpty()) atisMessage.appendMessage(
"\n");
2439 atisMessage.appendMessage(fixed);
2443 emit this->atisReplyReceived(cs, atisMessage);
2445 m_mapAtisMessages.remove(callsign);
2449 void CFSDClient::pendingTimeoutCheck()
2451 if (!this->isPendingConnection()) {
return; }
2453 const qint64 age = QDateTime::currentMSecsSinceEpoch() - m_loginSince;
2454 if (age < PendingConnectionTimeoutMs) {
return; }
2457 CLogMessage(
this).
warning(u
"Timeout on pending connection to '%1'") << this->getServer().getName();
2458 this->disconnectFromServer();
2474 if (suffix.contains(QStringLiteral(
"ATIS"), Qt::CaseInsensitive))
2476 static const CLength l_Atis(150.0, CLengthUnit::NM());
2477 return maxOrNotNull(networkRange, l_Atis);
2479 if (suffix.contains(QStringLiteral(
"GND"), Qt::CaseInsensitive))
2481 static const CLength l_Gnd(10.0, CLengthUnit::NM());
2482 return maxOrNotNull(networkRange, l_Gnd);
2484 if (suffix.contains(QStringLiteral(
"TWR"), Qt::CaseInsensitive))
2486 static const CLength l_Twr(25.0, CLengthUnit::NM());
2487 return maxOrNotNull(networkRange, l_Twr);
2489 if (suffix.contains(QStringLiteral(
"DEP"), Qt::CaseInsensitive))
2491 static const CLength l_Dep(150.0, CLengthUnit::NM());
2492 return maxOrNotNull(networkRange, l_Dep);
2494 if (suffix.contains(QStringLiteral(
"APP"), Qt::CaseInsensitive))
2496 static const CLength l_App(150.0, CLengthUnit::NM());
2497 return maxOrNotNull(networkRange, l_App);
2499 if (suffix.contains(QStringLiteral(
"CTR"), Qt::CaseInsensitive))
2501 static const CLength l_Ctr(300.0, CLengthUnit::NM());
2502 return maxOrNotNull(networkRange, l_Ctr);
2504 if (suffix.contains(QStringLiteral(
"FSS"), Qt::CaseInsensitive))
2506 static const CLength l_Fss(1500.0, CLengthUnit::NM());
2507 return maxOrNotNull(networkRange, l_Fss);
2510 return networkRange;
2515 if (l1.
isNull()) {
return l2; }
2516 if (l2.
isNull()) {
return l1; }
2517 return (l2 > l1) ? l2 : l1;
2520 QString CFSDClient::noColons(
const QString &input)
2522 if (!input.contains(
':')) {
return input; }
2523 QString copy(input);
2524 return copy.remove(
':');
2529 switch (flightRules)
2531 case CFlightPlan::IFR:
return FlightType::IFR;
2532 case CFlightPlan::VFR:
return FlightType::VFR;
2533 case CFlightPlan::SVFR:
return FlightType::SVFR;
2534 case CFlightPlan::DVFR:
return FlightType::DVFR;
2535 default:
return FlightType::IFR;
2539 const QString &CFSDClient::messageTypeToString(
MessageType mt)
const
2542 while (i != m_messageTypeMapping.constEnd())
2544 if (i.value() == mt) {
return i.key(); }
2548 static const QString empty;
2552 void CFSDClient::handleIllegalFsdState(
const QString &message)
2554 if (CBuildConfig::isLocalDeveloperDebugBuild()) {
SWIFT_VERIFY_X(
false, Q_FUNC_INFO,
"Illegal FSD state"); }
2558 const QJsonObject &CFSDClient::JsonPackets::aircraftConfigRequest()
2560 static const QJsonObject jsonObject { {
"request",
"full" } };
SWIFT_CORE_EXPORT swift::core::CApplication * sApp
Single instance of application object.
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
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.
void setCallsign(const swift::misc::aviation::CCallsign &callsign)
Preset functions.
void setIcaoCodes(const swift::misc::simulation::CSimulatedAircraft &ownAircraft)
Preset functions.
void sendClientQueryCom1Freq(const swift::misc::aviation::CCallsign &callsign)
void sendFlightPlan(const swift::misc::aviation::CFlightPlan &flightPlan)
bool isConnected() const
Connection status.
void sendTextMessages(const swift::misc::network::CTextMessageList &messages)
void sendClientQueryAtis(const swift::misc::aviation::CCallsign &callsign)
void setServer(const swift::misc::network::CServer &server)
Preset functions.
void setLiveryAndModelString(const QString &livery, bool sendLiveryString, const QString &modelString, bool sendModelString)
Preset functions.
const swift::misc::network::CServer & getServer() const
Get the server.
swift::misc::network::CLoginMode getLoginMode() const
Mode.
void disconnectFromServer()
Connect/disconnect.
void sendPlaneInfoRequest(const swift::misc::aviation::CCallsign &receiver)
void sendClientQueryRealName(const swift::misc::aviation::CCallsign &callsign)
PilotRating getPilotRating() const
Rating.
QStringList getPresetValues() const
List of all preset values.
void sendClientQueryServer(const swift::misc::aviation::CCallsign &callsign)
void setSimType(const swift::misc::simulation::CSimulatorInfo &simInfo)
Preset functions.
void sendClientQueryAircraftConfig(const swift::misc::aviation::CCallsign &callsign)
void textMessageSent(const swift::misc::network::CTextMessage &sentMessage)
We have sent a text message.
void sendTextMessage(const swift::misc::network::CTextMessage &message)
void sendClientQueryFlightPlan(const swift::misc::aviation::CCallsign &callsign)
void sendRadioMessage(const QVector< int > &frequencieskHz, const QString &message)
void connectToServer()
Connect/disconnect.
void sendPlaneInfoRequestFsinn(const swift::misc::aviation::CCallsign &callsign)
swift::misc::network::CConnectionStatus getConnectionStatus() const
Connection status.
bool isDisconnected() const
Connection status.
FSinn specific version of plane information request.
Request to send plane information. Shall be answered by a PlaneInformation message.
Text, radio or private message.
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.
Class for emitting a log message.
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.
Callable wrapper for a member function with function signature F.
Streamable status message, e.g.
static bool isInThisThread(const QObject *toBeTested)
Is the current thread the object's thread?
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.
void setOnGround(bool onGround)
Set aircraft on ground.
void setGearDown(bool down)
Set gear down.
void setLights(const CAircraftLights &lights)
Set aircraft lights.
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.
QString asFpVatsimAltitudeString() const
As simple VATSIM string, only FLxxx or altitude as ft.
void parseFromString(const QString &value)
Parse value from string.
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.
const QString & asString() const
Get callsign (normalized)
QString getSuffix() const
Get the callsign suffix ("TWR", "ATIS" ...) if any ("_" is removed)
bool isEmpty() const
Is empty?
QString getFsdCallsignString() const
The callsign string used with FSD.
Value object for a set of callsigns.
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.
const physical_quantities::CTime & getFuelTime() const
Get amount of fuel load in time.
const physical_quantities::CSpeed & getCruiseTrueAirspeed() const
Get planned cruise TAS.
const QString & getRoute() const
Get route string.
FlightRules
Flight rules (VFR or IFR)
const QDateTime & getTakeoffTimePlanned() const
Get planned takeoff time (planned)
FlightRules getFlightRules() const
Get flight rules as in FlightRules.
const QString & getRemarks() const
Get remarks string.
const CAirportIcaoCode & getAlternateAirportIcao() const
Get alternate destination airport ICAO code.
const CAltitude & getCruiseAltitude() const
Cruising altitudes.
const physical_quantities::CTime & getEnrouteTime() const
Get planned enroute flight time.
const CAirportIcaoCode & getOriginAirportIcao() const
Get origin airport ICAO code.
const CAirportIcaoCode & getDestinationAirportIcao() const
Get destination airport ICAO code.
const QDateTime & getTakeoffTimeActual() const
Get actual takeoff time (actual)
CFlightPlanAircraftInfo getAircraftInfo() const
Get ICAO aircraft NAV/COM equipment.
Heading as used in aviation, can be true or magnetic heading.
Information about the ground status.
bool isRestricted() const
Restricted channel?
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.
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.
Value object for a FSD setup.
bool shouldSendFlightPlanEquipmentInIcaoFormat() const
FSD setup flags.
const QString & getTextCodec() const
Get codec.
bool receiveEuroscopeSimData() const
FSD setup flags.
Value object encapsulating information about login mode.
bool isObserver() const
Is login as observer?
bool isPilot() const
Is login as pilot?
Value object for a raw FSD message.
Value object encapsulating information of a server.
const CFsdSetup & getFsdSetup() const
Get FSD setup.
const QString & getAddress() const
Get address.
const CUser & getUser() const
Get user.
ServerType getServerType() const
Get server type.
Value object encapsulating information of a text message.
void markAsSent()
Mark as sent.
bool isEmpty() const
Empty message.
bool isSupervisorMessage() const
Supervisor message?
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...
const QString & getPassword() const
Get password.
const QString & getRealName() const
Get full name.
const QString & getId() const
Get id.
QString getRealNameAndHomeBase(const QString &separator=QString(" ")) const
Real name + homebase.
Direct in memory access to client (network client) data.
Physical unit angle (radians, degrees)
Physical unit length (length)
int valueInteger(MU unit) const
As integer value.
bool isNull() const
Is quantity null?
double value(MU unit) const
Value in given unit.
QList< int > getHrsMinSecParts() const
Parts hh, mm, ss.
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.
Simulator getSimulator() const
Simulator.
Direct threadsafe in memory access to own aircraft.
Direct thread safe in memory access to remote aircraft.
PilotRating
Pilot ratings.
ClientQueryType
Client query types.
@ EuroscopeSimData
Broadcast to announce we request SIMDATA packets.
Capabilities
Client capability flags */.
TextMessageGroups
Message groups.
constexpr int PROTOCOL_REVISION_VATSIM_AUTH
Protocol version.
constexpr int PROTOCOL_REVISION_VATSIM_VELOCITY
Protocol version.
constexpr int PROTOCOL_REVISION_CLASSIC
Protocol version.
Free functions in swift::misc.
auto makeKeysRange(const T &container)
Returns a const CRange for iterating over the keys of a Qt associative container.
bool is09OnlyString(const QString &testString)
String with 0-9 only.
StatusSeverity
Status severities.
auto makePairsRange(const T &container)
Returns a const CRange for iterating over the keys and values of a Qt associative container.
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...
std::vector< std::string > split(const std::string &str, size_t maxSplitCount=0, const std::string &delimiter=" ")
Split string by delimiter and maxSplitCount times.
#define SWIFT_AUDIT_X(COND, WHERE, WHAT)
A weaker kind of verify.
#define SWIFT_VERIFY_X(COND, WHERE, WHAT)
A weaker kind of assert.