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());
93 initializeMessageTypes();
94 connectSocketSignals();
96 m_positionUpdateTimer.setObjectName(this->objectName().append(
":m_positionUpdateTimer"));
97 connect(&m_positionUpdateTimer, &QTimer::timeout,
this, &CFSDClient::sendPilotDataUpdate);
99 m_interimPositionUpdateTimer.setObjectName(this->objectName().append(
":m_interimPositionUpdateTimer"));
100 connect(&m_interimPositionUpdateTimer, &QTimer::timeout,
this, &CFSDClient::sendInterimPilotDataUpdate);
102 m_visualPositionUpdateTimer.setObjectName(this->objectName().append(
":m_visualPositionUpdateTimer"));
103 connect(&m_visualPositionUpdateTimer, &QTimer::timeout,
this, [
this] { sendVisualPilotDataUpdate(); });
105 m_scheduledConfigUpdate.setObjectName(this->objectName().append(
":m_scheduledConfigUpdate"));
106 connect(&m_scheduledConfigUpdate, &QTimer::timeout,
this, &CFSDClient::sendIncrementalAircraftConfig);
108 m_fsdSendMessageTimer.setObjectName(this->objectName().append(
":m_fsdSendMessageTimer"));
109 connect(&m_fsdSendMessageTimer, &QTimer::timeout,
this, [
this]() { this->sendQueuedMessage(); });
111 fsdMessageSettingsChanged();
121 void CFSDClient::connectSocketSignals()
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);
129 #ifdef SWIFT_VATSIM_SUPPORT
130 void CFSDClient::setClientIdAndKey(quint16
id,
const QByteArray &key)
132 QWriteLocker l(&m_lockUserClientBuffered);
133 m_clientAuth = vatsim_auth_create(
id, qPrintable(key));
134 m_serverAuth = vatsim_auth_create(
id, qPrintable(key));
141 "Can't change server details while still connected");
144 QTextCodec *textCodec = QTextCodec::codecForName(codecName.toLocal8Bit());
145 if (!textCodec) { textCodec = QTextCodec::codecForName(
"utf-8"); }
146 const int protocolRev = (server.
getServerType() == CServer::FSDServerVatsim) ?
150 QWriteLocker l(&m_lockUserClientBuffered);
152 m_protocolRevision = protocolRev;
153 m_fsdTextCodec = textCodec;
159 "Can't change callsign while still connected");
162 QWriteLocker l(&m_lockUserClientBuffered);
163 m_ownCallsign = callsign;
169 "Can't change ICAO codes while still connected");
172 QWriteLocker l(&m_lockUserClientBuffered);
186 bool sendModelString)
188 QWriteLocker l(&m_lockUserClientBuffered);
189 m_ownLivery = livery;
190 m_ownModelString = modelString;
191 m_sendLiveryString = sendLiveryString;
192 m_sendModelString = sendModelString;
199 QWriteLocker l(&m_lockUserClientBuffered);
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;
216 QReadLocker l(&m_lockUserClientBuffered);
217 const QStringList v = { m_ownModelString,
230 QMetaObject::invokeMethod(
this, [=] {
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);
244 m_filterPasswordFromLogin =
true;
246 m_loginSince = QDateTime::currentMSecsSinceEpoch();
247 const qint64 timerMs = qRound(PendingConnectionTimeoutMs * 1.25);
249 const QPointer<CFSDClient> myself(
this);
252 this->pendingTimeoutCheck();
255 this->updateConnectionStatus(CConnectionStatus::Connecting);
257 initiateConnection();
264 QMetaObject::invokeMethod(
this, [=] {
270 this->stopPositionTimers();
271 this->updateConnectionStatus(CConnectionStatus::Disconnecting);
275 if (m_socket->isOpen())
277 if (mode.
isPilot()) { this->sendDeletePilot(); }
278 else if (mode.
isObserver()) { this->sendDeleteAtc(); }
282 this->updateConnectionStatus(CConnectionStatus::Disconnected);
286 void CFSDClient::sendLogin(
const QString &token)
292 const QString callsign = m_ownCallsign.
asString();
297 const AddPilot pilotLogin(callsign, cid, password, m_pilotRating, m_protocolRevision, m_simType, name);
298 sendQueuedMessage(pilotLogin);
300 << callsign << cid << toQString(m_pilotRating) << m_protocolRevision << toQString(m_simType) << name;
304 const AddAtc addAtc(callsign, name, cid, password, m_atcRating, m_protocolRevision);
305 sendQueuedMessage(addAtc);
307 << callsign << cid << toQString(m_atcRating) << m_protocolRevision << name;
312 this->sendClientQuery(ClientQueryType::EuroscopeSimData, {}, {});
316 void CFSDClient::sendDeletePilot()
320 sendQueuedMessage(deletePilot);
323 void CFSDClient::sendDeleteAtc()
326 const DeleteAtc deleteAtc(getOwnCallsignAsString(), cid);
327 sendQueuedMessage(deleteAtc);
330 void CFSDClient::sendPilotDataUpdate()
334 if (m_loginMode == CLoginMode::Observer)
336 sendAtcDataUpdate(myAircraft.latitude().value(CAngleUnit::deg()),
337 myAircraft.longitude().value(CAngleUnit::deg()));
341 if (this->isVisualPositionSendingEnabledForServer())
346 sendVisualPilotDataUpdate(
true);
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);
364 void CFSDClient::sendInterimPilotDataUpdate()
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());
376 for (
const auto &receiver : std::as_const(m_interimPositionReceivers))
378 interimPilotDataUpdate.setReceiver(receiver.asString());
379 sendQueuedMessage(interimPilotDataUpdate);
384 void CFSDClient::sendVisualPilotDataUpdate(
bool slowUpdate)
387 if (m_loginMode == CLoginMode::Observer || !isVisualPositionSendingEnabledForServer()) {
return; }
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)
400 if (m_stoppedSendingVisualPositions) {
return; }
401 m_stoppedSendingVisualPositions =
true;
402 m_visualPositionUpdateSentCount = 0;
404 else { m_stoppedSendingVisualPositions =
false; }
406 if (!m_serverWantsVisualPositions) {
return; }
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()));
422 if (m_stoppedSendingVisualPositions) { sendQueuedMessage(visualPilotDataUpdate.toStopped()); }
423 else if (m_visualPositionUpdateSentCount++ % 25 == 0) { sendQueuedMessage(visualPilotDataUpdate.toPeriodic()); }
424 else { sendQueuedMessage(visualPilotDataUpdate); }
427 void CFSDClient::sendAtcDataUpdate(
double latitude,
double longitude)
429 const AtcDataUpdate atcDataUpdate(getOwnCallsignAsString(), 199998, CFacilityType::OBS, 300,
430 AtcRating::Observer, latitude, longitude, 0);
431 sendQueuedMessage(atcDataUpdate);
434 void CFSDClient::sendPing(
const QString &receiver)
436 const qint64 msecSinceEpoch = QDateTime::currentMSecsSinceEpoch();
437 const QString timeString = QString::number(msecSinceEpoch);
439 const Ping ping(getOwnCallsignAsString(), receiver, timeString);
440 sendQueuedMessage(ping);
443 increaseStatisticsValue(QStringLiteral(
"sendPing"));
446 void CFSDClient::sendClientQueryIsValidAtc(
const CCallsign &callsign)
448 sendClientQuery(ClientQueryType::IsValidATC, {}, { callsign.
asString() });
451 void CFSDClient::sendClientQueryCapabilities(
const CCallsign &callsign)
453 sendClientQuery(ClientQueryType::Capabilities, callsign);
458 sendClientQuery(ClientQueryType::Com1Freq, callsign);
463 sendClientQuery(ClientQueryType::RealName, callsign);
468 sendClientQuery(ClientQueryType::Server, callsign);
473 sendClientQuery(ClientQueryType::ATIS, callsign);
478 sendClientQuery(ClientQueryType::FP, {}, { callsign.
toQString() });
483 QString data = QJsonDocument(JsonPackets::aircraftConfigRequest()).toJson(QJsonDocument::Compact);
484 data = convertToUnicodeEscaped(data);
485 sendClientQuery(ClientQueryType::AircraftConfig, callsign, { data });
488 void CFSDClient::sendClientQuery(
ClientQueryType queryType,
const CCallsign &receiver,
const QStringList &queryData)
490 if (queryType == ClientQueryType::Unknown) {
return; }
493 QMetaObject::invokeMethod(
this, [=] {
500 if (queryType == ClientQueryType::IsValidATC)
502 const ClientQuery clientQuery(getOwnCallsignAsString(),
"SERVER", ClientQueryType::IsValidATC, queryData);
503 sendQueuedMessage(clientQuery);
505 else if (queryType == ClientQueryType::Capabilities)
507 const ClientQuery clientQuery(getOwnCallsignAsString(), receiverCallsign, ClientQueryType::Capabilities);
508 sendQueuedMessage(clientQuery);
510 else if (queryType == ClientQueryType::Com1Freq)
512 const ClientQuery clientQuery(getOwnCallsignAsString(), receiverCallsign, ClientQueryType::Com1Freq);
513 sendQueuedMessage(clientQuery);
515 else if (queryType == ClientQueryType::RealName)
517 const ClientQuery clientQuery(getOwnCallsignAsString(), receiverCallsign, ClientQueryType::RealName);
518 sendQueuedMessage(clientQuery);
520 else if (queryType == ClientQueryType::Server)
522 ClientQuery clientQuery(getOwnCallsignAsString(), receiverCallsign, ClientQueryType::Server);
523 sendQueuedMessage(clientQuery);
525 else if (queryType == ClientQueryType::ATIS)
527 const ClientQuery clientQuery(getOwnCallsignAsString(), receiverCallsign, ClientQueryType::ATIS);
528 sendQueuedMessage(clientQuery);
529 if (m_serverType != ServerType::Vatsim) { m_pendingAtisQueries.insert(receiver, {}); }
531 else if (queryType == ClientQueryType::PublicIP)
533 const ClientQuery clientQuery(getOwnCallsignAsString(), receiverCallsign, ClientQueryType::PublicIP);
534 sendQueuedMessage(clientQuery);
536 else if (queryType == ClientQueryType::INF)
538 const ClientQuery clientQuery(getOwnCallsignAsString(), receiverCallsign, ClientQueryType::INF);
539 sendQueuedMessage(clientQuery);
541 else if (queryType == ClientQueryType::FP)
543 if (queryData.isEmpty()) {
return; }
544 const ClientQuery clientQuery(getOwnCallsignAsString(),
"SERVER", ClientQueryType::FP, queryData);
545 sendQueuedMessage(clientQuery);
547 else if (queryType == ClientQueryType::AircraftConfig)
549 if (queryData.isEmpty()) {
return; }
550 const ClientQuery clientQuery(getOwnCallsignAsString(), receiverCallsign, ClientQueryType::AircraftConfig,
552 sendQueuedMessage(clientQuery);
554 else if (queryType == ClientQueryType::EuroscopeSimData)
556 const ClientQuery clientQuery(getOwnCallsignAsString(),
"@94835", ClientQueryType::EuroscopeSimData,
558 sendQueuedMessage(clientQuery);
561 increaseStatisticsValue(QStringLiteral(
"sendClientQuery"), toQString(queryType));
566 if (messages.
isEmpty()) {
return; }
569 QMetaObject::invokeMethod(
this, [=] {
576 const QString ownCallsign = getOwnCallsignAsString();
578 for (
const auto &message : privateMessages)
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"));
589 QVector<int> frequencies;
590 for (
const auto &message : radioMessages)
595 message.getFrequency() :
603 frequencies.push_back(freqkHz);
605 increaseStatisticsValue(QStringLiteral(
"sendTextMessages.FREQ"));
612 if (message.
isEmpty()) {
return; }
618 if (message.isEmpty()) {
return; }
621 QMetaObject::invokeMethod(
this, [=] {
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"); }
633 const TextMessage textMessage(getOwnCallsignAsString(), receiver, message);
634 sendQueuedMessage(textMessage);
635 if (receiver == QStringLiteral(
"*S"))
637 const CCallsign sender(getOwnCallsignAsString());
643 increaseStatisticsValue(QStringLiteral(
"sendTextMessages"));
654 QStringList receivers;
655 for (
const int &frequency : frequencieskHz)
657 receivers.push_back(QStringLiteral(
"@%1").arg(frequency - 100000));
660 const TextMessage radioMessage(getOwnCallsignAsString(), receivers.join(
'&'), message);
661 sendQueuedMessage(radioMessage);
662 increaseStatisticsValue(QStringLiteral(
"sendTextMessages"));
669 QMetaObject::invokeMethod(
this, [=] {
678 QString route = flightPlan.
getRoute();
697 Q_ASSERT_X(!act.isEmpty(), Q_FUNC_INFO,
"Aircraft type must not be empty");
701 const FlightPlan fp(getOwnCallsignAsString(),
"SERVER", flightType, act,
707 timePartsEnroute[CTime::Minutes], timePartsFuel[CTime::Hours],
711 sendQueuedMessage(fp);
712 increaseStatisticsValue(QStringLiteral(
"sendFlightPlan"));
719 QMetaObject::invokeMethod(
this, [=] {
726 sendQueuedMessage(planeInfoRequest);
727 increaseStatisticsValue(QStringLiteral(
"sendPlaneInfoRequest"));
734 QMetaObject::invokeMethod(
this, [=] {
741 SWIFT_VERIFY_X(connected, Q_FUNC_INFO,
"Can't send to server when disconnected");
742 if (!connected) {
return; }
745 const QString modelString = this->getConfiguredModelString(myAircraft);
749 sendQueuedMessage(planeInfoRequestFsinn);
750 increaseStatisticsValue(QStringLiteral(
"sendPlaneInfoRequestFsinn"));
753 void CFSDClient::sendPlaneInformation(
const QString &receiver,
const QString &aircraft,
const QString &airline,
754 const QString &livery)
756 const PlaneInformation planeInformation(getOwnCallsignAsString(), receiver, aircraft, airline, livery);
757 sendQueuedMessage(planeInformation);
758 increaseStatisticsValue(QStringLiteral(
"sendPlaneInformation"));
761 void CFSDClient::sendPlaneInformationFsinn(
const CCallsign &callsign)
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"));
773 void CFSDClient::sendAircraftConfiguration(
const QString &receiver,
const QString &aircraftConfigJson)
775 if (aircraftConfigJson.size() == 0) {
return; }
776 const ClientQuery clientQuery(getOwnCallsignAsString(), receiver, ClientQueryType::AircraftConfig,
777 { aircraftConfigJson });
778 sendQueuedMessage(clientQuery);
781 void CFSDClient::sendMessageString(
const QString &message)
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); }
789 emitRawFsdMessage(message.trimmed(),
true);
792 void CFSDClient::sendQueuedMessage()
794 if (m_queuedFsdMessages.isEmpty()) {
return; }
795 const int s = m_queuedFsdMessages.size();
796 this->sendMessageString(m_queuedFsdMessages.dequeue());
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()); }
808 const StatusSeverity severity = s > 75 ? SeverityWarning : SeverityInfo;
809 CLogMessage(
this).
log(severity, u
"Too many queued messages (%1), bulk send!") << s;
811 if (s > 75) { sendNo = 20; }
812 if (s > 100) { sendNo = 30; }
814 for (
int i = 0; i < sendNo; i++) { this->sendMessageString(m_queuedFsdMessages.dequeue()); }
818 void CFSDClient::sendFsdMessage(
const QString &message)
821 parseMessage(message);
824 QString CFSDClient::getConfiguredModelString(
const CSimulatedAircraft &myAircraft)
const
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;
832 QString CFSDClient::getConfiguredLiveryString(
const CSimulatedAircraft &myAircraft)
const
834 if (!m_sendLiveryString) {
return QString(); }
835 QReadLocker l(&m_lockUserClientBuffered);
840 void CFSDClient::sendAuthChallenge(
const QString &challenge)
842 const AuthChallenge pduAuthChallenge(getOwnCallsignAsString(),
"SERVER", challenge);
843 sendDirectMessage(pduAuthChallenge);
844 increaseStatisticsValue(QStringLiteral(
"sendAuthChallenge"));
847 void CFSDClient::sendAuthResponse(
const QString &response)
849 const AuthResponse pduAuthResponse(getOwnCallsignAsString(),
"SERVER", response);
850 sendDirectMessage(pduAuthResponse);
851 increaseStatisticsValue(QStringLiteral(
"sendAuthResponse"));
854 void CFSDClient::sendPong(
const QString &receiver,
const QString ×tamp)
856 const Pong pong(getOwnCallsignAsString(), receiver, timestamp);
857 sendQueuedMessage(pong);
858 increaseStatisticsValue(QStringLiteral(
"sendPong"));
861 void CFSDClient::sendClientResponse(ClientQueryType queryType,
const QString &receiver)
863 if (queryType == ClientQueryType::Unknown) {
return; }
864 if (queryType == ClientQueryType::IsValidATC)
866 this->handleIllegalFsdState(
"Never use sendClientResponse with IsValidATC from the client");
870 increaseStatisticsValue(QStringLiteral(
"sendClientResponse"), toQString(queryType));
872 QStringList responseData;
873 const QString ownCallsign = getOwnCallsignAsString();
875 if (queryType == ClientQueryType::Capabilities)
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);
897 else if (queryType == ClientQueryType::Com1Freq)
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);
905 else if (queryType == ClientQueryType::RealName)
911 responseData.push_back({});
913 if (m_loginMode.
isObserver()) { responseData.push_back(toQString(m_atcRating)); }
914 else { responseData.push_back(toQString(m_pilotRating)); }
916 const ClientResponse pduClientQueryResponse(ownCallsign, receiver, ClientQueryType::RealName, responseData);
917 sendQueuedMessage(pduClientQueryResponse);
919 else if (queryType == ClientQueryType::Server)
921 responseData.push_back(m_server.
getAddress());
922 const ClientResponse pduClientQueryResponse(ownCallsign, receiver, ClientQueryType::Server, responseData);
923 sendQueuedMessage(pduClientQueryResponse);
925 else if (queryType == ClientQueryType::ATIS)
927 this->handleIllegalFsdState(
928 QStringLiteral(
"Dont send '%1' as pilot client!").arg(toQString(ClientQueryType::ATIS)));
930 else if (queryType == ClientQueryType::PublicIP)
932 this->handleIllegalFsdState(
933 QStringLiteral(
"Dont send '%1' as pilot client!").arg(toQString(ClientQueryType::PublicIP)));
935 else if (queryType == ClientQueryType::INF)
941 const double latitude = situation.
latitude().
value(CAngleUnit::deg());
942 const double longitude = situation.
longitude().
value(CAngleUnit::deg());
945 std::array<char, 50> sysuid = {};
946 #ifdef SWIFT_VATSIM_SUPPORT
947 vatsim_get_system_unique_id(sysuid.data());
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;
955 const TextMessage textMessage(ownCallsign, receiver, userInfo);
956 sendQueuedMessage(textMessage);
958 else if (queryType == ClientQueryType::FP)
960 this->handleIllegalFsdState(
961 QStringLiteral(
"Dont send '%1' as pilot client!").arg(toQString(ClientQueryType::FP)));
963 else if (queryType == ClientQueryType::AircraftConfig)
965 this->handleIllegalFsdState(
966 QStringLiteral(
"Dont send '%1' as pilot client!").arg(toQString(ClientQueryType::AircraftConfig)));
970 #ifdef SWIFT_VATSIM_SUPPORT
971 void CFSDClient::sendClientIdentification(
const QString &fsdChallenge)
973 std::array<char, 50> sysuid = {};
974 vatsim_get_system_unique_id(sysuid.data());
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);
981 if (
getServer().getEcosystem().isSystem(CEcosystem::VATSIM))
984 { this, [this](const QString &token) {
985 this->sendLogin(token);
986 this->updateConnectionStatus(CConnectionStatus::Connected);
992 this->updateConnectionStatus(CConnectionStatus::Connected);
994 increaseStatisticsValue(QStringLiteral(
"sendClientIdentification"));
998 void CFSDClient::getVatsimAuthToken(
const QString &cid,
const QString &password,
1001 Q_ASSERT_X(
sApp, Q_FUNC_INFO,
"Need app");
1003 nwRequest.setHeader(QNetworkRequest::ContentTypeHeader,
"application/json");
1004 const QJsonObject jsonRequest { {
"cid", cid }, {
"password", password } };
1007 {
this, [=](QNetworkReply *nwReply) {
1008 const QByteArray data = nwReply->readAll();
1009 const QJsonObject json = QJsonDocument::fromJson(data).object();
1011 if (json.value(
"success").toBool()) { callback(json.value(
"token").toString()); }
1014 const QString error = json.value(
"error_msg").isString() ?
1015 json.value(
"error_msg").toString() :
1016 nwReply->errorString();
1018 disconnectFromServer();
1020 nwReply->deleteLater();
1024 void CFSDClient::sendIncrementalAircraftConfig()
1026 if (!m_unitTestMode && (!this->isConnected() || !this->getSetupForServer().sendAircraftParts())) {
return; }
1030 if (m_sentAircraftConfig == currentParts) {
return; }
1032 if (!m_tokenBucket.tryConsume()) {
return; }
1034 const QJsonObject previousConfig = m_sentAircraftConfig.toJson();
1035 const QJsonObject currentConfig = currentParts.toJson();
1036 const QJsonObject incrementalConfig = getIncrementalObject(previousConfig, currentConfig);
1038 const QString dataStr = convertToUnicodeEscaped(
1039 QJsonDocument(QJsonObject { {
"config", incrementalConfig } }).toJson(QJsonDocument::Compact));
1041 sendAircraftConfiguration(
"@94836", dataStr);
1042 m_sentAircraftConfig = currentParts;
1045 void CFSDClient::initializeMessageTypes()
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;
1076 m_messageTypeMapping[
"SIMDATA"] = MessageType::EuroscopeSimData;
1080 m_messageTypeMapping[
"!R"] = MessageType::RegistrationInfo;
1081 m_messageTypeMapping[
"-MD"] = MessageType::RevBClientParts;
1082 m_messageTypeMapping[
"-PD"] = MessageType::RevBPilotDescription;
1089 void CFSDClient::handleAtcDataUpdate(
const QStringList &tokens)
1091 const AtcDataUpdate atcDataUpdate = AtcDataUpdate::fromTokens(tokens);
1092 const QString senderCs = atcDataUpdate.sender();
1093 const CCallsign cs(senderCs, CCallsign::Atc);
1096 if (atcDataUpdate.m_facility == CFacilityType::Unknown && !cs.isObserverCallsign())
1100 if (atcDataUpdate.m_facility == CFacilityType::OBS && !cs.hasSuffix()) {
return; }
1102 CFrequency freq(atcDataUpdate.m_frequencykHz, CFrequencyUnit::kHz());
1103 freq.switchUnit(CFrequencyUnit::MHz());
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);
1113 emit this->atcDataUpdateReceived(cs, freq, position, range);
1115 m_atcStations.replaceOrAddObjectByCallsign({ cs, {}, freq, position, range });
1118 #ifdef SWIFT_VATSIM_SUPPORT
1119 void CFSDClient::handleAuthChallenge(
const QStringList &tokens)
1121 const AuthChallenge authChallenge = AuthChallenge::fromTokens(tokens);
1123 vatsim_auth_generate_response(m_clientAuth, qPrintable(authChallenge.m_challengeKey), response);
1124 sendAuthResponse(QString(response));
1127 vatsim_auth_generate_challenge(m_serverAuth, challenge);
1128 m_lastServerAuthChallenge = QString(challenge);
1129 sendAuthChallenge(m_lastServerAuthChallenge);
1133 #ifdef SWIFT_VATSIM_SUPPORT
1134 void CFSDClient::handleAuthResponse(
const QStringList &tokens)
1136 const AuthResponse authResponse = AuthResponse::fromTokens(tokens);
1138 char expectedResponse[33];
1139 vatsim_auth_generate_response(m_serverAuth, qPrintable(m_lastServerAuthChallenge), expectedResponse);
1140 if (authResponse.m_response != QString(expectedResponse))
1142 CLogMessage().
error(u
"The server you are connected to is not a VATSIM server. Disconnecting!");
1143 disconnectFromServer();
1148 void CFSDClient::handleDeleteATC(
const QStringList &tokens)
1150 const DeleteAtc deleteAtc = DeleteAtc::fromTokens(tokens);
1151 emit deleteAtcReceived(deleteAtc.m_cid);
1153 m_atcStations.removeByCallsign(deleteAtc.m_cid);
1156 void CFSDClient::handleDeletePilot(
const QStringList &tokens)
1158 const DeletePilot deletePilot = DeletePilot::fromTokens(tokens);
1159 const CCallsign cs(deletePilot.sender(), CCallsign::Aircraft);
1161 emit deletePilotReceived(deletePilot.m_cid);
1164 void CFSDClient::handleTextMessage(
const QStringList &tokens)
1166 const TextMessage textMessage = TextMessage::fromTokens(tokens);
1168 const CCallsign sender(textMessage.sender());
1169 const CCallsign receiver(textMessage.receiver());
1171 if (textMessage.m_type == TextMessage::PrivateMessage)
1177 if (m_server.getServerType() != CServer::FSDServerVatsim &&
1178 m_ownCallsign.asString() == textMessage.receiver() && m_pendingAtisQueries.contains(sender))
1180 maybeHandleAtisReply(sender, receiver, textMessage.m_message);
1184 CTextMessage tm(textMessage.m_message, sender, receiver);
1185 tm.setCurrentUtcTime();
1186 this->consolidateTextMessage(tm);
1188 else if (textMessage.m_type == TextMessage::RadioMessage)
1190 const CFrequency com1 = getOwnAircraft().getCom1System().getFrequencyActive();
1191 const CFrequency com2 = getOwnAircraft().getCom2System().getFrequencyActive();
1192 QList<CFrequency> frequencies;
1194 for (
int freqKhz : textMessage.m_frequencies)
1196 CFrequency f(freqKhz, CFrequencyUnit::kHz());
1197 CComSystem::roundToChannelSpacing(f, CComSystem::ChannelSpacing8_33KHz);
1198 if (f == com1 || f == com2) { frequencies.push_back(f); }
1200 if (frequencies.isEmpty()) {
return; }
1202 messages.setCurrentUtcTime();
1203 emit textMessagesReceived(messages);
1207 void CFSDClient::handlePilotDataUpdate(
const QStringList &tokens)
1209 const PilotDataUpdate dataUpdate = PilotDataUpdate::fromTokens(tokens);
1210 const CCallsign callsign(dataUpdate.sender(), CCallsign::Aircraft);
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()));
1218 CAltitude::PressureAltitude, CLengthUnit::ft()));
1220 const COnGroundInfo og(dataUpdate.m_onGround ? COnGroundInfo::OnGround : COnGroundInfo::NotOnGround,
1221 COnGroundInfo::NotSetGroundDetails);
1226 const qint64 offsetTimeMs =
1233 if (CTransponder::isValidTransponderCode(dataUpdate.m_transponderCode))
1235 transponder =
CTransponder(dataUpdate.m_transponderCode, dataUpdate.m_transponderMode);
1239 if (CBuildConfig::isLocalDeveloperDebugBuild())
1242 << dataUpdate.m_transponderCode << callsign;
1246 transponder =
CTransponder(2000, CTransponder::StateStandby);
1248 emit pilotDataUpdateReceived(situation, transponder);
1251 void CFSDClient::handleEuroscopeSimData(
const QStringList &tokens)
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()));
1261 const COnGroundInfo og(data.m_onGround ? COnGroundInfo::OnGround : COnGroundInfo::NotOnGround,
1262 COnGroundInfo::NotSetGroundDetails);
1267 const qint64 offsetTimeMs =
1276 emit euroscopeSimDataUpdatedReceived(situation, parts, currentOffsetTime(data.sender()), data.m_model,
1280 void CFSDClient::handleVisualPilotDataUpdate(
const QStringList & ,
MessageType )
1283 VisualPilotDataUpdate dataUpdate;
1284 switch (messageType)
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;
1291 const CCallsign callsign(dataUpdate.sender(), CCallsign::Aircraft);
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()));
1310 emit visualPilotDataUpdateReceived(situation);
1314 void CFSDClient::handleVisualPilotDataToggle(
const QStringList &tokens)
1316 const VisualPilotDataToggle toggle = VisualPilotDataToggle::fromTokens(tokens);
1317 m_serverWantsVisualPositions = toggle.m_active;
1320 void CFSDClient::handlePing(
const QStringList &tokens)
1322 const Ping ping = Ping::fromTokens(tokens);
1323 sendPong(ping.sender(), ping.m_timestamp);
1326 void CFSDClient::handlePong(
const QStringList &tokens)
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);
1334 void CFSDClient::handleKillRequest(
const QStringList &tokens)
1336 KillRequest killRequest = KillRequest::fromTokens(tokens);
1337 emit killRequestReceived(killRequest.m_reason);
1338 disconnectFromServer();
1341 void CFSDClient::handleFlightPlan(
const QStringList &tokens)
1343 FlightPlan fp = FlightPlan::fromTokens(tokens);
1346 switch (fp.m_flightType)
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;
1354 QString cruiseAltString = fp.m_cruiseAlt.trimmed();
1355 if (!cruiseAltString.isEmpty() &&
is09OnlyString(cruiseAltString))
1357 int ca = cruiseAltString.toInt();
1361 if (rules == CFlightPlan::IFR)
1363 if (ca >= 1000) { cruiseAltString = u
"FL" % QString::number(ca / 100); }
1364 else { cruiseAltString = u
"FL" % cruiseAltString; }
1368 if (ca >= 5000) { cruiseAltString = u
"FL" % QString::number(ca / 100); }
1369 else { cruiseAltString = cruiseAltString % u
"ft"; }
1373 cruiseAlt.
parseFromString(cruiseAltString, CPqString::SeparatorBestGuess);
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);
1378 const CCallsign callsign(fp.sender(), CCallsign::Aircraft);
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);
1386 emit flightPlanReceived(callsign, flightPlan);
1389 void CFSDClient::handleClientQuery(
const QStringList &tokens)
1391 const ClientQuery clientQuery = ClientQuery::fromTokens(tokens);
1393 if (clientQuery.m_queryType == ClientQueryType::Unknown) {
return; }
1394 if (clientQuery.m_queryType == ClientQueryType::IsValidATC)
1398 else if (clientQuery.m_queryType == ClientQueryType::Capabilities)
1400 sendClientResponse(ClientQueryType::Capabilities, clientQuery.sender());
1402 else if (clientQuery.m_queryType == ClientQueryType::Com1Freq)
1404 sendClientResponse(ClientQueryType::Com1Freq, clientQuery.sender());
1406 else if (clientQuery.m_queryType == ClientQueryType::RealName)
1408 sendClientResponse(ClientQueryType::RealName, clientQuery.sender());
1410 else if (clientQuery.m_queryType == ClientQueryType::Server)
1412 sendClientResponse(ClientQueryType::Server, clientQuery.sender());
1414 else if (clientQuery.m_queryType == ClientQueryType::ATIS)
1418 else if (clientQuery.m_queryType == ClientQueryType::PublicIP)
1422 else if (clientQuery.m_queryType == ClientQueryType::INF)
1424 sendClientResponse(ClientQueryType::INF, clientQuery.sender());
1426 else if (clientQuery.m_queryType == ClientQueryType::FP)
1430 else if (clientQuery.m_queryType == ClientQueryType::AircraftConfig)
1432 QStringList aircraftConfigTokens = tokens.mid(3);
1433 QString aircraftConfigJson = aircraftConfigTokens.join(
":");
1435 const CCallsign callsign(clientQuery.sender(), CCallsign::Aircraft);
1437 QJsonParseError parserError;
1438 const QByteArray json = aircraftConfigJson.toUtf8();
1439 const QJsonDocument doc = QJsonDocument::fromJson(json, &parserError);
1441 if (parserError.error != QJsonParseError::NoError)
1443 CLogMessage(
this).
warning(u
"Failed to parse aircraft config packet: '%1' packet: '%2'")
1444 << parserError.errorString() << QString(json);
1448 const QJsonObject packet = doc.object();
1449 if (packet == JsonPackets::aircraftConfigRequest())
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);
1461 const bool inRange = isAircraftInRange(callsign);
1462 if (!inRange) {
return; }
1463 if (!getSetupForServer().receiveAircraftParts()) {
return; }
1464 const QJsonObject config = doc.object().value(
"config").toObject();
1465 if (config.isEmpty()) {
return; }
1467 const qint64 offsetTimeMs = currentOffsetTime(callsign);
1468 emit aircraftConfigReceived(clientQuery.sender(), config, offsetTimeMs);
1472 void CFSDClient::handleClientResponse(
const QStringList &tokens)
1474 const ClientResponse clientResponse = ClientResponse::fromTokens(tokens);
1475 if (clientResponse.isUnknownQuery()) {
return; }
1476 const QString sender = clientResponse.sender();
1478 QString responseData1;
1479 QString responseData2;
1480 if (clientResponse.m_responseData.size() > 0) { responseData1 = clientResponse.m_responseData.at(0); }
1482 if (clientResponse.m_responseData.size() > 1) { responseData2 = clientResponse.m_responseData.at(1); }
1484 if (clientResponse.m_queryType == ClientQueryType::IsValidATC)
1486 emit validAtcResponseReceived(responseData2, responseData1 == u
"Y");
1488 else if (clientResponse.m_queryType == ClientQueryType::Capabilities)
1491 for (
int i = 0; i < clientResponse.m_responseData.size(); ++i)
1493 const QString keyValuePair = clientResponse.m_responseData.at(i);
1494 if (keyValuePair.count(
'=') != 1) {
continue; }
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);
1501 if (value ==
"1") { capabilities |= fromQString<Capabilities>(key); }
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; }
1511 emit capabilityResponseReceived(clientResponse.sender(), caps);
1513 else if (clientResponse.m_queryType == ClientQueryType::Com1Freq)
1515 if (responseData1.isEmpty()) {
return; }
1517 const double freqMHz = responseData1.toDouble(&ok);
1518 if (!ok) {
return; }
1519 emit com1FrequencyResponseReceived(clientResponse.sender(),
CFrequency(freqMHz, CFrequencyUnit::MHz()));
1521 else if (clientResponse.m_queryType == ClientQueryType::RealName)
1524 emit realNameResponseReceived(clientResponse.sender(), responseData1);
1526 else if (clientResponse.m_queryType == ClientQueryType::Server)
1528 emit serverResponseReceived(clientResponse.sender(), responseData1);
1530 else if (clientResponse.m_queryType == ClientQueryType::ATIS)
1532 if (responseData1.isEmpty())
1537 updateAtisMap(clientResponse.sender(), fromQString<AtisLineType>(responseData1), responseData2);
1539 else if (clientResponse.m_queryType == ClientQueryType::PublicIP)
1543 else if (clientResponse.m_queryType == ClientQueryType::INF)
1547 else if (clientResponse.m_queryType == ClientQueryType::FP)
1551 else if (clientResponse.m_queryType == ClientQueryType::AircraftConfig)
1557 void CFSDClient::handleServerError(
const QStringList &tokens)
1559 const ServerError serverError = ServerError::fromTokens(tokens);
1560 switch (serverError.m_errorNumber)
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:
1567 case ServerErrorCode::InvalidRevision:
1568 CLogMessage(
this).
error(u
"This server does not support our protocol version");
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");
1575 case ServerErrorCode::InvalidClient:
1576 CLogMessage(
this).
error(u
"This software is not authorized for use on this network");
1578 case ServerErrorCode::RequestedLevelTooHigh:
1579 CLogMessage(
this).
error(u
"You are not authorized to use the requested pilot rating");
1583 case ServerErrorCode::SyntaxError:
1585 u
"Malformed packet, syntax error: '%1'. This can also occur if an OBS sends frequency text messages.")
1586 << serverError.getCausingParameter();
1588 case ServerErrorCode::InvalidSrcCallsign:
1589 CLogMessage(
this).
info(u
"FSD message was using an invalid callsign: %1 (%2)")
1590 << serverError.getCausingParameter() << serverError.getDescription();
1592 case ServerErrorCode::NoSuchCallsign:
1594 << serverError.getCausingParameter() << serverError.getDescription();
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");
1602 case ServerErrorCode::AlreadyRegistered:
1603 CLogMessage(
this).
warning(u
"Server says already registered: %1") << serverError.getDescription();
1605 case ServerErrorCode::InvalidCtrl:
1606 CLogMessage(
this).
warning(u
"Server invalid control: %1") << serverError.getDescription();
1608 case ServerErrorCode::Unknown:
1610 << serverError.getCausingParameter() << serverError.getDescription();
1612 case ServerErrorCode::AuthTimeout:
CLogMessage(
this).
warning(u
"Client did not authenticate in time");
break;
1614 if (serverError.isFatalError()) { disconnectFromServer(); }
1617 void CFSDClient::handleRevBClientPartsPacket(
const QStringList &tokens)
1621 const RevBClientParts RevBClientParts = RevBClientParts::fromTokens(tokens);
1622 const CCallsign callsign(RevBClientParts.sender(), CCallsign::Aircraft);
1624 const bool inRange = isAircraftInRange(callsign);
1626 if (!inRange) {
return; }
1627 if (!getSetupForServer().receiveAircraftParts()) {
return; }
1629 const qint64 offsetTimeMs = currentOffsetTime(callsign);
1630 emit revbAircraftConfigReceived(RevBClientParts.sender(), RevBClientParts.m_partsval1, offsetTimeMs);
1634 void CFSDClient::handleRehost(
const QStringList &tokens)
1636 const Rehost rehost = Rehost::fromTokens(tokens);
1638 CLogMessage(
this).
info(u
"Server requested we switch server to %1") << rehost.m_hostname;
1640 SWIFT_AUDIT_X(!m_rehosting, Q_FUNC_INFO,
"Rehosting already in progress");
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();
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)
1659 updateConnectionStatus(CConnectionStatus::Disconnected);
1663 initiateConnection(rehostingSocket, rehost.m_hostname);
1666 void CFSDClient::handleMute(
const QStringList &tokens)
1668 const Mute mute = Mute::fromTokens(tokens);
1669 if (mute.receiver() != m_ownCallsign.asString()) {
return; }
1670 emit muteRequestReceived(mute.m_mute);
1673 void CFSDClient::initiateConnection(std::shared_ptr<QTcpSocket> rehostingSocket,
const QString &rehostingHost)
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());
1680 resolveLoadBalancing(host, [=](
const QString &host) {
1681 socket->connectToHost(host, port);
1682 if (!rehostingSocket) { this->startPositionTimers(); }
1686 void CFSDClient::resolveLoadBalancing(
const QString &host, std::function<
void(
const QString &)> callback)
1688 if (QHostAddress(host).isNull() && (getServer().getName() ==
"AUTOMATIC" || m_rehosting) &&
1689 getServer().getEcosystem() == CEcosystem::VATSIM)
1692 Q_ASSERT_X(
sApp, Q_FUNC_INFO,
"Need app");
1695 QScopedPointer<QNetworkReply, QScopedPointerDeleteLater> nwReply(nwReplyPtr);
1697 if (nwReply->error() == QNetworkReply::NoError)
1699 QHostAddress addr(
static_cast<QString
>(nwReply->readAll()));
1702 callback(addr.toString());
1709 else { callback(host); }
1712 void CFSDClient::handleCustomPilotPacket(
const QStringList &tokens)
1714 const QString subType = tokens.at(2);
1716 if (subType == u
"PIR")
1718 PlaneInfoRequest planeInfoRequest = PlaneInfoRequest::fromTokens(tokens);
1721 const QString airlineIcao = m_server.getFsdSetup().force3LetterAirlineCodes() ?
1725 const QString livery = this->getConfiguredLiveryString(myAircraft);
1727 sendPlaneInformation(planeInfoRequest.sender(), acTypeICAO, airlineIcao, livery);
1729 else if (subType ==
"PI")
1731 if (tokens.size() > 6 && tokens.at(3) ==
"X")
1735 else if (tokens.size() > 4 && tokens.at(3) ==
"GEN")
1737 const PlaneInformation planeInformation = PlaneInformation::fromTokens(tokens);
1738 emit planeInformationReceived(planeInformation.sender(), planeInformation.m_aircraft,
1739 planeInformation.m_airline, planeInformation.m_livery);
1742 else if (subType ==
"I")
1746 else if (subType ==
"VI")
1749 if (!isInterimPositionReceivingEnabledForServer()) {
return; }
1751 const InterimPilotDataUpdate interimPilotDataUpdate = InterimPilotDataUpdate::fromTokens(tokens);
1752 const CCallsign callsign(interimPilotDataUpdate.sender(), CCallsign::Aircraft);
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()));
1763 const COnGroundInfo og(interimPilotDataUpdate.m_onGround ? COnGroundInfo::OnGround :
1764 COnGroundInfo::NotOnGround,
1765 COnGroundInfo::NotSetGroundDetails);
1770 const qint64 offsetTimeMs =
1774 emit interimPilotDataUpdatedReceived(situation);
1776 else if (subType ==
"FSIPI")
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);
1784 else if (subType ==
"FSIPIR")
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);
1796 const QString sender = tokens.at(0);
1797 const QStringList data = tokens.mid(3);
1798 emit customPilotPacketReceived(sender, data);
1802 #ifdef SWIFT_VATSIM_SUPPORT
1803 void CFSDClient::handleFsdIdentification(
const QStringList &tokens)
1807 const FSDIdentification fsdIdentification = FSDIdentification::fromTokens(tokens);
1808 vatsim_auth_set_initial_challenge(m_clientAuth, qPrintable(fsdIdentification.m_initialChallenge));
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));
1818 u
"You tried to connect to a VATSIM server without using VATSIM protocol, disconnecting!");
1819 disconnectFromServer();
1824 void CFSDClient::handleUnknownPacket(
const QString &line)
1829 void CFSDClient::handleUnknownPacket(
const QStringList &tokens) { this->handleUnknownPacket(tokens.join(
", ")); }
1831 void CFSDClient::printSocketError(QAbstractSocket::SocketError socketError)
1833 if (m_rehosting) {
return; }
1835 CLogMessage(
this).
error(u
"FSD socket error: %1") << this->socketErrorString(socketError);
1838 void CFSDClient::handleSocketError(QAbstractSocket::SocketError socketError)
1840 if (m_rehosting) {
return; }
1842 const QString error = this->socketErrorString(socketError);
1843 switch (socketError)
1846 case QAbstractSocket::RemoteHostClosedError:
1847 emit this->severeNetworkError(error);
1848 this->disconnectFromServer();
1854 void CFSDClient::handleSocketConnected()
1859 this->updateConnectionStatus(CConnectionStatus::Connected);
1865 if (this->getConnectionStatus() == newStatus) {
return; }
1870 QWriteLocker l(&m_lockUserClientBuffered);
1871 m_server.setConnectedSinceNow();
1872 ecoSystem = m_server.getEcosystem();
1874 this->setCurrentEcosystem(ecoSystem);
1878 QWriteLocker l(&m_lockUserClientBuffered);
1879 m_server.markAsDisconnected();
1884 this->stopPositionTimers();
1886 this->setLastEcosystem(m_server.getEcosystem());
1887 this->setCurrentEcosystem(CEcosystem::NoSystem);
1888 this->saveNetworkStatistics(m_server.getName());
1893 QWriteLocker l(&m_lockConnectionStatus);
1894 oldStatus = m_connectionStatus;
1895 m_connectionStatus = newStatus;
1898 emit this->connectionStatusChanged(oldStatus, newStatus);
1901 void CFSDClient::consolidateTextMessage(
const CTextMessage &textMessage)
1906 m_textMessagesToConsolidate.addConsolidatedTextMessage(textMessage);
1907 m_dsSendTextMessage.inputSignal();
1911 void CFSDClient::emitConsolidatedTextMessages()
1913 emit this->textMessagesReceived(m_textMessagesToConsolidate);
1914 m_textMessagesToConsolidate.clear();
1917 qint64 CFSDClient::receivedPositionFixTsAndGetOffsetTime(
const CCallsign &callsign, qint64 markerTs)
1919 Q_ASSERT_X(!callsign.
isEmpty(), Q_FUNC_INFO,
"Need callsign");
1921 if (markerTs < 0) { markerTs = QDateTime::currentMSecsSinceEpoch(); }
1922 if (!m_lastPositionUpdate.contains(callsign))
1924 m_lastPositionUpdate.insert(callsign, markerTs);
1925 return CFsdSetup::c_positionTimeOffsetMsec;
1927 const qint64 oldTs = m_lastPositionUpdate.value(callsign);
1928 m_lastPositionUpdate[callsign] = markerTs;
1931 const qint64 diff = qAbs(markerTs - oldTs);
1932 this->insertLatestOffsetTime(callsign, diff);
1935 const qint64 avgTimeMs = this->averageOffsetTimeMs(callsign, count, 3);
1936 qint64 offsetTime = CFsdSetup::c_positionTimeOffsetMsec;
1938 if (avgTimeMs < CFsdSetup::c_interimPositionTimeOffsetMsec && count >= 3)
1940 offsetTime = CFsdSetup::c_interimPositionTimeOffsetMsec;
1943 return m_additionalOffsetTime + offsetTime;
1946 qint64 CFSDClient::currentOffsetTime(
const CCallsign &callsign)
const
1948 Q_ASSERT_X(!callsign.
isEmpty(), Q_FUNC_INFO,
"Need callsign");
1950 if (!m_lastOffsetTimes.contains(callsign) || m_lastOffsetTimes[callsign].isEmpty())
1952 return CFsdSetup::c_positionTimeOffsetMsec;
1954 return m_lastOffsetTimes[callsign].front();
1957 void CFSDClient::clearState()
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();
1973 void CFSDClient::clearState(
const CCallsign &callsign)
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);
1982 void CFSDClient::insertLatestOffsetTime(
const CCallsign &callsign, qint64 offsetMs)
1984 QList<qint64> &offsets = m_lastOffsetTimes[callsign];
1985 offsets.push_front(offsetMs);
1986 if (offsets.size() > c_maxOffsetTimes) { offsets.removeLast(); }
1989 qint64 CFSDClient::averageOffsetTimeMs(
const CCallsign &callsign,
int &count,
int maxLastValues)
const
1991 const QList<qint64> &offsets = m_lastOffsetTimes[callsign];
1992 if (offsets.size() < 1) {
return -1; }
1995 for (qint64 v : offsets)
1999 if (count > maxLastValues) {
break; }
2001 return qRound(
static_cast<double>(sum) / count);
2004 qint64 CFSDClient::averageOffsetTimeMs(
const CCallsign &callsign,
int maxLastValues)
const
2007 return this->averageOffsetTimeMs(callsign, maxLastValues, count);
2010 bool CFSDClient::isInterimPositionSendingEnabledForServer()
const
2012 const CFsdSetup::SendReceiveDetails d = this->getSetupForServer().getSendReceiveDetails();
2013 return (d & CFsdSetup::SendInterimPositions);
2016 bool CFSDClient::isInterimPositionReceivingEnabledForServer()
const
2018 const CFsdSetup::SendReceiveDetails d = this->getSetupForServer().getSendReceiveDetails();
2019 return (d & CFsdSetup::ReceiveInterimPositions);
2022 bool CFSDClient::isVisualPositionSendingEnabledForServer()
const
2024 const CFsdSetup::SendReceiveDetails d = this->getSetupForServer().getSendReceiveDetails();
2025 return (d & CFsdSetup::SendVisualPositions);
2028 const CFsdSetup &CFSDClient::getSetupForServer()
const {
return m_server.getFsdSetup(); }
2030 void CFSDClient::maybeHandleAtisReply(
const CCallsign &sender,
const CCallsign &receiver,
const QString &message)
2032 Q_ASSERT(m_pendingAtisQueries.contains(sender));
2033 PendingAtisQuery &pendingQuery = m_pendingAtisQueries[sender];
2034 pendingQuery.m_atisMessage.push_back(message);
2037 if (pendingQuery.m_queryTime.secsTo(QDateTime::currentDateTimeUtc()) > 5)
2039 const QString atisMessage(pendingQuery.m_atisMessage.join(QChar::LineFeed));
2041 tm.setCurrentUtcTime();
2042 this->consolidateTextMessage(tm);
2043 m_pendingAtisQueries.remove(sender);
2050 thread_local
const QRegularExpression reLogoff(
"^\\d{0,4}z$");
2051 if (reLogoff.match(message).hasMatch())
2053 emit atisLogoffTimeReplyReceived(sender, message);
2055 for (
const auto &line : std::as_const(pendingQuery.m_atisMessage))
2057 if (!atisMessage.isEmpty()) atisMessage.appendMessage(
"\n");
2058 atisMessage.appendMessage(line);
2061 m_pendingAtisQueries.remove(sender);
2066 void CFSDClient::fsdMessageSettingsChanged()
2068 if (m_rawFsdMessageLogFile.isOpen()) { m_rawFsdMessageLogFile.close(); }
2075 const QString filePath = CFileUtils::appendFilePaths(setting.
getFileDir(),
"rawfsdmessages.log");
2076 m_rawFsdMessageLogFile.setFileName(filePath);
2077 m_rawFsdMessageLogFile.open(QIODevice::Text | QIODevice::WriteOnly);
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);
2085 else if (setting.
getFileWriteMode() == CRawFsdMessageSettings::Timestamped)
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);
2099 return m_interimPositionReceivers;
2104 m_interimPositionReceivers = interimPositionReceivers;
2107 bool CFSDClient::isPendingConnection()
const
2109 return m_connectionStatus.isConnecting() || m_connectionStatus.isDisconnecting();
2112 int CFSDClient::increaseStatisticsValue(
const QString &identifier,
const QString &appendix)
2114 if (identifier.isEmpty() || !m_statistics) {
return -1; }
2116 QWriteLocker l(&m_lockStatistics);
2117 const QString i = appendix.isEmpty() ? identifier : identifier % u
"." % appendix;
2118 int &v = m_callStatistics[i];
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(); }
2127 int CFSDClient::increaseStatisticsValue(
const QString &identifier,
int value)
2129 return increaseStatisticsValue(identifier, QString::number(value));
2132 void CFSDClient::clearStatistics()
2134 QWriteLocker l(&m_lockStatistics);
2135 m_callStatistics.clear();
2136 m_callByTime.clear();
2139 QString CFSDClient::getNetworkStatisticsAsText(
bool reset,
const QString &separator)
2141 QVector<std::pair<int, QString>> transformed;
2143 QVector<QPair<qint64, QString>> callByTime;
2146 QReadLocker l(&m_lockStatistics);
2147 callStatistics = m_callStatistics;
2148 callByTime = m_callByTime;
2151 if (callStatistics.isEmpty()) {
return QString(); }
2152 for (
const auto [key, value] :
makePairsRange(std::as_const(callStatistics)))
2155 transformed.push_back({ value, key });
2159 std::sort(transformed.begin(), transformed.end(), std::greater<>());
2161 for (
const auto &pair : transformed)
2163 stats += (stats.isEmpty() ? QString() : separator) % pair.second % u
": " % QString::number(pair.first);
2166 for (
const auto &pair : transformed)
2168 stats += (stats.isEmpty() ? QString() : separator) % pair.second % u
": " % QString::number(pair.first);
2171 if (!callByTime.isEmpty())
2173 const qint64 lastTs = callByTime.front().first;
2174 for (
const auto &pair : std::as_const(callByTime))
2176 const qint64 deltaTs = lastTs - pair.first;
2177 stats += separator % QStringLiteral(
"%1").arg(deltaTs, 5, 10, QChar(
'0')) % u
": " % pair.second;
2181 if (reset) { this->clearStatistics(); }
2185 void CFSDClient::gracefulShutdown()
2187 disconnectFromServer();
2191 void CFSDClient::readDataFromSocketMaxLines(
int maxLines)
2193 if (m_socket->bytesAvailable() < 1) {
return; }
2198 while (m_socket->canReadLine())
2200 const QByteArray dataEncoded = m_socket->readLine();
2201 if (dataEncoded.isEmpty()) {
continue; }
2202 const QString data = m_fsdTextCodec->toUnicode(dataEncoded);
2203 this->parseMessage(data);
2206 static constexpr
int MaxLines = 75 - 1;
2207 if (maxLines < 0) { maxLines = MaxLines; }
2209 if (lines > maxLines)
2211 static constexpr
int DelayMs = 10;
2212 const int newMax = qRound(1.2 * lines);
2214 CLogMessage(
this).
debug(u
"ReadDataFromSocket has too many lines (>%1), will read again in %2ms")
2215 << MaxLines << DelayMs;
2216 QPointer<CFSDClient> myself(
this);
2219 if (myself) { myself->readDataFromSocketMaxLines(newMax); }
2226 QString CFSDClient::socketErrorString(QAbstractSocket::SocketError error)
const
2228 QString e = CFSDClient::socketErrorToQString(error);
2229 if (!m_socket->errorString().isEmpty()) { e += QStringLiteral(
": ") % m_socket->errorString(); }
2233 QString CFSDClient::socketErrorToQString(QAbstractSocket::SocketError error)
2235 static const QMetaEnum metaEnum = QMetaEnum::fromType<QAbstractSocket::SocketError>();
2236 return metaEnum.valueToKey(error);
2239 void CFSDClient::parseMessage(
const QString &lineRaw)
2243 const QString line = lineRaw.trimmed();
2245 if (m_printToConsole) { qDebug() <<
"FSD Recv=>" << line; }
2246 emitRawFsdMessage(line,
false);
2248 for (
const QString &str :
makeKeysRange(std::as_const(m_messageTypeMapping)))
2250 if (line.startsWith(str))
2253 messageType = m_messageTypeMapping.value(str, MessageType::Unknown);
2261 increaseStatisticsValue(QStringLiteral(
"parseMessage"), this->messageTypeToString(messageType));
2264 if (messageType != MessageType::Unknown)
2267 const QString payload = line.mid(cmd.size()).trimmed();
2270 if (payload.length() == 0) {
return; }
2272 const QStringList tokens = payload.split(
':');
2273 switch (messageType)
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;
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;
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;
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;
2316 case MessageType::Unknown: handleUnknownPacket(tokens);
break;
2319 else { handleUnknownPacket(line); }
2322 void CFSDClient::emitRawFsdMessage(
const QString &fsdMessage,
bool isSent)
2324 if (!m_unitTestMode && !m_rawFsdMessagesEnabled) {
return; }
2325 QString fsdMessageFiltered(fsdMessage);
2326 if (m_filterPasswordFromLogin)
2328 if (fsdMessageFiltered.startsWith(
"#AP"))
2330 thread_local
const QRegularExpression re(
"^(#AP\\w+:SERVER:\\d+:)[^:]+(:\\d+:\\d+:\\d+:.+)$");
2331 fsdMessageFiltered.replace(re,
"\\1<password>\\2");
2332 m_filterPasswordFromLogin =
false;
2336 const QString prefix = isSent ?
"FSD Sent=>" :
"FSD Recv=>";
2338 rawMessage.setCurrentUtcTime();
2339 if (m_rawFsdMessageLogFile.isOpen())
2341 QTextStream stream(&m_rawFsdMessageLogFile);
2342 stream << rawMessage.toQString().trimmed() << Qt::endl;
2344 emit rawFsdMessage(rawMessage);
2347 bool CFSDClient::saveNetworkStatistics(
const QString &server)
2349 if (m_callStatistics.isEmpty()) {
return false; }
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);
2359 void CFSDClient::startPositionTimers()
2361 m_positionUpdateTimer.start(c_updatePositionIntervalMsec);
2362 m_scheduledConfigUpdate.start(c_processingIntervalMsec);
2363 m_fsdSendMessageTimer.start(c_sendFsdMsgIntervalMsec);
2364 m_queuedFsdMessages.clear();
2367 if (this->isInterimPositionSendingEnabledForServer())
2369 m_interimPositionUpdateTimer.start(c_updateInterimPositionIntervalMsec);
2371 else { m_interimPositionUpdateTimer.stop(); }
2372 if (this->isVisualPositionSendingEnabledForServer())
2374 m_visualPositionUpdateTimer.start(c_updateVisualPositionIntervalMsec);
2376 else { m_visualPositionUpdateTimer.stop(); }
2379 void CFSDClient::stopPositionTimers()
2381 m_positionUpdateTimer.stop();
2382 m_interimPositionUpdateTimer.stop();
2383 m_visualPositionUpdateTimer.stop();
2384 m_scheduledConfigUpdate.stop();
2385 m_fsdSendMessageTimer.stop();
2388 void CFSDClient::updateAtisMap(
const QString &callsign, AtisLineType type,
const QString &line)
2390 if (type == AtisLineType::VoiceRoom)
2392 m_mapAtisMessages[callsign].voiceRoom = line;
2393 m_mapAtisMessages[callsign].lineCount++;
2396 else if (type == AtisLineType::TextMessage)
2398 m_mapAtisMessages[callsign].textLines.push_back(line);
2399 m_mapAtisMessages[callsign].lineCount++;
2402 else if (type == AtisLineType::ZuluLogoff)
2404 m_mapAtisMessages[callsign].zuluLogoff = line;
2405 m_mapAtisMessages[callsign].lineCount++;
2410 if (!m_mapAtisMessages.contains(callsign)) {
return; }
2413 m_mapAtisMessages[callsign].lineCount++;
2415 const CCallsign cs(callsign, CCallsign::Atc);
2417 emit atisLogoffTimeReplyReceived(cs, m_mapAtisMessages[callsign].zuluLogoff);
2420 for (
const QString &tm : std::as_const(m_mapAtisMessages[callsign].textLines))
2422 const QString fixed = tm.trimmed();
2423 if (!fixed.isEmpty())
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;
2431 if (test.length() == 1)
return;
2434 if (!atisMessage.isEmpty()) atisMessage.appendMessage(
"\n");
2435 atisMessage.appendMessage(fixed);
2439 emit this->atisReplyReceived(cs, atisMessage);
2441 m_mapAtisMessages.remove(callsign);
2446 void CFSDClient::pendingTimeoutCheck()
2448 if (!this->isPendingConnection()) {
return; }
2450 const qint64 age = QDateTime::currentMSecsSinceEpoch() - m_loginSince;
2451 if (age < PendingConnectionTimeoutMs) {
return; }
2454 CLogMessage(
this).
warning(u
"Timeout on pending connection to '%1'") << this->getServer().getName();
2455 this->disconnectFromServer();
2471 if (suffix.contains(QStringLiteral(
"ATIS"), Qt::CaseInsensitive))
2473 static const CLength l_Atis(150.0, CLengthUnit::NM());
2474 return maxOrNotNull(networkRange, l_Atis);
2476 if (suffix.contains(QStringLiteral(
"GND"), Qt::CaseInsensitive))
2478 static const CLength l_Gnd(10.0, CLengthUnit::NM());
2479 return maxOrNotNull(networkRange, l_Gnd);
2481 if (suffix.contains(QStringLiteral(
"TWR"), Qt::CaseInsensitive))
2483 static const CLength l_Twr(25.0, CLengthUnit::NM());
2484 return maxOrNotNull(networkRange, l_Twr);
2486 if (suffix.contains(QStringLiteral(
"DEP"), Qt::CaseInsensitive))
2488 static const CLength l_Dep(150.0, CLengthUnit::NM());
2489 return maxOrNotNull(networkRange, l_Dep);
2491 if (suffix.contains(QStringLiteral(
"APP"), Qt::CaseInsensitive))
2493 static const CLength l_App(150.0, CLengthUnit::NM());
2494 return maxOrNotNull(networkRange, l_App);
2496 if (suffix.contains(QStringLiteral(
"CTR"), Qt::CaseInsensitive))
2498 static const CLength l_Ctr(300.0, CLengthUnit::NM());
2499 return maxOrNotNull(networkRange, l_Ctr);
2501 if (suffix.contains(QStringLiteral(
"FSS"), Qt::CaseInsensitive))
2503 static const CLength l_Fss(1500.0, CLengthUnit::NM());
2504 return maxOrNotNull(networkRange, l_Fss);
2507 return networkRange;
2512 if (l1.
isNull()) {
return l2; }
2513 if (l2.
isNull()) {
return l1; }
2514 return (l2 > l1) ? l2 : l1;
2517 QString CFSDClient::noColons(
const QString &input)
2519 if (!input.contains(
':')) {
return input; }
2520 QString copy(input);
2521 return copy.remove(
':');
2526 switch (flightRules)
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;
2536 const QString &CFSDClient::messageTypeToString(
MessageType mt)
const
2539 while (i != m_messageTypeMapping.constEnd())
2541 if (i.value() == mt) {
return i.key(); }
2545 static const QString empty;
2549 void CFSDClient::handleIllegalFsdState(
const QString &message)
2551 if (CBuildConfig::isLocalDeveloperDebugBuild()) {
SWIFT_VERIFY_X(
false, Q_FUNC_INFO,
"Illegal FSD state"); }
2555 const QJsonObject &CFSDClient::JsonPackets::aircraftConfigRequest()
2557 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.
#define PROTOCOL_REVISION_VATSIM_VELOCITY
Protocol version.
#define PROTOCOL_REVISION_VATSIM_AUTH
Protocol version.
#define 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.