From acf540be34dc92df3ec8e8ee967e7ec4956ca332 Mon Sep 17 00:00:00 2001 From: Roland Rossgotterer Date: Fri, 13 Sep 2019 11:19:26 +0200 Subject: [PATCH] Long live FsdClient --- samples/cliclient/client.h | 2 +- samples/fsd/main.cpp | 90 + samples/fsd/samplefsd.pro | 24 + samples/samples.pro | 4 +- src/blackcore/airspaceanalyzer.cpp | 15 +- src/blackcore/airspaceanalyzer.h | 4 +- src/blackcore/airspacemonitor.cpp | 90 +- src/blackcore/airspacemonitor.h | 11 +- src/blackcore/application.cpp | 3 +- src/blackcore/blackcore.pro | 4 +- src/blackcore/context/contextnetwork.cpp | 36 + src/blackcore/context/contextnetwork.h | 5 +- src/blackcore/context/contextnetworkimpl.cpp | 143 +- src/blackcore/context/contextnetworkimpl.h | 7 +- src/blackcore/fsd/addatc.cpp | 58 + src/blackcore/fsd/addatc.h | 68 + src/blackcore/fsd/addpilot.cpp | 54 + src/blackcore/fsd/addpilot.h | 90 + src/blackcore/fsd/atcdataupdate.cpp | 62 + src/blackcore/fsd/atcdataupdate.h | 68 + src/blackcore/fsd/authchallenge.cpp | 47 + src/blackcore/fsd/authchallenge.h | 51 + src/blackcore/fsd/authresponse.cpp | 43 + src/blackcore/fsd/authresponse.h | 53 + src/blackcore/fsd/blackfsd.pro | 19 + src/blackcore/fsd/clientidentification.cpp | 56 + src/blackcore/fsd/clientidentification.h | 67 + src/blackcore/fsd/clientquery.cpp | 51 + src/blackcore/fsd/clientquery.h | 59 + src/blackcore/fsd/clientresponse.cpp | 50 + src/blackcore/fsd/clientresponse.h | 57 + src/blackcore/fsd/deleteatc.cpp | 43 + src/blackcore/fsd/deleteatc.h | 50 + src/blackcore/fsd/deletepilot.cpp | 46 + src/blackcore/fsd/deletepilot.h | 52 + src/blackcore/fsd/enums.h | 210 ++ src/blackcore/fsd/flightplan.cpp | 79 + src/blackcore/fsd/flightplan.h | 83 + src/blackcore/fsd/fsdclient.cpp | 1910 +++++++++++++++++ src/blackcore/fsd/fsdclient.h | 442 ++++ src/blackcore/fsd/fsdidentification.cpp | 46 + src/blackcore/fsd/fsdidentification.h | 41 + src/blackcore/fsd/interimpilotdataupdate.cpp | 73 + src/blackcore/fsd/interimpilotdataupdate.h | 67 + src/blackcore/fsd/killrequest.cpp | 44 + src/blackcore/fsd/killrequest.h | 51 + src/blackcore/fsd/messagebase.cpp | 26 + src/blackcore/fsd/messagebase.h | 78 + src/blackcore/fsd/pbh.h | 86 + src/blackcore/fsd/pilotdataupdate.cpp | 79 + src/blackcore/fsd/pilotdataupdate.h | 77 + src/blackcore/fsd/ping.cpp | 44 + src/blackcore/fsd/ping.h | 50 + src/blackcore/fsd/planeinforequest.cpp | 43 + src/blackcore/fsd/planeinforequest.h | 48 + src/blackcore/fsd/planeinforequestfsinn.cpp | 61 + src/blackcore/fsd/planeinforequestfsinn.h | 62 + src/blackcore/fsd/planeinformation.cpp | 70 + src/blackcore/fsd/planeinformation.h | 58 + src/blackcore/fsd/planeinformationfsinn.cpp | 63 + src/blackcore/fsd/planeinformationfsinn.h | 63 + src/blackcore/fsd/pong.cpp | 44 + src/blackcore/fsd/pong.h | 51 + src/blackcore/fsd/serializer.cpp | 311 +++ src/blackcore/fsd/serializer.h | 85 + src/blackcore/fsd/servererror.cpp | 71 + src/blackcore/fsd/servererror.h | 58 + src/blackcore/fsd/textmessage.cpp | 61 + src/blackcore/fsd/textmessage.h | 50 + src/blackcore/network.cpp | 147 -- src/blackcore/network.h | 644 ------ src/blackcore/registermetadata.cpp | 17 +- src/blackcore/vatsim/networkvatlib.cpp | 1820 ---------------- src/blackcore/vatsim/networkvatlib.h | 334 --- src/blackgui/components/mappingcomponent.h | 1 + src/blackmisc/audio/notificationsounds.cpp | 3 + src/blackmisc/network/client.cpp | 2 +- src/blackmisc/network/client.h | 3 +- .../network/registermetadatanetwork.cpp | 6 +- src/swiftlauncher/swiftlauncher.cpp | 15 +- tests/blackcore/blackcore.pro | 2 +- tests/blackcore/fsd/fsd.pro | 5 + .../fsd/testfsdclient/testfsdclient.cpp | 829 +++++++ .../testfsdclient/testfsdclient.pro} | 11 +- .../fsd/testfsdmessages/testfsdmessages.cpp | 611 ++++++ .../fsd/testfsdmessages/testfsdmessages.pro | 28 + tests/blackcore/vatsim/testnetwork/expect.cpp | 123 -- tests/blackcore/vatsim/testnetwork/expect.h | 299 --- .../vatsim/testnetwork/testnetwork.cpp | 175 -- tests/blackcore/vatsim/vatsim.pro | 3 - 90 files changed, 7646 insertions(+), 3699 deletions(-) create mode 100644 samples/fsd/main.cpp create mode 100644 samples/fsd/samplefsd.pro create mode 100644 src/blackcore/fsd/addatc.cpp create mode 100644 src/blackcore/fsd/addatc.h create mode 100644 src/blackcore/fsd/addpilot.cpp create mode 100644 src/blackcore/fsd/addpilot.h create mode 100644 src/blackcore/fsd/atcdataupdate.cpp create mode 100644 src/blackcore/fsd/atcdataupdate.h create mode 100644 src/blackcore/fsd/authchallenge.cpp create mode 100644 src/blackcore/fsd/authchallenge.h create mode 100644 src/blackcore/fsd/authresponse.cpp create mode 100644 src/blackcore/fsd/authresponse.h create mode 100644 src/blackcore/fsd/blackfsd.pro create mode 100644 src/blackcore/fsd/clientidentification.cpp create mode 100644 src/blackcore/fsd/clientidentification.h create mode 100644 src/blackcore/fsd/clientquery.cpp create mode 100644 src/blackcore/fsd/clientquery.h create mode 100644 src/blackcore/fsd/clientresponse.cpp create mode 100644 src/blackcore/fsd/clientresponse.h create mode 100644 src/blackcore/fsd/deleteatc.cpp create mode 100644 src/blackcore/fsd/deleteatc.h create mode 100644 src/blackcore/fsd/deletepilot.cpp create mode 100644 src/blackcore/fsd/deletepilot.h create mode 100644 src/blackcore/fsd/enums.h create mode 100644 src/blackcore/fsd/flightplan.cpp create mode 100644 src/blackcore/fsd/flightplan.h create mode 100644 src/blackcore/fsd/fsdclient.cpp create mode 100644 src/blackcore/fsd/fsdclient.h create mode 100644 src/blackcore/fsd/fsdidentification.cpp create mode 100644 src/blackcore/fsd/fsdidentification.h create mode 100644 src/blackcore/fsd/interimpilotdataupdate.cpp create mode 100644 src/blackcore/fsd/interimpilotdataupdate.h create mode 100644 src/blackcore/fsd/killrequest.cpp create mode 100644 src/blackcore/fsd/killrequest.h create mode 100644 src/blackcore/fsd/messagebase.cpp create mode 100644 src/blackcore/fsd/messagebase.h create mode 100644 src/blackcore/fsd/pbh.h create mode 100644 src/blackcore/fsd/pilotdataupdate.cpp create mode 100644 src/blackcore/fsd/pilotdataupdate.h create mode 100644 src/blackcore/fsd/ping.cpp create mode 100644 src/blackcore/fsd/ping.h create mode 100644 src/blackcore/fsd/planeinforequest.cpp create mode 100644 src/blackcore/fsd/planeinforequest.h create mode 100644 src/blackcore/fsd/planeinforequestfsinn.cpp create mode 100644 src/blackcore/fsd/planeinforequestfsinn.h create mode 100644 src/blackcore/fsd/planeinformation.cpp create mode 100644 src/blackcore/fsd/planeinformation.h create mode 100644 src/blackcore/fsd/planeinformationfsinn.cpp create mode 100644 src/blackcore/fsd/planeinformationfsinn.h create mode 100644 src/blackcore/fsd/pong.cpp create mode 100644 src/blackcore/fsd/pong.h create mode 100644 src/blackcore/fsd/serializer.cpp create mode 100644 src/blackcore/fsd/serializer.h create mode 100644 src/blackcore/fsd/servererror.cpp create mode 100644 src/blackcore/fsd/servererror.h create mode 100644 src/blackcore/fsd/textmessage.cpp create mode 100644 src/blackcore/fsd/textmessage.h delete mode 100644 src/blackcore/network.cpp delete mode 100644 src/blackcore/network.h delete mode 100644 src/blackcore/vatsim/networkvatlib.cpp delete mode 100644 src/blackcore/vatsim/networkvatlib.h create mode 100644 tests/blackcore/fsd/fsd.pro create mode 100644 tests/blackcore/fsd/testfsdclient/testfsdclient.cpp rename tests/blackcore/{vatsim/testnetwork/testnetwork.pro => fsd/testfsdclient/testfsdclient.pro} (73%) create mode 100644 tests/blackcore/fsd/testfsdmessages/testfsdmessages.cpp create mode 100644 tests/blackcore/fsd/testfsdmessages/testfsdmessages.pro delete mode 100644 tests/blackcore/vatsim/testnetwork/expect.cpp delete mode 100644 tests/blackcore/vatsim/testnetwork/expect.h delete mode 100644 tests/blackcore/vatsim/testnetwork/testnetwork.cpp delete mode 100644 tests/blackcore/vatsim/vatsim.pro diff --git a/samples/cliclient/client.h b/samples/cliclient/client.h index 813c1f382..1204889be 100644 --- a/samples/cliclient/client.h +++ b/samples/cliclient/client.h @@ -12,7 +12,7 @@ #ifndef BLACKSAMPLE_CLICLIENT_CLIENT_H #define BLACKSAMPLE_CLICLIENT_CLIENT_H -#include "blackcore/network.h" +#include "blackcore/fsd/fsdclient.h" #include "blackmisc/geo/coordinategeodetic.h" #include "blackmisc/identifiable.h" #include "blackmisc/network/textmessagelist.h" diff --git a/samples/fsd/main.cpp b/samples/fsd/main.cpp new file mode 100644 index 000000000..679d6bade --- /dev/null +++ b/samples/fsd/main.cpp @@ -0,0 +1,90 @@ +/* Copyright (C) 2018 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project, + * including this file, may be copied, modified, propagated, or distributed except according to the terms + * contained in the LICENSE file. + */ + +//! \file +//! \ingroup samplefsd + +#include "blackcore/fsd/fsdclient.h" +#include "blackmisc/network/clientprovider.h" +#include "blackmisc/simulation/ownaircraftproviderdummy.h" +#include "blackmisc/simulation/remoteaircraftproviderdummy.h" +#include +#include + +using namespace BlackMisc::Network; +using namespace BlackMisc::Simulation; +using namespace BlackCore::Fsd; + +//! main +int main(int argc, char *argv[]) +{ + QCoreApplication qa(argc, argv); + + + + COwnAircraftProviderDummy::instance()->updateOwnCallsign("BER368"); + + FSDClient client(CClientProviderDummy::instance(), COwnAircraftProviderDummy::instance(), CRemoteAircraftProviderDummy::instance(), &qa); + client.setClientName("Test Client"); + client.setHostApplication("None"); + client.setVersion(0, 8); + QString key("727d1efd5cb9f8d2c28372469d922bb4"); + client.setClientIdAndKey(0xb9ba, key.toLocal8Bit()); + client.setClientCapabilities(Capabilities::AtcInfo | Capabilities::AircraftInfo | Capabilities::AircraftConfig); + + const CUser user("1234567", "Test user - EDDM", "", "123456"); + CServer server("fsd.swift-project.org", 6809, user); + server.setServerType(CServer::FSDServerVatsim); + client.setServer(server); + client.setSimType(SimType::XPLANE10); + client.setPilotRating(PilotRating::Student); + client.printToConsole(true); + + /*client.sendFsdMessage("$CRLOWW_F_APP:LHA449:ATIS:V:voice.vacc.ch/loww_f_app\r\n"); + client.sendFsdMessage("$CRLOWW_F_APP:LHA449:ATIS:T:Wien Director, Servus\r\n"); + client.sendFsdMessage("$CRLOWW_F_APP:LHA449:ATIS:T:CALLSIGN ONLY\r\n"); + client.sendFsdMessage("$CRLOWW_F_APP:LHA449:ATIS:T:For charts visit www.vacc-austria.org\r\n"); + client.sendFsdMessage("$CRLOWW_F_APP:LHA449:ATIS:Z:z\r\n"); + client.sendFsdMessage("$CRLOWW_F_APP:LHA449:ATIS:E:6\r\n"); + client.sendFsdMessage("$CRN1234:BAW345:CAPS:INTERIMPOS=1:MODELDESC=1:ATCINFO=1:STEALTH=1:ACCONFIG=1\r\n"); + client.sendFsdMessage("#SBBAW106:LHA449:PI:GEN:EQUIPMENT=B744:AIRLINE=BAW:LIVERY=UNION\r\n"); + client.sendFsdMessage("#SBGEC55F:DESWL:FSIPIR:1::MD11:12.93209:-0.01354:3648.00000:4.CB8FB1E0.984745A0::PMDG MD-11F Lufthansa Cargo WOW\r\n"); + client.sendFsdMessage("#TMAFR529:@20500&@26000:taxi to entry N1 via M A4\r\n"); + client.sendFsdMessage("$CQDLH123:BER368:ACC:json config\r\n"); + client.sendFsdMessage("$CQDLH123:@94836:ACC:{\"request\":\"full\"}\r\n"); + client.sendFsdMessage("#SBAUA417C:LHA449:PI:X:0:1:~B737"); + client.sendFsdMessage("#SBBER368:someone:I:47.29946:14.45892:41082:473:4278194872\r\n"); + client.sendFsdMessage("$CQURRR_R_APP:@94835:SC:VTD740:ÈËÑ\r\n"); + client.sendFsdMessage("@N:SVA732:346:1:53.10591:2.50108:37010:529:4261225460:42\r\n"); + client.sendFsdMessage("$CRLHA449:LOWW_TWR:RN:Peter Buchegger - LOWL:NONE:1\r\n");*/ + + client.connectToServer(); + + while (client.getConnectionStatus() == CConnectionStatus::Disconnected) + { + QCoreApplication::processEvents(); + QThread::msleep(100); + } + + bool functionTestsDone = false; + while (client.getConnectionStatus() == CConnectionStatus::Connected) + { + QThread::msleep(100); + if (!functionTestsDone) + { + /*client.sendFlightPlan(FlightType::VFR, "B744", 420, "EGLL", 1530, 1535, "FL350", "KORD", 8, 15, + 9, 30, "NONE", "Unit Test", "EGLL.KORD"); + client.sendClientQuery(ClientQueryType::FP, "SERVER", { "BER368" });*/ + functionTestsDone = true; + } + QCoreApplication::processEvents(); + } + + return qa.exec(); +} diff --git a/samples/fsd/samplefsd.pro b/samples/fsd/samplefsd.pro new file mode 100644 index 000000000..09ac9b36e --- /dev/null +++ b/samples/fsd/samplefsd.pro @@ -0,0 +1,24 @@ +load(common_pre) + +QT += core dbus network + +TARGET = samplefsd +TEMPLATE = app + +CONFIG += console +CONFIG += blackmisc blackcore +CONFIG -= app_bundle + +DEPENDPATH += . $$SourceRoot/src +INCLUDEPATH += . $$SourceRoot/src + +SOURCES += *.cpp + +LIBS *= -lvatsimauth + +DESTDIR = $$DestRoot/bin + +target.path = $$PREFIX/bin +INSTALLS += target + +load(common_post) diff --git a/samples/samples.pro b/samples/samples.pro index 75bb2e1d3..5ac880857 100644 --- a/samples/samples.pro +++ b/samples/samples.pro @@ -3,13 +3,14 @@ load(common_pre) TEMPLATE = subdirs CONFIG += ordered -SUBDIRS += samplecliclient +# SUBDIRS += samplecliclient SUBDIRS += sampleblackmiscquantities SUBDIRS += sampleblackmiscdbus SUBDIRS += sampleblackmisc SUBDIRS += sampleblackmiscsim SUBDIRS += samplehotkey SUBDIRS += sampleweatherdata +SUBDIRS += samplefsd samplecliclient.file = cliclient/samplecliclient.pro sampleblackmiscquantities.file = blackmiscquantities/sampleblackmiscquantities.pro @@ -18,5 +19,6 @@ sampleblackmisc.file = blackmisc/sampleblackmisc.pro sampleblackmiscsim.file = blackmiscsim/sampleblackmiscsim.pro samplehotkey.file = hotkey/samplehotkey.pro sampleweatherdata.file = weatherdata/sampleweatherdata.pro +samplefsd.file = fsd/samplefsd.pro load(common_post) diff --git a/src/blackcore/airspaceanalyzer.cpp b/src/blackcore/airspaceanalyzer.cpp index 8d9694fc1..5a88324e0 100644 --- a/src/blackcore/airspaceanalyzer.cpp +++ b/src/blackcore/airspaceanalyzer.cpp @@ -28,15 +28,16 @@ using namespace BlackMisc::Network; using namespace BlackMisc::Simulation; using namespace BlackMisc::Aviation; using namespace BlackMisc::PhysicalQuantities; +using namespace BlackCore::Fsd; namespace BlackCore { - CAirspaceAnalyzer::CAirspaceAnalyzer(IOwnAircraftProvider *ownAircraftProvider, INetwork *network, CAirspaceMonitor *airspaceMonitorParent) : + CAirspaceAnalyzer::CAirspaceAnalyzer(IOwnAircraftProvider *ownAircraftProvider, FSDClient *fsdClient, CAirspaceMonitor *airspaceMonitorParent) : CContinuousWorker(airspaceMonitorParent, "CAirspaceAnalyzer"), COwnAircraftAware(ownAircraftProvider), CRemoteAircraftAware(airspaceMonitorParent) { - Q_ASSERT_X(network, Q_FUNC_INFO, "Network object required to connect"); + Q_ASSERT_X(fsdClient, Q_FUNC_INFO, "Network object required to connect"); // all in new thread from here on this->setObjectName(getName()); @@ -46,17 +47,17 @@ namespace BlackCore Q_ASSERT(c); // network connected - c = connect(network, &INetwork::pilotDisconnected, this, &CAirspaceAnalyzer::watchdogRemoveAircraftCallsign, Qt::QueuedConnection); + c = connect(fsdClient, &FSDClient::deletePilotReceived, this, &CAirspaceAnalyzer::watchdogRemoveAircraftCallsign, Qt::QueuedConnection); Q_ASSERT(c); - c = connect(network, &INetwork::atcDisconnected, this, &CAirspaceAnalyzer::watchdogRemoveAtcCallsign, Qt::QueuedConnection); + c = connect(fsdClient, &FSDClient::deleteAtcReceived, this, &CAirspaceAnalyzer::watchdogRemoveAtcCallsign, Qt::QueuedConnection); Q_ASSERT(c); - c = connect(network, &INetwork::connectionStatusChanged, this, &CAirspaceAnalyzer::onConnectionStatusChanged, Qt::QueuedConnection); + c = connect(fsdClient, &FSDClient::connectionStatusChanged, this, &CAirspaceAnalyzer::onConnectionStatusChanged, Qt::QueuedConnection); Q_ASSERT(c); // network situations - c = connect(network, &INetwork::aircraftPositionUpdate, this, &CAirspaceAnalyzer::onNetworkPositionUpdate, Qt::QueuedConnection); + c = connect(fsdClient, &FSDClient::pilotDataUpdateReceived, this, &CAirspaceAnalyzer::onNetworkPositionUpdate, Qt::QueuedConnection); Q_ASSERT(c); - c = connect(network, &INetwork::atcPositionUpdate, this, &CAirspaceAnalyzer::watchdogTouchAtcCallsign, Qt::QueuedConnection); + c = connect(fsdClient, &FSDClient::atcDataUpdateReceived, this, &CAirspaceAnalyzer::watchdogTouchAtcCallsign, Qt::QueuedConnection); Q_ASSERT(c); // Monitor diff --git a/src/blackcore/airspaceanalyzer.h b/src/blackcore/airspaceanalyzer.h index 782d7675e..1ddfbff70 100644 --- a/src/blackcore/airspaceanalyzer.h +++ b/src/blackcore/airspaceanalyzer.h @@ -12,7 +12,7 @@ #define BLACKCORE_AIRSPACE_ANALYZER_H #include "blackcore/blackcoreexport.h" -#include "blackcore/network.h" +#include "blackcore/fsd/fsdclient.h" #include "blackmisc/network/connectionstatus.h" #include "blackmisc/simulation/airspaceaircraftsnapshot.h" #include "blackmisc/simulation/ownaircraftprovider.h" @@ -65,7 +65,7 @@ namespace BlackCore //! Constructor CAirspaceAnalyzer(BlackMisc::Simulation::IOwnAircraftProvider *ownAircraftProvider, - INetwork *network, + Fsd::FSDClient *fsdClient, CAirspaceMonitor *airspaceMonitorParent); //! Destructor diff --git a/src/blackcore/airspacemonitor.cpp b/src/blackcore/airspacemonitor.cpp index 103927bb0..57a934462 100644 --- a/src/blackcore/airspacemonitor.cpp +++ b/src/blackcore/airspacemonitor.cpp @@ -6,7 +6,6 @@ * or distributed except according to the terms contained in the LICENSE file. */ -#include "blackcore/vatsim/networkvatlib.h" #include "blackcore/vatsim/vatsimbookingreader.h" #include "blackcore/vatsim/vatsimdatafilereader.h" #include "blackcore/airspaceanalyzer.h" @@ -61,37 +60,39 @@ using namespace BlackMisc::Json; using namespace BlackMisc::Network; using namespace BlackMisc::PhysicalQuantities; using namespace BlackMisc::Weather; +using namespace BlackCore::Fsd; using namespace BlackCore::Vatsim; namespace BlackCore { - CAirspaceMonitor::CAirspaceMonitor(IOwnAircraftProvider *ownAircraftProvider, IAircraftModelSetProvider *modelSetProvider, INetwork *network, QObject *parent) + CAirspaceMonitor::CAirspaceMonitor(IOwnAircraftProvider *ownAircraftProvider, IAircraftModelSetProvider *modelSetProvider, FSDClient *fsdClient, QObject *parent) : CRemoteAircraftProvider(parent), COwnAircraftAware(ownAircraftProvider), CAircraftModelSetAware(modelSetProvider), - m_network(network), - m_analyzer(new CAirspaceAnalyzer(ownAircraftProvider, network, this)) + m_fsdClient(fsdClient), + m_analyzer(new CAirspaceAnalyzer(ownAircraftProvider, m_fsdClient, this)) { this->setObjectName("CAirspaceMonitor"); this->enableReverseLookupMessages(sApp->isDeveloperFlagSet() || CBuildConfig::isLocalDeveloperDebugBuild() ? RevLogEnabled : RevLogEnabledSimplified); - connect(m_network, &INetwork::atcPositionUpdate, this, &CAirspaceMonitor::onAtcPositionUpdate); - connect(m_network, &INetwork::atisReplyReceived, this, &CAirspaceMonitor::onAtisReceived); - connect(m_network, &INetwork::atisVoiceRoomReplyReceived, this, &CAirspaceMonitor::onAtisVoiceRoomReceived); - connect(m_network, &INetwork::atisLogoffTimeReplyReceived, this, &CAirspaceMonitor::onAtisLogoffTimeReceived); - connect(m_network, &INetwork::flightPlanReplyReceived, this, &CAirspaceMonitor::onFlightPlanReceived); - connect(m_network, &INetwork::realNameReplyReceived, this, &CAirspaceMonitor::onRealNameReplyReceived); - connect(m_network, &INetwork::icaoCodesReplyReceived, this, &CAirspaceMonitor::onIcaoCodesReceived); - connect(m_network, &INetwork::pilotDisconnected, this, &CAirspaceMonitor::onPilotDisconnected); - connect(m_network, &INetwork::atcDisconnected, this, &CAirspaceMonitor::onAtcControllerDisconnected); - connect(m_network, &INetwork::aircraftPositionUpdate, this, &CAirspaceMonitor::onAircraftUpdateReceived); - connect(m_network, &INetwork::aircraftInterimPositionUpdate, this, &CAirspaceMonitor::onAircraftInterimUpdateReceived); - connect(m_network, &INetwork::frequencyReplyReceived, this, &CAirspaceMonitor::onFrequencyReceived); - connect(m_network, &INetwork::capabilitiesReplyReceived, this, &CAirspaceMonitor::onCapabilitiesReplyReceived); - connect(m_network, &INetwork::customFSInnPacketReceived, this, &CAirspaceMonitor::onCustomFSInnPacketReceived); - connect(m_network, &INetwork::serverReplyReceived, this, &CAirspaceMonitor::onServerReplyReceived); - connect(m_network, &INetwork::aircraftConfigPacketReceived, this, &CAirspaceMonitor::onAircraftConfigReceived); - connect(m_network, &INetwork::connectionStatusChanged, this, &CAirspaceMonitor::onConnectionStatusChanged); + connect(m_fsdClient, &FSDClient::atcDataUpdateReceived, this, &CAirspaceMonitor::onAtcPositionUpdate); + // FSD TODO + connect(m_fsdClient, &FSDClient::atisReplyReceived, this, &CAirspaceMonitor::onAtisReceived); + connect(m_fsdClient, &FSDClient::atisVoiceRoomReplyReceived, this, &CAirspaceMonitor::onAtisVoiceRoomReceived); + connect(m_fsdClient, &FSDClient::atisLogoffTimeReplyReceived, this, &CAirspaceMonitor::onAtisLogoffTimeReceived); + connect(m_fsdClient, &FSDClient::flightPlanReceived, this, &CAirspaceMonitor::onFlightPlanReceived); + connect(m_fsdClient, &FSDClient::realNameResponseReceived, this, &CAirspaceMonitor::onRealNameReplyReceived); + connect(m_fsdClient, &FSDClient::planeInformationReceived, this, &CAirspaceMonitor::onIcaoCodesReceived); + connect(m_fsdClient, &FSDClient::deletePilotReceived, this, &CAirspaceMonitor::onPilotDisconnected); + connect(m_fsdClient, &FSDClient::deleteAtcReceived, this, &CAirspaceMonitor::onAtcControllerDisconnected); + connect(m_fsdClient, &FSDClient::pilotDataUpdateReceived, this, &CAirspaceMonitor::onAircraftUpdateReceived); + connect(m_fsdClient, &FSDClient::interimPilotDataUpdatedReceived, this, &CAirspaceMonitor::onAircraftInterimUpdateReceived); + connect(m_fsdClient, &FSDClient::com1FrequencyResponseReceived, this, &CAirspaceMonitor::onFrequencyReceived); + connect(m_fsdClient, &FSDClient::capabilityResponseReceived, this, &CAirspaceMonitor::onCapabilitiesReplyReceived); + connect(m_fsdClient, &FSDClient::planeInformationFsinnReceived, this, &CAirspaceMonitor::onCustomFSInnPacketReceived); + connect(m_fsdClient, &FSDClient::serverResponseReceived, this, &CAirspaceMonitor::onServerReplyReceived); + connect(m_fsdClient, &FSDClient::aircraftConfigReceived, this, &CAirspaceMonitor::onAircraftConfigReceived); + connect(m_fsdClient, &FSDClient::connectionStatusChanged, this, &CAirspaceMonitor::onConnectionStatusChanged); // AutoConnection: this should also avoid race conditions by updating the bookings Q_ASSERT_X(sApp && sApp->getWebDataServices(), Q_FUNC_INFO, "Missing data reader"); @@ -123,22 +124,22 @@ namespace BlackCore bool CAirspaceMonitor::updateFastPositionEnabled(const CCallsign &callsign, bool enableFastPositonUpdates) { const bool r = CRemoteAircraftProvider::updateFastPositionEnabled(callsign, enableFastPositonUpdates); - if (m_network && sApp && !sApp->isShuttingDown()) + if (m_fsdClient && sApp && !sApp->isShuttingDown()) { // thread safe update of m_network const QPointer myself(this); - QTimer::singleShot(0, m_network, [ = ] + QTimer::singleShot(0, m_fsdClient, [ = ] { if (!myself) { return; } - if (m_network) + if (m_fsdClient) { if (enableFastPositonUpdates) { - m_network->addInterimPositionReceiver(callsign); + m_fsdClient->addInterimPositionReceiver(callsign); } else { - m_network->removeInterimPositionReceiver(callsign); + m_fsdClient->removeInterimPositionReceiver(callsign); } } }); @@ -171,7 +172,7 @@ namespace BlackCore // outdated, or not in cache at all, or NOT own aircraft plan = CFlightPlan(); // reset m_flightPlanCache.remove(callsign); // loading FP from network - m_network->sendFlightPlanQuery(callsign); + m_fsdClient->sendClientQueryFlightPlan(callsign); // with this little trick we try to make an asynchronous signal / slot based approach // a synchronous return value @@ -463,17 +464,16 @@ namespace BlackCore this->updateOrAddClient(callsign, vm, false); } - void CAirspaceMonitor::onCapabilitiesReplyReceived(const CCallsign &callsign, int clientCaps) + void CAirspaceMonitor::onCapabilitiesReplyReceived(const CCallsign &callsign, CClient::Capabilities clientCaps) { if (!this->isConnectedAndNotShuttingDown() || callsign.isEmpty()) { return; } - const CClient::Capabilities caps = static_cast(clientCaps); const CVoiceCapabilities voiceCaps = sApp->getWebDataServices()->getVoiceCapabilityForCallsign(callsign); CPropertyIndexVariantMap vm(CClient::IndexCapabilities, CVariant::from(clientCaps)); vm.addValue({CClient::IndexVoiceCapabilities}, voiceCaps); this->updateOrAddClient(callsign, vm, false); // for aircraft parts - if (caps.testFlag(CClient::FsdWithAircraftConfig)) { m_network->sendAircraftConfigQuery(callsign); } + if (clientCaps.testFlag(CClient::FsdWithAircraftConfig)) { m_fsdClient->sendClientQueryAircraftConfig(callsign); } } void CAirspaceMonitor::onServerReplyReceived(const CCallsign &callsign, const QString &server) @@ -1356,10 +1356,10 @@ namespace BlackCore void CAirspaceMonitor::sendInitialAtcQueries(const CCallsign &callsign) { if (!this->isConnectedAndNotShuttingDown()) { return; } - m_network->sendRealNameQuery(callsign); - m_network->sendAtisQuery(callsign); // request ATIS and voice rooms - m_network->sendCapabilitiesQuery(callsign); - m_network->sendServerQuery(callsign); + m_fsdClient->sendClientQueryRealName(callsign); + m_fsdClient->sendClientQueryAtis(callsign); // request ATIS and voice rooms + m_fsdClient->sendClientQueryCapabilities(callsign); + m_fsdClient->sendClientQueryServer(callsign); } bool CAirspaceMonitor::sendNextStaggeredAtisQuery() @@ -1368,7 +1368,7 @@ namespace BlackCore if (!this->isConnectedAndNotShuttingDown()) { return false; } const CCallsign cs = m_queryAtis.dequeue(); if (!m_atcStationsOnline.containsCallsign(cs)) { return false; } - m_network->sendAtisQuery(cs); + m_fsdClient->sendClientQueryAtis(cs); return true; } @@ -1376,13 +1376,13 @@ namespace BlackCore { if (!this->isConnectedAndNotShuttingDown()) { return; } - if (withIcaoQuery) { m_network->sendIcaoCodesQuery(callsign); } - if (withFsInn) { m_network->sendCustomFsinnQuery(callsign); } + if (withIcaoQuery) { m_fsdClient->sendPlaneInfoRequest(callsign); } + if (withFsInn) { m_fsdClient->sendPlaneInfoRequestFsinn(callsign); } - m_network->sendFrequencyQuery(callsign); - m_network->sendRealNameQuery(callsign); - m_network->sendCapabilitiesQuery(callsign); - m_network->sendServerQuery(callsign); + m_fsdClient->sendClientQueryCom1Freq(callsign); + m_fsdClient->sendClientQueryRealName(callsign); + m_fsdClient->sendClientQueryCapabilities(callsign); + m_fsdClient->sendClientQueryServer(callsign); } bool CAirspaceMonitor::sendNextStaggeredPilotDataQuery() @@ -1391,21 +1391,21 @@ namespace BlackCore if (!this->isConnectedAndNotShuttingDown()) { return false; } const CCallsign cs = m_queryPilot.dequeue(); if (!this->isAircraftInRange(cs)) { return false; } - m_network->sendFrequencyQuery(cs); + m_fsdClient->sendClientQueryCom1Freq(cs); // we only query ICAO if we have none yet // it happens sometimes with some FSD servers (e.g our testserver) a first query is skipped // Important: this is only a workaround and must not replace a sendInitialPilotQueries if (!this->getAircraftInRangeForCallsign(cs).hasAircraftDesignator()) { - m_network->sendIcaoCodesQuery(cs); + m_fsdClient->sendPlaneInfoRequest(cs); } return true; } bool CAirspaceMonitor::isConnected() const { - return m_network && m_network->isConnected(); + return m_fsdClient && m_fsdClient->getConnectionStatus().isConnected(); } bool CAirspaceMonitor::isConnectedAndNotShuttingDown() const @@ -1418,7 +1418,7 @@ namespace BlackCore { static const CServer empty; if (!this->isConnected()) { return empty; } - return m_network->getPresetServer(); + return m_fsdClient->getServer(); } const CEcosystem &CAirspaceMonitor::getCurrentEcosystem() const diff --git a/src/blackcore/airspacemonitor.h b/src/blackcore/airspacemonitor.h index a1b8a357a..0d6786f86 100644 --- a/src/blackcore/airspacemonitor.h +++ b/src/blackcore/airspacemonitor.h @@ -11,10 +11,12 @@ #ifndef BLACKCORE_AIRSPACE_MONITOR_H #define BLACKCORE_AIRSPACE_MONITOR_H -#include "blackcore/network.h" #include "blackcore/blackcoreexport.h" #include "blackmisc/simulation/settings/modelmatchersettings.h" #include "blackmisc/simulation/aircraftmodelsetprovider.h" +#include "blackcore/fsd/fsdclient.h" +#include "blackmisc/network/server.h" +#include "blackmisc/network/ecosystem.h" #include "blackmisc/simulation/aircraftmodel.h" #include "blackmisc/simulation/airspaceaircraftsnapshot.h" #include "blackmisc/simulation/matchinglog.h" @@ -51,7 +53,6 @@ namespace BlackCore { class CAirspaceAnalyzer; - class INetwork; //! Keeps track of other entities in the airspace: aircraft, ATC stations, etc. //! Central instance of data for \sa IRemoteAircraftProvider. @@ -74,7 +75,7 @@ namespace BlackCore //! Constructor CAirspaceMonitor(BlackMisc::Simulation::IOwnAircraftProvider *ownAircraft, BlackMisc::Simulation::IAircraftModelSetProvider *modelSetProvider, - INetwork *network, + Fsd::FSDClient *fsdClient, QObject *parent); //! Members not implenented or fully implenented by CRemoteAircraftProvider @@ -256,7 +257,7 @@ namespace BlackCore BlackMisc::CSettingReadOnly m_matchingSettings { this }; //!< settings QQueue m_queryAtis; //!< query the ATIS QQueue m_queryPilot; //!< query the pilot data - INetwork *m_network = nullptr; //!< corresponding network interface + Fsd::FSDClient *m_fsdClient = nullptr; //!< corresponding network interface CAirspaceAnalyzer *m_analyzer = nullptr; //!< owned analyzer bool m_bookingsRequested = false; //!< bookings have been requested, it can happen we receive an BlackCore::Vatsim::CVatsimBookingReader::atcBookingsReadUnchanged signal QTimer m_processTimer; @@ -371,7 +372,7 @@ namespace BlackCore void onCustomFSInnPacketReceived(const BlackMisc::Aviation::CCallsign &callsign, const QString &airlineIcaoDesignator, const QString &aircraftDesignator, const QString &combinedAircraftType, const QString &modelString); void onRealNameReplyReceived(const BlackMisc::Aviation::CCallsign &callsign, const QString &realname); - void onCapabilitiesReplyReceived(const BlackMisc::Aviation::CCallsign &callsign, int clientCaps); + void onCapabilitiesReplyReceived(const BlackMisc::Aviation::CCallsign &callsign, BlackMisc::Network::CClient::Capabilities clientCaps); void onServerReplyReceived(const BlackMisc::Aviation::CCallsign &callsign, const QString &server); void onFlightPlanReceived(const BlackMisc::Aviation::CCallsign &callsign, const BlackMisc::Aviation::CFlightPlan &flightPlan); void onAtcControllerDisconnected(const BlackMisc::Aviation::CCallsign &callsign); diff --git a/src/blackcore/application.cpp b/src/blackcore/application.cpp index 2c3ba9c8c..bd66f34f3 100644 --- a/src/blackcore/application.cpp +++ b/src/blackcore/application.cpp @@ -14,7 +14,6 @@ #include "blackcore/context/contextapplication.h" #include "blackcore/cookiemanager.h" #include "blackcore/corefacade.h" -#include "blackcore/vatsim/networkvatlib.h" #include "blackcore/registermetadata.h" #include "blackcore/setupreader.h" #include "blackcore/webdataservices.h" @@ -1302,7 +1301,7 @@ namespace BlackCore void CApplication::addVatlibOptions() { - this->addParserOptions(CNetworkVatlib::getCmdLineOptions()); + this->addParserOptions(BlackCore::Context::IContextNetwork::getCmdLineOptions()); } QString CApplication::getCmdDBusAddressValue() const diff --git a/src/blackcore/blackcore.pro b/src/blackcore/blackcore.pro index 04387fee2..8498ef56b 100644 --- a/src/blackcore/blackcore.pro +++ b/src/blackcore/blackcore.pro @@ -27,14 +27,16 @@ HEADERS += $$PWD/context/*.h HEADERS += $$PWD/data/*.h HEADERS += $$PWD/db/*.h HEADERS += $$PWD/vatsim/*.h +HEADERS += $$PWD/fsd/*.h SOURCES += *.cpp SOURCES += $$PWD/context/*.cpp SOURCES += $$PWD/data/*.cpp SOURCES += $$PWD/db/*.cpp SOURCES += $$PWD/vatsim/*.cpp +SOURCES += $$PWD/fsd/*.cpp -LIBS *= -lvatlib +LIBS *= -lvatlib -lvatsimauth DESTDIR = $$DestRoot/lib DLLDESTDIR = $$DestRoot/bin diff --git a/src/blackcore/context/contextnetwork.cpp b/src/blackcore/context/contextnetwork.cpp index 8e39b07e6..a5f76b0f3 100644 --- a/src/blackcore/context/contextnetwork.cpp +++ b/src/blackcore/context/contextnetwork.cpp @@ -10,8 +10,11 @@ #include "blackcore/context/contextnetworkempty.h" #include "blackcore/context/contextnetworkimpl.h" #include "blackcore/context/contextnetworkproxy.h" +#include "blackcore/application.h" #include "blackmisc/dbusserver.h" +#include "blackconfig/buildconfig.h" +using namespace BlackConfig; using namespace BlackCore; using namespace BlackMisc; @@ -31,5 +34,38 @@ namespace BlackCore return new CContextNetworkEmpty(runtime); } } + + const QList &IContextNetwork::getCmdLineOptions() + { + static const QList e; + static const QList opts + { + QCommandLineOption({ "idAndKey", "clientIdAndKey" }, + QCoreApplication::translate("CContextNetwork", "Client id and key pair separated by ':', e.g. :."), "clientIdAndKey") + }; + + // only in not officially shipped versions + return (CBuildConfig::isLocalDeveloperDebugBuild()) ? opts : e; + } + + bool IContextNetwork::getCmdLineClientIdAndKey(int &id, QString &key) + { + // init values + id = 0; + key = ""; + + // split parser values + if (IContextNetwork::getCmdLineOptions().isEmpty()) { return false; } // no such option, avoid warnings + const QString clientIdAndKey = sApp->getParserValue("clientIdAndKey").toLower(); + if (clientIdAndKey.isEmpty() || !clientIdAndKey.contains(':')) { return false; } + const QStringList stringList = clientIdAndKey.split(':'); + const QString clientIdAsString = stringList[0]; + bool ok = true; + id = clientIdAsString.toInt(&ok, 0); // base 0 means C convention + if (!ok || id == 0) { return false; } + key = stringList[1]; + return true; + } + } // namesapce } // namesapce diff --git a/src/blackcore/context/contextnetwork.h b/src/blackcore/context/contextnetwork.h index 51d2f2dc9..e5845289a 100644 --- a/src/blackcore/context/contextnetwork.h +++ b/src/blackcore/context/contextnetwork.h @@ -14,7 +14,6 @@ #include "blackcore/context/context.h" #include "blackcore/corefacade.h" #include "blackcore/corefacadeconfig.h" -#include "blackcore/network.h" #include "blackcore/blackcoreexport.h" #include "blackmisc/simulation/remoteaircraftprovider.h" #include "blackmisc/simulation/simulatedaircraftlist.h" @@ -41,6 +40,7 @@ #include #include +#include #include // clazy:excludeall=const-signal-or-slot @@ -369,6 +369,9 @@ namespace BlackCore //! Connect to receive raw fsd messages virtual QMetaObject::Connection connectRawFsdMessageSignal(QObject *receiver, RawFsdMessageReceivedSlot rawFsdMessageReceivedSlot) = 0; + static const QList &getCmdLineOptions(); + static bool getCmdLineClientIdAndKey(int &id, QString &key); + protected: //! Constructor IContextNetwork(CCoreFacadeConfig::ContextMode mode, CCoreFacade *runtime) : CContext(mode, runtime) {} diff --git a/src/blackcore/context/contextnetworkimpl.cpp b/src/blackcore/context/contextnetworkimpl.cpp index e5f5c5bdf..17e2b5550 100644 --- a/src/blackcore/context/contextnetworkimpl.cpp +++ b/src/blackcore/context/contextnetworkimpl.cpp @@ -6,7 +6,6 @@ * or distributed except according to the terms contained in the LICENSE file. */ -#include "blackcore/vatsim/networkvatlib.h" #include "blackcore/context/contextnetworkimpl.h" #include "blackcore/context/contextownaircraft.h" #include "blackcore/context/contextownaircraftimpl.h" @@ -52,6 +51,7 @@ using namespace BlackMisc::Geo; using namespace BlackMisc::Audio; using namespace BlackMisc::Simulation; using namespace BlackMisc::Weather; +using namespace BlackCore::Fsd; using namespace BlackCore::Vatsim; namespace BlackCore @@ -68,15 +68,12 @@ namespace BlackCore CContextNetwork::registerHelp(); // 1. Init by "network driver" - m_network = new CNetworkVatlib( - this, - this->getRuntime()->getCContextOwnAircraft(), - this, - this); - connect(m_network, &INetwork::connectionStatusChanged, this, &CContextNetwork::onFsdConnectionStatusChanged); - connect(m_network, &INetwork::kicked, this, &CContextNetwork::kicked); - connect(m_network, &INetwork::textMessagesReceived, this, &CContextNetwork::onTextMessagesReceived, Qt::QueuedConnection); - connect(m_network, &INetwork::textMessageSent, this, &CContextNetwork::textMessageSent); + m_fsdClient = new FSDClient(this, this->getRuntime()->getCContextOwnAircraft(), this, this); + connect(m_fsdClient, &FSDClient::connectionStatusChanged, this, &CContextNetwork::onFsdConnectionStatusChanged); + connect(m_fsdClient, &FSDClient::killRequestReceived, this, &CContextNetwork::kicked); + connect(m_fsdClient, &FSDClient::textMessagesReceived, this, &CContextNetwork::textMessagesReceived); +// connect(m_fsdClient, &FSDClient::textMessagesReceived, this, &CContextNetwork::checkForSupervisiorTextMessage); + connect(m_fsdClient, &FSDClient::textMessageSent, this, &CContextNetwork::textMessageSent); // 2. Update timer for data (network data such as frequency) // we use 2 timers so we can query at different times (not too many queirs at once) @@ -96,8 +93,8 @@ namespace BlackCore m_airspace = new CAirspaceMonitor( this->getRuntime()->getCContextOwnAircraft(), this->getRuntime()->getCContextSimulator(), - m_network, this); - m_network->setClientProvider(m_airspace); + m_fsdClient, this); + m_fsdClient->setClientProvider(m_airspace); connect(m_airspace, &CAirspaceMonitor::changedAtcStationsOnline, this, &CContextNetwork::changedAtcStationsOnline, Qt::QueuedConnection); connect(m_airspace, &CAirspaceMonitor::changedAtcStationsBooked, this, &CContextNetwork::changedAtcStationsBooked, Qt::QueuedConnection); connect(m_airspace, &CAirspaceMonitor::changedAtcStationOnlineConnectionStatus, this, &CContextNetwork::changedAtcStationOnlineConnectionStatus, Qt::QueuedConnection); @@ -118,7 +115,7 @@ namespace BlackCore void CContextNetwork::setSimulationEnvironmentProvider(ISimulationEnvironmentProvider *provider) { if (m_airspace) { m_airspace->setSimulationEnvironmentProvider(provider); } - if (m_network) { m_network->setSimulationEnvironmentProvider(provider); } + if (m_fsdClient) { m_fsdClient->setSimulationEnvironmentProvider(provider); } } CContextNetwork::~CContextNetwork() @@ -213,7 +210,7 @@ namespace BlackCore { this->disconnect(); // all signals if (this->isConnected()) { this->disconnectFromNetwork(); } - if (m_network) { m_network->setClientProvider(nullptr); } + if (m_fsdClient) { m_fsdClient->setClientProvider(nullptr); } if (m_airspace) { m_airspace->gracefulShutdown(); } } @@ -224,56 +221,69 @@ namespace BlackCore if (!server.getUser().hasCredentials()) { return CStatusMessage({ CLogCategory::validation() }, CStatusMessage::SeverityError, u"Invalid user credentials"); } if (!this->ownAircraft().getAircraftIcaoCode().hasDesignator()) { return CStatusMessage({ CLogCategory::validation() }, CStatusMessage::SeverityError, u"Invalid ICAO data for own aircraft"); } if (!CNetworkUtils::canConnect(server, msg, 5000)) { return CStatusMessage(CStatusMessage::SeverityError, msg); } - if (m_network->isConnected()) { return CStatusMessage({ CLogCategory::validation() }, CStatusMessage::SeverityError, u"Already connected"); } + if (m_fsdClient->isConnected()) { return CStatusMessage({ CLogCategory::validation() }, CStatusMessage::SeverityError, u"Already connected"); } if (this->isPendingConnection()) { return CStatusMessage({ CLogCategory::validation() }, CStatusMessage::SeverityError, u"Pending connection, please wait"); } - m_currentStatus = INetwork::Connecting; // as semaphore we are going to connect this->getIContextOwnAircraft()->updateOwnAircraftPilot(server.getUser()); const CSimulatedAircraft ownAircraft(this->ownAircraft()); - m_network->presetServer(server); - m_network->presetPartnerCallsign(partnerCallsign); + m_fsdClient->setPartnerCallsign(partnerCallsign); // Fall back to observer mode, if no simulator is available or not simulating if (!CBuildConfig::isLocalDeveloperDebugBuild() && !this->getIContextSimulator()->isSimulatorSimulating()) { CLogMessage(this).info(u"No simulator connected or connected simulator not simulating. Falling back to observer mode"); - mode = INetwork::LoginAsObserver; + mode.setLoginMode(CLoginMode::Observer); } const QString l = extraLiveryString.isEmpty() ? ownAircraft.getModel().getSwiftLiveryString() : extraLiveryString; const QString m = extraModelString.isEmpty() ? ownAircraft.getModelString() : extraModelString; m_currentMode = mode; - m_network->presetLoginMode(mode); - m_network->presetCallsign(ownAircraft.getCallsign()); - m_network->presetIcaoCodes(ownAircraft); - m_network->presetLiveryAndModelString(l, sendLivery, m, sendModelString); + m_fsdClient->setLoginMode(mode); + m_fsdClient->setCallsign(ownAircraft.getCallsign()); + m_fsdClient->setIcaoCodes(ownAircraft); + m_fsdClient->setLiveryAndModelString(l, sendLivery, m, sendModelString); + m_fsdClient->setClientName(sApp->swiftVersionChar()); + m_fsdClient->setVersion(CBuildConfig::getVersion().majorVersion(), CBuildConfig::getVersion().minorVersion()); + + int clientId = 0; + QString clientKey; + if (!getCmdLineClientIdAndKey(clientId, clientKey)) + { + clientId = CBuildConfig::vatsimClientId(); + clientKey = CBuildConfig::vatsimPrivateKey(); + } + + m_fsdClient->setClientIdAndKey(clientId, clientKey.toLocal8Bit()); + m_fsdClient->setClientCapabilities(Capabilities::AircraftInfo | Capabilities::FastPos | Capabilities::AtcInfo | Capabilities::AircraftConfig); + m_fsdClient->setServer(server); const CSimulatorPluginInfo sim = this->getIContextSimulator() ? this->getIContextSimulator()->getSimulatorPluginInfo() : CSimulatorPluginInfo(); - m_network->presetSimulatorInfo(sim); - m_network->initiateConnection(); + m_fsdClient->setSimType(sim); + m_fsdClient->setPilotRating(PilotRating::Student); + m_fsdClient->setAtcRating(AtcRating::Observer); + + m_fsdClient->connectToServer(); return CStatusMessage({ CLogCategory::validation() }, CStatusMessage::SeverityInfo, u"Connection pending " % server.getAddress() % u' ' % QString::number(server.getPort())); } CServer CContextNetwork::getConnectedServer() const { - return this->isConnected() ? m_network->getPresetServer() : CServer(); + return this->isConnected() ? m_fsdClient->getServer() : CServer(); } - INetwork::LoginMode CContextNetwork::getLoginMode() const + CLoginMode CContextNetwork::getLoginMode() const { if (this->isDebugEnabled()) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO; } - return m_currentMode; + return m_fsdClient->getLoginMode(); } CStatusMessage CContextNetwork::disconnectFromNetwork() { if (this->isDebugEnabled()) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO; } - if (m_network->isConnected() || m_network->isPendingConnection()) + if (m_fsdClient->isConnected() || m_fsdClient->isPendingConnection()) { - m_currentStatus = INetwork::Disconnecting; // as semaphore we are going to disconnect - m_currentMode = INetwork::LoginNormal; - m_network->terminateConnection(); + m_fsdClient->disconnectFromServer(); return CStatusMessage({ CLogCategory::validation() }, CStatusMessage::SeverityInfo, u"Connection terminating"); } else @@ -285,16 +295,12 @@ namespace BlackCore bool CContextNetwork::isConnected() const { if (this->isDebugEnabled()) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO; } - return m_network->isConnected(); + return m_fsdClient->isConnected(); } bool CContextNetwork::isPendingConnection() const { - // if underlying class says pending, we believe it. But not all states (e.g. disconnecting) are covered - if (m_network->isPendingConnection()) return true; - - // now check out own extra states, e.g. disconnecting - return INetwork::isPendingStatus(m_currentStatus); + return m_fsdClient->isPendingConnection(); } bool CContextNetwork::parseCommandLine(const QString &commandLine, const CIdentifier &originator) @@ -457,16 +463,16 @@ namespace BlackCore else if (parser.matchesCommand(".wallop")) { if (parser.countParts() < 2) { return false; } - if (!m_network) { return false; } + if (!m_fsdClient) { return false; } if (!this->isConnected()) { return false; } const QString wallopMsg = parser.part(1).simplified().trimmed(); - m_network->sendWallopMessage(wallopMsg); + m_fsdClient->sendTextMessage(TextMessageGroups::AllSups, wallopMsg); return true; } else if (parser.matchesCommand(".enable", ".unignore")) { if (parser.countParts() < 2) { return false; } - if (!m_network) { return false; } + if (!m_fsdClient) { return false; } if (!this->isConnected()) { return false; } const CCallsign cs(parser.part(1)); if (cs.isValid()) { this->updateAircraftEnabled(cs, true); } @@ -474,7 +480,7 @@ namespace BlackCore else if (parser.matchesCommand(".disable", ".ignore")) { if (parser.countParts() < 2) { return false; } - if (!m_network) { return false; } + if (!m_fsdClient) { return false; } if (!this->isConnected()) { return false; } const CCallsign cs(parser.part(1)); if (cs.isValid()) { this->updateAircraftEnabled(cs, false); } @@ -486,14 +492,14 @@ namespace BlackCore void CContextNetwork::sendTextMessages(const CTextMessageList &textMessages) { if (this->isDebugEnabled()) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO << textMessages; } - m_network->sendTextMessages(textMessages); + m_fsdClient->sendTextMessages(textMessages); } void CContextNetwork::sendFlightPlan(const CFlightPlan &flightPlan) { if (this->isDebugEnabled()) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO << flightPlan; } - m_network->sendFlightPlan(flightPlan); - m_network->sendFlightPlanQuery(this->ownAircraft().getCallsign()); + m_fsdClient->sendFlightPlan(flightPlan); + m_fsdClient->sendClientQueryFlightPlan(this->ownAircraft().getCallsign()); } CFlightPlan CContextNetwork::loadFlightPlanFromNetwork(const CCallsign &callsign) const @@ -599,9 +605,9 @@ namespace BlackCore { CLogMessage(this).info(u"Connected, own aircraft %1") << this->ownAircraft().getCallsignAsString(); - if (m_network) + if (m_fsdClient) { - const CServer server = m_network->getPresetServer(); + const CServer server = m_fsdClient->getServer(); emit this->connectedServerChanged(server); } } @@ -673,9 +679,9 @@ namespace BlackCore } // part to send to partner "forward" - if (m_network && !m_network->getPresetPartnerCallsign().isEmpty()) + if (m_fsdClient && !m_fsdClient->getPresetPartnerCallsign().isEmpty()) { - const CCallsign partnerCallsign = m_network->getPresetPartnerCallsign(); + const CCallsign partnerCallsign = m_fsdClient->getPresetPartnerCallsign(); // IMPORTANT: use messages AND NOT textMessages here, exclude messages from partner to avoid infinite roundtrips CTextMessageList relayedMessages; @@ -851,15 +857,15 @@ namespace BlackCore QString CContextNetwork::getNetworkStatistics(bool reset, const QString &separator) { if (this->isDebugEnabled()) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO; } - if (!m_network) { return QString(); } - return m_network->getNetworkStatisticsAsText(reset, separator); + if (!m_fsdClient) { return QString(); } + return m_fsdClient->getNetworkStatisticsAsText(reset, separator); } bool CContextNetwork::setNetworkStatisticsEnable(bool enabled) { if (this->isDebugEnabled()) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO; } - if (!m_network) { return false; } - return m_network->setStatisticsEnable(enabled); + if (!m_fsdClient) { return false; } + return m_fsdClient->setStatisticsEnable(enabled); } bool CContextNetwork::testAddAltitudeOffset(const CCallsign &callsign, const CLength &offset) @@ -870,8 +876,9 @@ namespace BlackCore QStringList CContextNetwork::getNetworkPresetValues() const { - if (!m_network) { return {}; } - return m_network->getPresetValues(); + if (!m_fsdClient) { return {}; } + return m_fsdClient->getPresetValues(); + return {}; } CAtcStation CContextNetwork::getOnlineStationForCallsign(const CCallsign &callsign) const @@ -1101,29 +1108,29 @@ namespace BlackCore void CContextNetwork::setFastPositionEnabledCallsigns(CCallsignSet &callsigns) { if (this->isDebugEnabled()) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO << callsigns; } - Q_ASSERT(m_network); - m_network->setInterimPositionReceivers(callsigns); + Q_ASSERT(m_fsdClient); + m_fsdClient->setInterimPositionReceivers(callsigns); } CCallsignSet CContextNetwork::getFastPositionEnabledCallsigns() const { if (this->isDebugEnabled()) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO; } - Q_ASSERT(m_network); - return m_network->getInterimPositionReceivers(); + Q_ASSERT(m_fsdClient); + return m_fsdClient->getInterimPositionReceivers(); } QString CContextNetwork::getLibraryInfo(bool detailed) const { if (this->isDebugEnabled()) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO << detailed; } - Q_ASSERT(m_network); - return m_network->getLibraryInfo(detailed); + Q_ASSERT(m_fsdClient); + return ""; } void CContextNetwork::testRequestAircraftConfig(const CCallsign &callsign) { if (this->isDebugEnabled()) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO << callsign; } - Q_ASSERT(m_network); - m_network->sendAircraftConfigQuery(callsign); + Q_ASSERT(m_fsdClient); + m_fsdClient->sendClientQueryAircraftConfig(callsign); } void CContextNetwork::testCreateDummyOnlineAtcStations(int number) @@ -1141,18 +1148,18 @@ namespace BlackCore void CContextNetwork::testReceivedAtisMessage(const CCallsign &callsign, const CInformationMessage &msg) { if (this->isDebugEnabled()) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO << callsign.asString(); } - if (this->network()) + if (this->fsdClient()) { - emit this->network()->atisReplyReceived(callsign, msg); + emit this->fsdClient()->atisReplyReceived(callsign, msg); } } void CContextNetwork::testReceivedTextMessages(const CTextMessageList &textMessages) { if (this->isDebugEnabled()) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO << textMessages.toQString(); } - if (this->network()) + if (this->fsdClient()) { - emit this->network()->textMessagesReceived(textMessages); + emit this->fsdClient()->textMessagesReceived(textMessages); } } @@ -1194,7 +1201,7 @@ namespace BlackCore // bind does not allow to define connection type, so we use receiver as workaround const QMetaObject::Connection uc; // unconnected - const QMetaObject::Connection c = rawFsdMessageReceivedSlot ? connect(m_network, &INetwork::rawFsdMessageReceived, receiver, rawFsdMessageReceivedSlot) : uc; + const QMetaObject::Connection c = rawFsdMessageReceivedSlot ? connect(m_fsdClient, &FSDClient::rawFsdMessage, receiver, rawFsdMessageReceivedSlot) : uc; Q_ASSERT_X(c || !rawFsdMessageReceivedSlot, Q_FUNC_INFO, "connect failed"); return c; } diff --git a/src/blackcore/context/contextnetworkimpl.h b/src/blackcore/context/contextnetworkimpl.h index 94757bb96..9186b19a0 100644 --- a/src/blackcore/context/contextnetworkimpl.h +++ b/src/blackcore/context/contextnetworkimpl.h @@ -21,7 +21,7 @@ #include "blackcore/blackcoreexport.h" #include "blackcore/context/contextnetwork.h" #include "blackcore/corefacadeconfig.h" -#include "blackcore/network.h" +#include "blackcore/fsd/fsdclient.h" #include "blackmisc/audio/voiceroomlist.h" #include "blackmisc/simulation/aircraftmodel.h" #include "blackmisc/simulation/airspaceaircraftsnapshot.h" @@ -150,7 +150,7 @@ namespace BlackCore //! \remarks public so values can be logged/monitored //! @{ //! Network library - INetwork *network() const { return m_network; } + Fsd::FSDClient *fsdClient() const { return m_fsdClient; } //! Airspace CAirspaceMonitor *airspace() const { return m_airspace; } @@ -293,8 +293,7 @@ namespace BlackCore private: CAirspaceMonitor *m_airspace = nullptr; - INetwork *m_network = nullptr; - INetwork::ConnectionStatus m_currentStatus = INetwork::Disconnected; //!< used to detect pending connections + Fsd::FSDClient *m_fsdClient = nullptr; BlackMisc::Network::CLoginMode m_currentMode = BlackMisc::Network::CLoginMode::Pilot; //!< current modeM QTimer *m_requestAircraftDataTimer = nullptr; //!< general updates such as frequencies, see requestAircraftDataUpdates() QTimer *m_requestAtisTimer = nullptr; //!< general updates such as ATIS diff --git a/src/blackcore/fsd/addatc.cpp b/src/blackcore/fsd/addatc.cpp new file mode 100644 index 000000000..6070dbeca --- /dev/null +++ b/src/blackcore/fsd/addatc.cpp @@ -0,0 +1,58 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +#include "addatc.h" +#include "serializer.h" +#include "blackmisc/logmessage.h" + +namespace BlackCore +{ + namespace Fsd + { + AddAtc::AddAtc() : MessageBase() + { } + + AddAtc::AddAtc(const QString &callsign, const QString &realName, const QString &cid, const QString &password, + AtcRating rating, int protocolRevision) + : MessageBase(callsign, "SERVER"), + m_cid(cid), + m_password(password), + m_rating(rating), + m_protocolRevision(protocolRevision), + m_realName(realName) + { + } + + QStringList AddAtc::toTokens() const + { + QStringList tokens; + tokens.push_back(m_sender); + tokens.push_back(m_receiver); + tokens.push_back(m_realName); + tokens.push_back(m_cid); + tokens.push_back(m_password); + tokens.push_back(toQString(m_rating)); + tokens.push_back(QString::number(m_protocolRevision)); + return tokens; + } + + AddAtc AddAtc::fromTokens(const QStringList &tokens) + { + if (tokens.size() < 7) + { + BlackMisc::CLogMessage(static_cast(nullptr)).warning(u"Wrong number of arguments."); + return {}; + } + + AtcRating rating = static_cast(tokens[5].toInt()); + int protocolRevision = tokens[6].toInt(); + return AddAtc(tokens[0], tokens[2], tokens[3], tokens[4], rating, protocolRevision); + } + } +} + diff --git a/src/blackcore/fsd/addatc.h b/src/blackcore/fsd/addatc.h new file mode 100644 index 000000000..eee657bd2 --- /dev/null +++ b/src/blackcore/fsd/addatc.h @@ -0,0 +1,68 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKCORE_FSD_ADDATC_H +#define BLACKCORE_FSD_ADDATC_H + +#include "messagebase.h" +#include "enums.h" + +#include +#include + +namespace BlackCore +{ + namespace Fsd + { + class BLACKCORE_EXPORT AddAtc : public MessageBase + { + public: + AddAtc(const QString &callsign, const QString &realName, const QString &cid, + const QString &password, AtcRating rating, int protocolRevision); + + QStringList toTokens() const; + static AddAtc fromTokens(const QStringList &tokens); + static QString pdu() { return QStringLiteral("#AA"); } + + QString cid() const { return m_cid; } + QString password() const { return m_password; } + AtcRating rating() const { return m_rating; } + int protocolRevision() const { return m_protocolRevision; } + QString realName() const { return m_realName; } + + private: + AddAtc(); + + QString m_cid; + QString m_password; + AtcRating m_rating = AtcRating::Unknown; + int m_protocolRevision = 0; + QString m_realName; + }; + + inline bool operator==(const AddAtc &lhs, const AddAtc &rhs) + { + return lhs.sender() == rhs.sender() && + lhs.receiver() == rhs.receiver() && + lhs.cid() == rhs.cid() && + lhs.password() == rhs.password() && + lhs.rating() == rhs.rating() && + lhs.protocolRevision() == rhs.protocolRevision() && + lhs.realName() == rhs.realName(); + } + + inline bool operator!=(const AddAtc &lhs, const AddAtc &rhs) + { + return !(lhs == rhs); + } + } +} + +#endif // guard diff --git a/src/blackcore/fsd/addpilot.cpp b/src/blackcore/fsd/addpilot.cpp new file mode 100644 index 000000000..9e85f5a2b --- /dev/null +++ b/src/blackcore/fsd/addpilot.cpp @@ -0,0 +1,54 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +#include "addpilot.h" +#include "serializer.h" + +#include "blackmisc/logmessage.h" + +namespace BlackCore +{ + namespace Fsd + { + AddPilot::AddPilot(const QString &callsign, const QString &cid, const QString &password, PilotRating rating, + int protocolRevision, SimType simType, const QString &realName) + : MessageBase(callsign, "SERVER"), + m_cid(cid), m_password(password), m_rating(rating), m_protocolRevision(protocolRevision), + m_simType(simType), m_realName(realName) + { } + + QStringList AddPilot::toTokens() const + { + QStringList tokens; + tokens.push_back(m_sender); + tokens.push_back(m_receiver); + tokens.push_back(m_cid); + tokens.push_back(m_password); + tokens.push_back(toQString(m_rating)); + tokens.push_back(QString::number(m_protocolRevision)); + tokens.push_back(toQString(m_simType)); + tokens.push_back(m_realName); + return tokens; + } + + AddPilot AddPilot::fromTokens(const QStringList &tokens) + { + if (tokens.size() < 6) + { + BlackMisc::CLogMessage(static_cast(nullptr)).warning(u"Wrong number of arguments."); + return {}; + }; + + PilotRating rating = static_cast(tokens[4].toInt()); + int protocolRevision = tokens[5].toInt(); + SimType simType = static_cast(tokens[6].toInt()); + AddPilot packet(tokens[0], tokens[2], tokens[3], rating, protocolRevision, simType, tokens[7]); + return packet; + } + } +} diff --git a/src/blackcore/fsd/addpilot.h b/src/blackcore/fsd/addpilot.h new file mode 100644 index 000000000..928d17b58 --- /dev/null +++ b/src/blackcore/fsd/addpilot.h @@ -0,0 +1,90 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKCORE_FSD_ADDPILOT_H +#define BLACKCORE_FSD_ADDPILOT_H + +#include "messagebase.h" +#include "enums.h" + +#include +#include + +namespace BlackCore +{ + namespace Fsd + { + //! FSD Message: Add Pilot + class BLACKCORE_EXPORT AddPilot : public MessageBase + { + public: + // Constructor + AddPilot(const QString &callsign, const QString &cid, const QString &password, PilotRating rating, int protocolRevision, SimType simType, const QString &realName); + + //! Get user cid + QString cid() const { return m_cid; } + + //! Get user password + QString password() const { return m_password; } + + //! Get pilot rating + PilotRating rating() const { return m_rating; } + + //! Get protocol version + int protocolVersion() const { return m_protocolRevision; } + + //! Get simulator type + SimType simType() const { return m_simType; } + + //! Get real name + QString realName() const { return m_realName; } + + //! Message converted to tokens + QStringList toTokens() const; + + //! Construct from tokens + static AddPilot fromTokens(const QStringList &tokens); + + //! PDU identifier + static QString pdu() { return "#AP"; } + + private: + AddPilot() : MessageBase() {} + + QString m_cid; + QString m_password; + PilotRating m_rating = PilotRating::Unknown; + int m_protocolRevision = 0; + SimType m_simType = SimType::Unknown; + QString m_realName; + }; + + //! AddPilot equal operator + inline bool operator==(const AddPilot &lhs, const AddPilot &rhs) + { + return lhs.sender() == rhs.sender() && + lhs.receiver() == rhs.receiver() && + lhs.cid() == rhs.cid() && + lhs.password() == rhs.password() && + lhs.rating() == rhs.rating() && + lhs.protocolVersion() == rhs.protocolVersion() && + lhs.simType() == rhs.simType() && + lhs.realName() == rhs.realName(); + } + + //! AddPilot not equal operator + inline bool operator!=(const AddPilot &lhs, const AddPilot &rhs) + { + return !(lhs == rhs); + } + } +} + +#endif // guard diff --git a/src/blackcore/fsd/atcdataupdate.cpp b/src/blackcore/fsd/atcdataupdate.cpp new file mode 100644 index 000000000..5188590bb --- /dev/null +++ b/src/blackcore/fsd/atcdataupdate.cpp @@ -0,0 +1,62 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +#include "atcdataupdate.h" +#include "serializer.h" + +#include "blackmisc/logmessage.h" + +using namespace BlackMisc::Network; + +namespace BlackCore +{ + namespace Fsd + { + AtcDataUpdate::AtcDataUpdate() : MessageBase() + { } + + AtcDataUpdate::AtcDataUpdate(const QString &sender, int frequencykHz, CFacilityType facility, int visibleRange, AtcRating rating, + double latitude, double longitude, int elevation) : + MessageBase(sender, {}), + m_frequencykHz(frequencykHz), + m_facility(facility), + m_visibleRange(visibleRange), + m_rating(rating), + m_latitude(latitude), + m_longitude(longitude), + m_elevation(elevation) + { } + + QStringList AtcDataUpdate::toTokens() const + { + QStringList tokens; + tokens.push_back(m_sender); + tokens.push_back(QString::number(m_frequencykHz - 100000)); + tokens.push_back(toQString(m_facility)); + tokens.push_back(QString::number(m_visibleRange)); + tokens.push_back(toQString(m_rating)); + tokens.push_back(QString::number(m_latitude, 'f', 5)); + tokens.push_back(QString::number(m_longitude, 'f', 5)); + tokens.push_back(QString::number(m_elevation)); + return tokens; + } + + AtcDataUpdate AtcDataUpdate::fromTokens(const QStringList &tokens) + { + if (tokens.size() < 7) + { + BlackMisc::CLogMessage(static_cast(nullptr)).warning(u"Wrong number of arguments."); + return {}; + }; + + AtcDataUpdate packet(tokens[0], tokens[1].toInt() + 100000, fromQString(tokens[2]), tokens[3].toInt(), fromQString(tokens[4]), + tokens[5].toDouble(), tokens[6].toDouble(), tokens[7].toInt()); + return packet; + } + } +} diff --git a/src/blackcore/fsd/atcdataupdate.h b/src/blackcore/fsd/atcdataupdate.h new file mode 100644 index 000000000..c9a0fca15 --- /dev/null +++ b/src/blackcore/fsd/atcdataupdate.h @@ -0,0 +1,68 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKCORE_FSD_ATCPOSITION_H +#define BLACKCORE_FSD_ATCPOSITION_H + +#include "messagebase.h" +#include "enums.h" +#include "blackmisc/network/facilitytype.h" + +#include + +namespace BlackCore +{ + namespace Fsd + { + class BLACKCORE_EXPORT AtcDataUpdate : public MessageBase + { + public: + AtcDataUpdate(const QString &sender, int frequencykHz, BlackMisc::Network::CFacilityType facility, int visibleRange, AtcRating rating, + double latitude, double longitude, int elevation); + + virtual ~AtcDataUpdate() {} + + QStringList toTokens() const; + static AtcDataUpdate fromTokens(const QStringList &tokens); + static QString pdu() { return "%"; } + + int m_frequencykHz = 0.0; + BlackMisc::Network::CFacilityType m_facility; + int m_visibleRange = 0.0; + AtcRating m_rating = AtcRating::Unknown; + double m_latitude = 0.0; + double m_longitude = 0.0; + int m_elevation = 0.0; + + private: + AtcDataUpdate(); + }; + + inline bool operator==(const AtcDataUpdate &lhs, const AtcDataUpdate &rhs) + { + return lhs.sender() == rhs.sender() && + lhs.receiver() == rhs.receiver() && + lhs.m_frequencykHz == rhs.m_frequencykHz && + lhs.m_facility == rhs.m_facility && + lhs.m_visibleRange == rhs.m_visibleRange && + lhs.m_rating == rhs.m_rating && + qFuzzyCompare(1 + lhs.m_latitude, 1 + rhs.m_latitude) && + qFuzzyCompare(1 + lhs.m_longitude, 1 + rhs.m_longitude) && + lhs.m_elevation == rhs.m_elevation; + } + + inline bool operator!=(const AtcDataUpdate &lhs, const AtcDataUpdate &rhs) + { + return !(lhs == rhs); + } + } +} + +#endif // guard diff --git a/src/blackcore/fsd/authchallenge.cpp b/src/blackcore/fsd/authchallenge.cpp new file mode 100644 index 000000000..3a667fc8d --- /dev/null +++ b/src/blackcore/fsd/authchallenge.cpp @@ -0,0 +1,47 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +#include "authchallenge.h" + +#include "blackmisc/logmessage.h" + +namespace BlackCore +{ + namespace Fsd + { + AuthChallenge::AuthChallenge() : MessageBase () + { } + + AuthChallenge::AuthChallenge(const QString &sender, const QString &target, const QString &challengeKey) : + MessageBase(sender, target), + m_challengeKey(challengeKey) + { } + + QStringList AuthChallenge::toTokens() const + { + auto tokens = QStringList {}; + tokens.push_back(m_sender); + tokens.push_back(m_receiver); + tokens.push_back(m_challengeKey); + return tokens; + } + + AuthChallenge AuthChallenge::fromTokens(const QStringList &tokens) + { + if (tokens.size() < 3) + { + BlackMisc::CLogMessage(static_cast(nullptr)).warning(u"Wrong number of arguments."); + return {}; + }; + return AuthChallenge(tokens[0], tokens[1], tokens[2]); + } + } +} + + + diff --git a/src/blackcore/fsd/authchallenge.h b/src/blackcore/fsd/authchallenge.h new file mode 100644 index 000000000..6c1dbdfc4 --- /dev/null +++ b/src/blackcore/fsd/authchallenge.h @@ -0,0 +1,51 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKCORE_FSD_AUTHCHALLENGE_H +#define BLACKCORE_FSD_AUTHCHALLENGE_H + +#include "messagebase.h" + +namespace BlackCore +{ + namespace Fsd + { + class BLACKCORE_EXPORT AuthChallenge : public MessageBase + { + public: + AuthChallenge(const QString &sender, const QString &target, const QString &challengeKey); + + virtual ~AuthChallenge() {} + + QStringList toTokens() const; + static AuthChallenge fromTokens(const QStringList &tokens); + static QString pdu() { return QStringLiteral("$ZC"); } + + QString m_challengeKey; + + private: + AuthChallenge(); + }; + + inline bool operator==(const AuthChallenge &lhs, const AuthChallenge &rhs) + { + return lhs.sender() == rhs.sender() && + lhs.receiver() == rhs.receiver() && + lhs.m_challengeKey == rhs.m_challengeKey; + } + + inline bool operator!=(const AuthChallenge &lhs, const AuthChallenge &rhs) + { + return !(lhs == rhs); + } + } +} + +#endif // guard diff --git a/src/blackcore/fsd/authresponse.cpp b/src/blackcore/fsd/authresponse.cpp new file mode 100644 index 000000000..da21d715f --- /dev/null +++ b/src/blackcore/fsd/authresponse.cpp @@ -0,0 +1,43 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +#include "authresponse.h" +#include "blackmisc/logmessage.h" + +namespace BlackCore +{ + namespace Fsd + { + AuthResponse::AuthResponse() : MessageBase() + {} + + AuthResponse::AuthResponse(const QString &sender, const QString &receiver, const QString &response) : + MessageBase(sender, receiver), + m_response(response) + { } + + QStringList AuthResponse::toTokens() const + { + auto tokens = QStringList {}; + tokens.push_back(m_sender); + tokens.push_back(m_receiver); + tokens.push_back(m_response); + return tokens; + } + + AuthResponse AuthResponse::fromTokens(const QStringList &tokens) + { + if (tokens.size() < 3) + { + BlackMisc::CLogMessage(static_cast(nullptr)).warning(u"Wrong number of arguments."); + return {}; + }; + return AuthResponse(tokens[0], tokens[1], tokens[2]); + } + } +} diff --git a/src/blackcore/fsd/authresponse.h b/src/blackcore/fsd/authresponse.h new file mode 100644 index 000000000..1697d09e5 --- /dev/null +++ b/src/blackcore/fsd/authresponse.h @@ -0,0 +1,53 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKCORE_FSD_AUTHRESPONSE_H +#define BLACKCORE_FSD_AUTHRESPONSE_H + +#include "messagebase.h" + +namespace BlackCore +{ + namespace Fsd + { + //! Responds to an authentication challenge. + //! The protocol for formulating and responding to auth challenges restricted. + class BLACKCORE_EXPORT AuthResponse : public MessageBase + { + public: + AuthResponse(const QString &sender, const QString &receiver, const QString &response); + + virtual ~AuthResponse() {} + + QStringList toTokens() const; + static AuthResponse fromTokens(const QStringList &tokens); + static QString pdu() { return QStringLiteral("$ZR"); } + + QString m_response; + + private: + AuthResponse(); + }; + + inline bool operator==(const AuthResponse &lhs, const AuthResponse &rhs) + { + return lhs.sender() == rhs.sender() && + lhs.receiver() == rhs.receiver() && + lhs.m_response == rhs.m_response; + } + + inline bool operator!=(const AuthResponse &lhs, const AuthResponse &rhs) + { + return !(lhs == rhs); + } + } +} + +#endif // guard diff --git a/src/blackcore/fsd/blackfsd.pro b/src/blackcore/fsd/blackfsd.pro new file mode 100644 index 000000000..ca7a2fa56 --- /dev/null +++ b/src/blackcore/fsd/blackfsd.pro @@ -0,0 +1,19 @@ +load(common_pre) + +QT += core dbus network + +TARGET = blackfsd +TEMPLATE = lib + +CONFIG += staticlib +CONFIG += blackmisc + +DEPENDPATH += . $$SourceRoot/src +INCLUDEPATH += . $$SourceRoot/src + +HEADERS += *.h +SOURCES += *.cpp + +DESTDIR = $$DestRoot/lib + +load(common_post) diff --git a/src/blackcore/fsd/clientidentification.cpp b/src/blackcore/fsd/clientidentification.cpp new file mode 100644 index 000000000..2e7729e68 --- /dev/null +++ b/src/blackcore/fsd/clientidentification.cpp @@ -0,0 +1,56 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +#include "clientidentification.h" + +#include "blackmisc/logmessage.h" + +namespace BlackCore +{ + namespace Fsd + { + ClientIdentification::ClientIdentification() : MessageBase() + { } + + ClientIdentification::ClientIdentification(const QString &sender, quint16 clientId, const QString &clientName, int clientVersionMajor, + int clientVersionMinor, const QString &userCid, const QString &sysUid, const QString &initialChallenge) + : MessageBase(sender, "SERVER"), + m_clientId(clientId), m_clientName(clientName), + m_clientVersionMajor(clientVersionMajor), m_clientVersionMinor(clientVersionMinor), + m_userCid(userCid), m_sysUid(sysUid), + m_initialChallenge(initialChallenge) + { } + + QStringList ClientIdentification::toTokens() const + { + auto tokens = QStringList {}; + tokens.push_back(m_sender); + tokens.push_back(m_receiver); + tokens.push_back(QString::number(m_clientId, 16)); + tokens.push_back(m_clientName); + tokens.push_back(QString::number(m_clientVersionMajor)); + tokens.push_back(QString::number(m_clientVersionMinor)); + tokens.push_back(m_userCid); + tokens.push_back(m_sysUid); + tokens.push_back(m_initialChallenge); + return tokens; + } + + ClientIdentification ClientIdentification::fromTokens(const QStringList &tokens) + { + if (tokens.size() < 8) + { + BlackMisc::CLogMessage(static_cast(nullptr)).warning(u"Wrong number of arguments."); + return {}; + }; + ClientIdentification packet(tokens[0], tokens[2].toUShort(nullptr, 16), tokens[3], tokens[4].toInt(), tokens[5].toInt(), + tokens[6], tokens[7], tokens.size() > 8 ? tokens[8] : QString()); + return packet; + } + } +} diff --git a/src/blackcore/fsd/clientidentification.h b/src/blackcore/fsd/clientidentification.h new file mode 100644 index 000000000..c38649249 --- /dev/null +++ b/src/blackcore/fsd/clientidentification.h @@ -0,0 +1,67 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKCORE_FSD_CLIENTIDENTIFICATION_H +#define BLACKCORE_FSD_CLIENTIDENTIFICATION_H + +#include "messagebase.h" + +namespace BlackCore +{ + namespace Fsd + { + //! This packet is sent by any client that supports the VATSIM client authentication protocol, + //! in response to a $DI query from the server. + //! Further information to the client authentication protocol is kept restricted. + class BLACKCORE_EXPORT ClientIdentification : public MessageBase + { + public: + ClientIdentification(const QString &sender, quint16 clientId, const QString &clientName, int clientVersionMajor, int clientVersionMinor, + const QString &userCid, const QString &sysUid, const QString &initialChallenge); + + virtual ~ClientIdentification() {} + + QStringList toTokens() const; + static ClientIdentification fromTokens(const QStringList &tokens); + static QString pdu() { return "$ID"; } + + std::uint16_t m_clientId; + QString m_clientName; + int m_clientVersionMajor; + int m_clientVersionMinor; + QString m_userCid; + QString m_sysUid; + QString m_initialChallenge; + + private: + ClientIdentification(); + }; + + inline bool operator==(const ClientIdentification &lhs, const ClientIdentification &rhs) + { + return lhs.sender() == rhs.sender() && + lhs.receiver() == rhs.receiver() && + lhs.m_clientId == rhs.m_clientId && + lhs.m_clientName == rhs.m_clientName && + lhs.m_clientVersionMajor == rhs.m_clientVersionMajor && + lhs.m_clientVersionMinor == rhs.m_clientVersionMinor && + lhs.m_userCid == rhs.m_userCid && + lhs.m_sysUid == rhs.m_sysUid && + lhs.m_initialChallenge == rhs.m_initialChallenge; + } + + inline bool operator!=(const ClientIdentification &lhs, const ClientIdentification &rhs) + { + return !(lhs == rhs); + } + } +} + +#endif // guard diff --git a/src/blackcore/fsd/clientquery.cpp b/src/blackcore/fsd/clientquery.cpp new file mode 100644 index 000000000..841c56704 --- /dev/null +++ b/src/blackcore/fsd/clientquery.cpp @@ -0,0 +1,51 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +#include "clientquery.h" +#include "serializer.h" + +#include "blackmisc/logmessage.h" + +namespace BlackCore +{ + namespace Fsd + { + ClientQuery::ClientQuery() : MessageBase() + { } + + ClientQuery::ClientQuery(const QString &sender, const QString &clientToBeQueried, ClientQueryType queryType, const QStringList &queryData) + : MessageBase(sender, clientToBeQueried), + m_queryType(queryType), + m_queryData(queryData) + { } + + QStringList ClientQuery::toTokens() const + { + QStringList tokens; + tokens.push_back(m_sender); + tokens.push_back(m_receiver); + tokens.push_back(toQString(m_queryType)); + tokens.append(m_queryData); + return tokens; + } + + ClientQuery ClientQuery::fromTokens(const QStringList &tokens) + { + if (tokens.size() < 3) + { + BlackMisc::CLogMessage(static_cast(nullptr)).debug(u"Wrong number of arguments."); + return {}; + }; + + QStringList payload; + if (tokens.size() > 3) { payload = tokens.mid(3); } + return ClientQuery(tokens[0], tokens[1], fromQString(tokens[2]), payload); + } + } +} + diff --git a/src/blackcore/fsd/clientquery.h b/src/blackcore/fsd/clientquery.h new file mode 100644 index 000000000..1d0b10404 --- /dev/null +++ b/src/blackcore/fsd/clientquery.h @@ -0,0 +1,59 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKCORE_FSD_CLIENTQUERY_H +#define BLACKCORE_FSD_CLIENTQUERY_H + +#include "messagebase.h" +#include "enums.h" + +namespace BlackCore +{ + namespace Fsd + { + //! This packet is used to query a client’s data. + //! + //! Current uses include requests for flight-plans, INF responses, realname details, current server and current frequency. + //! All requests are sent directly to the client to be queried, currently, except the flight-plan request which is sent to + //! the server. Therefore, the only client which will return an error is SERVER. + //! Other clients will simply not reply if the code is unrecognised or request invalid. + class BLACKCORE_EXPORT ClientQuery : public MessageBase + { + public: + ClientQuery(const QString &sender, const QString &clientToBeQueried, ClientQueryType queryType, const QStringList &queryData = {}); + virtual ~ClientQuery() {} + + QStringList toTokens() const; + static ClientQuery fromTokens(const QStringList &tokens); + static QString pdu() { return "$CQ"; } + + ClientQueryType m_queryType = ClientQueryType::Unknown; + QStringList m_queryData; + + private: + ClientQuery(); + }; + + inline bool operator==(const ClientQuery &lhs, const ClientQuery &rhs) + { + return lhs.sender() == rhs.sender() && + lhs.receiver() == rhs.receiver() && + lhs.m_queryType == rhs.m_queryType && + lhs.m_queryData == rhs.m_queryData; + } + + inline bool operator!=(const ClientQuery &lhs, const ClientQuery &rhs) + { + return !(lhs == rhs); + } + } +} + +#endif // guard diff --git a/src/blackcore/fsd/clientresponse.cpp b/src/blackcore/fsd/clientresponse.cpp new file mode 100644 index 000000000..30233b814 --- /dev/null +++ b/src/blackcore/fsd/clientresponse.cpp @@ -0,0 +1,50 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +#include "clientresponse.h" +#include "serializer.h" + +#include "blackmisc/logmessage.h" + +namespace BlackCore +{ + namespace Fsd + { + ClientResponse::ClientResponse() : MessageBase() + { } + + ClientResponse::ClientResponse(const QString &sender, const QString &receiver, ClientQueryType queryType, const QStringList &responseData) + : MessageBase(sender, receiver), + m_queryType(queryType), + m_responseData(responseData) + { } + + QStringList ClientResponse::toTokens() const + { + QStringList tokens; + tokens.push_back(m_sender); + tokens.push_back(m_receiver); + tokens.push_back(toQString(m_queryType)); + tokens.append(m_responseData); + return tokens; + } + + ClientResponse ClientResponse::fromTokens(const QStringList &tokens) + { + if (tokens.size() < 3) + { + BlackMisc::CLogMessage(static_cast(nullptr)).warning(u"Wrong number of arguments."); + return {}; + }; + + QStringList responseData; + if (tokens.size() > 3) { responseData = tokens.mid(3); } + return ClientResponse(tokens[0], tokens[1], fromQString(tokens[2]), responseData); + } + } +} diff --git a/src/blackcore/fsd/clientresponse.h b/src/blackcore/fsd/clientresponse.h new file mode 100644 index 000000000..a6066cee4 --- /dev/null +++ b/src/blackcore/fsd/clientresponse.h @@ -0,0 +1,57 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKCORE_FSD_ClientResponse_H +#define BLACKCORE_FSD_ClientResponse_H + +#include "messagebase.h" +#include "enums.h" + +namespace BlackCore +{ + namespace Fsd + { + //! This packet is used to respond to a client data request. + class BLACKCORE_EXPORT ClientResponse : public MessageBase + { + public: + ClientResponse(const QString &sender, const QString &receiver, ClientQueryType queryType, const QStringList &responseData); + + virtual ~ClientResponse() {} + + bool isUnknownQuery() const { return m_queryType == ClientQueryType::Unknown; } + + QStringList toTokens() const; + static ClientResponse fromTokens(const QStringList &tokens); + static QString pdu() { return "$CR"; } + + ClientQueryType m_queryType; + QStringList m_responseData; + + private: + ClientResponse(); + }; + + inline bool operator==(const ClientResponse &lhs, const ClientResponse &rhs) + { + return lhs.sender() == rhs.sender() && + lhs.receiver() == rhs.receiver() && + lhs.m_queryType == rhs.m_queryType && + lhs.m_responseData == rhs.m_responseData; + } + + inline bool operator!=(const ClientResponse &lhs, const ClientResponse &rhs) + { + return !(lhs == rhs); + } + } +} + +#endif // guard diff --git a/src/blackcore/fsd/deleteatc.cpp b/src/blackcore/fsd/deleteatc.cpp new file mode 100644 index 000000000..1d1a7d5d2 --- /dev/null +++ b/src/blackcore/fsd/deleteatc.cpp @@ -0,0 +1,43 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +#include "deleteatc.h" + +#include "blackmisc/logmessage.h" + +namespace BlackCore +{ + namespace Fsd + { + DeleteAtc::DeleteAtc() : MessageBase() + { } + + DeleteAtc::DeleteAtc(const QString &sender, const QString &cid) + : MessageBase(sender), + m_cid(cid) + { } + + QStringList DeleteAtc::toTokens() const + { + QStringList tokens; + tokens.push_back(m_sender); + tokens.push_back(m_cid); + return tokens; + } + + DeleteAtc DeleteAtc::fromTokens(const QStringList &tokens) + { + if (tokens.size() < 1) + { + BlackMisc::CLogMessage(static_cast(nullptr)).debug(u"Wrong number of arguments."); + return {}; + }; + return DeleteAtc(tokens[0], (tokens.size() >= 2) ? tokens[1] : QString()); + } + } +} diff --git a/src/blackcore/fsd/deleteatc.h b/src/blackcore/fsd/deleteatc.h new file mode 100644 index 000000000..b3093126f --- /dev/null +++ b/src/blackcore/fsd/deleteatc.h @@ -0,0 +1,50 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKCORE_FSD_DELETEATC_H +#define BLACKCORE_FSD_DELETEATC_H + +#include "messagebase.h" + +namespace BlackCore +{ + namespace Fsd + { + class BLACKCORE_EXPORT DeleteAtc : public MessageBase + { + public: + DeleteAtc(const QString &sender, const QString &cid); + virtual ~DeleteAtc() {} + + QStringList toTokens() const; + static DeleteAtc fromTokens(const QStringList &tokens); + static QString pdu() { return "#DA"; } + + QString m_cid; + + private: + DeleteAtc(); + }; + + inline bool operator==(const DeleteAtc &lhs, const DeleteAtc &rhs) + { + return lhs.sender() == rhs.sender() && + lhs.receiver() == rhs.receiver() && + lhs.m_cid == rhs.m_cid; + } + + inline bool operator!=(const DeleteAtc &lhs, const DeleteAtc &rhs) + { + return !(lhs == rhs); + } + } +} + +#endif // guard diff --git a/src/blackcore/fsd/deletepilot.cpp b/src/blackcore/fsd/deletepilot.cpp new file mode 100644 index 000000000..ab8d17f94 --- /dev/null +++ b/src/blackcore/fsd/deletepilot.cpp @@ -0,0 +1,46 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +#include "deletepilot.h" + +#include "blackmisc/logmessage.h" + +namespace BlackCore +{ + namespace Fsd + { + DeletePilot::DeletePilot() : MessageBase() + { } + + DeletePilot::DeletePilot(const QString &callsign, const QString &id) + : MessageBase(callsign), + m_cid(id) + { } + + QStringList DeletePilot::toTokens() const + { + auto tokens = QStringList {}; + tokens.push_back(m_sender); + tokens.push_back(m_cid); + return tokens; + } + + DeletePilot DeletePilot::fromTokens(const QStringList &tokens) + { + if (tokens.size() < 1) + { + BlackMisc::CLogMessage(static_cast(nullptr)).debug(u"Wrong number of arguments."); + return {}; + }; + // VATSIM FSD will always supply the CERTIFICATE ID when it rebroadcasts this PDU without regard for whether + // the client originally specified it. But other FSDs might not. + DeletePilot packet(tokens[0], (tokens.size() >= 2) ? tokens[1] : ""); + return packet; + } + } +} diff --git a/src/blackcore/fsd/deletepilot.h b/src/blackcore/fsd/deletepilot.h new file mode 100644 index 000000000..2b55a978c --- /dev/null +++ b/src/blackcore/fsd/deletepilot.h @@ -0,0 +1,52 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKCORE_FSD_DELETEPILOT_H +#define BLACKCORE_FSD_DELETEPILOT_H + +#include "messagebase.h" + +namespace BlackCore +{ + namespace Fsd + { + //! Used to notify the server of the intention to close the connection. + //! If a client receives this packet it should remove the client from its internal database. + class BLACKCORE_EXPORT DeletePilot : public MessageBase + { + public: + DeletePilot(const QString &sender, const QString &cid); + virtual ~DeletePilot() {} + + QStringList toTokens() const; + static DeletePilot fromTokens(const QStringList &tokens); + static QString pdu() { return QStringLiteral("#DP"); } + + QString m_cid; + + private: + DeletePilot(); + }; + + inline bool operator==(const DeletePilot& lhs, const DeletePilot& rhs) + { + return lhs.sender() == rhs.sender() && + lhs.receiver() == rhs.receiver() && + lhs.m_cid == rhs.m_cid; + } + + inline bool operator!=(const DeletePilot& lhs, const DeletePilot& rhs) + { + return !(lhs == rhs); + } + } +} + +#endif // guard diff --git a/src/blackcore/fsd/enums.h b/src/blackcore/fsd/enums.h new file mode 100644 index 000000000..7f18600c0 --- /dev/null +++ b/src/blackcore/fsd/enums.h @@ -0,0 +1,210 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKCORE_FSD_ENUMS_H +#define BLACKCORE_FSD_ENUMS_H + +#include + +namespace BlackCore +{ + namespace Fsd + { + /*! ATC ratings */ + enum class AtcRating + { + Unknown, /*!< Unknown */ + Observer, /*!< OBS */ + Student, /*!< S1 */ + Student2, /*!< S2 */ + Student3, /*!< S3 */ + Controller1, /*!< C1 */ + Controller2, /*!< C2 */ + Controller3, /*!< C3 */ + Instructor1, /*!< I1 */ + Instructor2, /*!< I2 */ + Instructor3, /*!< I3 */ + Supervisor, /*!< SUP */ + Administrator /*!< ADM */ + }; + + /*! Pilot ratings */ + enum class PilotRating + { + Unknown, /*!< Unknown rating */ + Student, /*!< P1 */ + VFR, /*!< P2 */ + IFR, /*!< P3 */ + Instructor, /*!< Instructor */ + Supervisor /*!< SUP */ + }; + + /*! Flight simulator type */ + enum class SimType + { + Unknown, /*!< Unknown simulator type */ + MSFS95, /*!< MS Flight Simulator 95 */ + MSFS98, /*!< MS Flight Simulator 98 */ + MSCFS, /*!< MS Combat Flight Simulator */ + MSFS2000, /*!< MS Flight Simulator 2000 */ + MSCFS2, /*!< MS Combat Flight Simulator 2 */ + MSFS2002, /*!< MS Flight Simulator 2002 */ + MSCFS3, /*!< MS Combat Flight Simulator 3 */ + MSFS2004, /*!< MS Flight Simulator 2004 */ + MSFSX, /*!< MS Flight Simulator X */ + XPLANE8, /*!< X-Plane 8 */ + XPLANE9, /*!< X-Plane 9 */ + XPLANE10, /*!< X-Plane 10 */ + XPLANE11, /*!< X-Plane 11 */ + P3Dv1, /*!< Prepar3D V1 */ + P3Dv2, /*!< Prepar3D V2 */ + P3Dv3, /*!< Prepar3D V3 */ + P3Dv4, /*!< Prepar3D V4 */ + FlightGear /*!< Flight Gear */ + }; + + //! Client query types + enum class ClientQueryType + { + Unknown, /*!< Unknown client query type */ + IsValidATC, /*!< Is this client working ATC or just an observer. */ + Capabilities, /*!< What capabilities does this client have? */ + Com1Freq, /*!< What is your COM1 Frequency? Response by pilot clients only. */ + RealName, /*!< What is your real-name (and other ATC data) */ + Server, /*!< What server are you on? */ + ATIS, /*!< What is your ATIS? Reponse by ATC clients only. */ + PublicIP, /*!< What is my public IP address? */ + INF, /*!< Supervisor Privileged Information Request. */ + FP, /*!< Send Cached Flight Plan. Response by SERVER. */ + AircraftConfig /*!< Aircraft Configuration */ + // There are many more which are only relevant to ATC clients. + }; + + //! Flight types + enum class FlightType + { + IFR, /*!< IFR flight rules. */ + VFR, /*!< Visual flight rules. */ + SVFR, /*!< Special visual flight rules. */ + DVFR /*!< Defense visual Flight Rules. */ + }; + + //! Server error codes + enum class ServerErrorCode + { + NoError, /*!< No error */ + CallsignInUse, /*!< Callsign in use */ + InvalidCallsign, /*!< Invalid callsign */ + AlreadyRegistered, /*!< Already registered */ + SyntaxError, /*!< Syntax error */ + InvalidSrcCallsign, /*!< Invalid source callsign */ + InvalidCidPassword, /*!< Invalid CID/password */ + NoSuchCallsign, /*!< No such callsign */ + NoFlightPlan, //!< No flightplan + NoWeatherProfile, /*!< No such weather profile */ + InvalidRevision, /*!< Invalid protocol revision */ + RequestedLevelTooHigh, /*!< Requested level too high */ + ServerFull, /*!< Too many clients connected */ + CidSuspended, /*!< CID/PID was suspended */ + InvalidCtrl, /*!< Not valid control */ + RatingTooLow, /*!< Rating too low for this position */ + InvalidClient, /*!< Unauthorized client software */ + AuthTimeout, /*!< Wrong server type */ + Unknown /*!< Unknown error */ + }; + + //! FSD Server type + enum class ServerType + { + LegacyFsd, //!< Legacy FSD + Vatsim //!< VATSIM server + }; + + //! Client capability flags */ + enum class Capabilities : int + { + /*! None. */ + None = (1 << 0), + + /*! Can accept ATIS responses. */ + AtcInfo = (1 << 1), + + /*! Can send/receive secondary visibility center points (ATC/Server only). */ + SecondaryPos = (1 << 2), + + /*! + * Can send/receive modern model packets. + * + * This should be the standard for any new pilot client. Also all older VATSIM clients + * starting from SB3 do support this capability. + * Aircraft info contains + * \li Aircraft ICAO identifier + * \li Airline ICAO identifier (optional) + * \li Airline livery (optional) + */ + AircraftInfo = (1 << 3), + + /*! Can send/receive inter-facility coordination packets (ATC only). */ + OngoingCoord = (1 << 4), + + /*! + * Can send/receive Interim position updates (pilot only) + * \deprecated Used only by Squawkbox with high precision errors. Use + * FastPos instead. + */ + InterminPos = (1 << 5), + + /*! Can send/receive fast position updates (pilot only). */ + FastPos = (1 << 6), + + /*! Stealth mode */ + Stealth = (1 << 7), + + /*! Aircraft Config */ + AircraftConfig = (1 << 8) + }; + + inline Capabilities operator | (Capabilities lhs, Capabilities rhs) + { + return static_cast(static_cast(lhs) | static_cast(rhs)); + } + + inline Capabilities& operator |= (Capabilities& lhs, Capabilities rhs) + { + lhs = lhs | rhs; + return lhs; + } + + inline bool operator & (Capabilities lhs, Capabilities rhs) + { + return static_cast(lhs) & static_cast(rhs); + } + + enum class AtisLineType + { + Unknown, + VoiceRoom, + TextMessage, + ZuluLogoff, + LineCount + }; + } +} + +Q_DECLARE_METATYPE(BlackCore::Fsd::AtcRating) +Q_DECLARE_METATYPE(BlackCore::Fsd::PilotRating) +Q_DECLARE_METATYPE(BlackCore::Fsd::SimType) +Q_DECLARE_METATYPE(BlackCore::Fsd::ClientQueryType) +Q_DECLARE_METATYPE(BlackCore::Fsd::FlightType) +Q_DECLARE_METATYPE(BlackCore::Fsd::ServerErrorCode) +Q_DECLARE_METATYPE(BlackCore::Fsd::ServerType) +Q_DECLARE_METATYPE(BlackCore::Fsd::Capabilities) + +#endif // guard diff --git a/src/blackcore/fsd/flightplan.cpp b/src/blackcore/fsd/flightplan.cpp new file mode 100644 index 000000000..dd8b91875 --- /dev/null +++ b/src/blackcore/fsd/flightplan.cpp @@ -0,0 +1,79 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +#include "flightplan.h" +#include "serializer.h" + +#include "blackmisc/logmessage.h" + +namespace BlackCore +{ + namespace Fsd + { + FlightPlan::FlightPlan() : MessageBase() { } + + FlightPlan::FlightPlan(const QString &sender, const QString &receiver, FlightType flightType, const QString &aircraftIcaoType, + int trueCruisingSpeed, const QString &depAirport, int estimatedDepTime, int actualDepTime, const QString &cruiseAlt, + const QString &destAirport, int hoursEnroute, int minutesEnroute, int fuelAvailHours, int fuelAvailMinutes, + const QString &altAirport, const QString &remarks, const QString &route) + : MessageBase(sender, receiver), + m_flightType(flightType), + m_aircraftIcaoType(aircraftIcaoType), + m_trueCruisingSpeed(trueCruisingSpeed), + m_depAirport(depAirport), + m_estimatedDepTime(estimatedDepTime), + m_actualDepTime(actualDepTime), + m_cruiseAlt(cruiseAlt), + m_destAirport(destAirport), + m_hoursEnroute(hoursEnroute), + m_minutesEnroute(minutesEnroute), + m_fuelAvailHours(fuelAvailHours), + m_fuelAvailMinutes(fuelAvailMinutes), + m_altAirport(altAirport), + m_remarks(remarks), + m_route(route) + { } + + QStringList FlightPlan::toTokens() const + { + auto tokens = QStringList {}; + tokens.push_back(m_sender); + tokens.push_back(m_receiver); + tokens.push_back(toQString(m_flightType)); + tokens.push_back(m_aircraftIcaoType); + tokens.push_back(QString::number(m_trueCruisingSpeed)); + tokens.push_back(m_depAirport); + tokens.push_back(QString::number(m_estimatedDepTime)); + tokens.push_back(QString::number(m_actualDepTime)); + tokens.push_back(m_cruiseAlt); + tokens.push_back(m_destAirport); + tokens.push_back(QString::number(m_hoursEnroute)); + tokens.push_back(QString::number(m_minutesEnroute)); + tokens.push_back(QString::number(m_fuelAvailHours)); + tokens.push_back(QString::number(m_fuelAvailMinutes)); + tokens.push_back(m_altAirport); + tokens.push_back(m_remarks); + tokens.push_back(m_route); + + Q_ASSERT(tokens.size() == 17); + return tokens; + } + + FlightPlan FlightPlan::fromTokens(const QStringList &tokens) + { + if (tokens.size() < 17) + { + BlackMisc::CLogMessage(static_cast(nullptr)).debug(u"Wrong number of arguments."); + return {}; + }; + return FlightPlan(tokens[0], tokens[1], fromQString(tokens[2]), tokens[3], tokens[4].toInt(), tokens[5], + tokens[6].toInt(), tokens[7].toInt(), tokens[8], tokens[9], tokens[10].toInt(), tokens[11].toInt(), tokens[12].toInt(), + tokens[13].toInt(), tokens[14], tokens[15], tokens[16]); + } + } +} diff --git a/src/blackcore/fsd/flightplan.h b/src/blackcore/fsd/flightplan.h new file mode 100644 index 000000000..644a44cd3 --- /dev/null +++ b/src/blackcore/fsd/flightplan.h @@ -0,0 +1,83 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKCORE_FSD_FLIGHTPLAN_H +#define BLACKCORE_FSD_FLIGHTPLAN_H + +#include "messagebase.h" +#include "enums.h" + +namespace BlackCore +{ + namespace Fsd + { + class BLACKCORE_EXPORT FlightPlan : public MessageBase + { + public: + FlightPlan(const QString &sender, const QString &receiver, FlightType flightType, const QString &aircraftIcaoType, + int trueCruisingSpeed, const QString &depAirport, int estimatedDepTime, int actualDepTime, const QString &cruiseAlt, + const QString &destAirport, int hoursEnroute, int minutesEnroute, int fuelAvailHours, int fuelAvailMinutes, + const QString &altAirport, const QString &remarks, const QString &route); + + virtual ~FlightPlan() {} + + QStringList toTokens() const; + static FlightPlan fromTokens(const QStringList &tokens); + static QString pdu() { return "$FP"; } + + FlightType m_flightType; + QString m_aircraftIcaoType; + int m_trueCruisingSpeed = 0; + QString m_depAirport; + int m_estimatedDepTime = 0; + int m_actualDepTime = 0; + QString m_cruiseAlt; + QString m_destAirport; + int m_hoursEnroute = 0; + int m_minutesEnroute = 0; + int m_fuelAvailHours = 0; + int m_fuelAvailMinutes = 0; + QString m_altAirport; + QString m_remarks; + QString m_route; + + protected: + FlightPlan(); + }; + + inline bool operator==(const FlightPlan &lhs, const FlightPlan &rhs) + { + return lhs.sender() == rhs.sender() && + lhs.receiver() == rhs.receiver() && + lhs.m_flightType == rhs.m_flightType && + lhs.m_aircraftIcaoType == rhs.m_aircraftIcaoType && + lhs.m_trueCruisingSpeed == rhs.m_trueCruisingSpeed && + lhs.m_depAirport == rhs.m_depAirport && + lhs.m_estimatedDepTime == rhs.m_estimatedDepTime && + lhs.m_actualDepTime == rhs.m_actualDepTime && + lhs.m_cruiseAlt == rhs.m_cruiseAlt && + lhs.m_destAirport == rhs.m_destAirport && + lhs.m_hoursEnroute == rhs.m_hoursEnroute && + lhs.m_minutesEnroute == rhs.m_minutesEnroute && + lhs.m_fuelAvailHours == rhs.m_fuelAvailHours && + lhs.m_fuelAvailMinutes == rhs.m_fuelAvailMinutes && + lhs.m_altAirport == rhs.m_altAirport && + lhs.m_remarks == rhs.m_remarks && + lhs.m_route == rhs.m_route; + } + + inline bool operator!=(const FlightPlan &lhs, const FlightPlan &rhs) + { + return !(lhs == rhs); + } + } +} + +#endif // guard diff --git a/src/blackcore/fsd/fsdclient.cpp b/src/blackcore/fsd/fsdclient.cpp new file mode 100644 index 000000000..1c5bf36e1 --- /dev/null +++ b/src/blackcore/fsd/fsdclient.cpp @@ -0,0 +1,1910 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +#include "fsdclient.h" + +#include "blackcore/application.h" +#include "blackcore/fsd/addatc.h" +#include "blackcore/fsd/addpilot.h" +#include "blackcore/fsd/atcdataupdate.h" +#include "blackcore/fsd/authchallenge.h" +#include "blackcore/fsd/authresponse.h" +#include "blackcore/fsd/clientidentification.h" +#include "blackcore/fsd/deleteatc.h" +#include "blackcore/fsd/deletepilot.h" +#include "blackcore/fsd/pilotdataupdate.h" +#include "blackcore/fsd/ping.h" +#include "blackcore/fsd/pong.h" +#include "blackcore/fsd/killrequest.h" +#include "blackcore/fsd/textmessage.h" +#include "blackcore/fsd/clientquery.h" +#include "blackcore/fsd/clientresponse.h" +#include "blackcore/fsd/flightplan.h" +#include "blackcore/fsd/fsdidentification.h" +#include "blackcore/fsd/serializer.h" +#include "blackcore/fsd/servererror.h" +#include "blackcore/fsd/interimpilotdataupdate.h" +#include "blackcore/fsd/planeinforequest.h" +#include "blackcore/fsd/planeinformation.h" +#include "blackcore/fsd/planeinforequestfsinn.h" +#include "blackcore/fsd/planeinformationfsinn.h" + +#include "blackconfig/buildconfig.h" +#include "blackmisc/logmessage.h" +#include "blackmisc/range.h" +#include "blackmisc/aviation/flightplan.h" +#include "blackmisc/network/rawfsdmessage.h" + +#include +#include +#include + +using namespace BlackConfig; +using namespace BlackCore::Vatsim; +using namespace BlackMisc; +using namespace BlackMisc::Aviation; +using namespace BlackMisc::Geo; +using namespace BlackMisc::Json; +using namespace BlackMisc::Network; +using namespace BlackMisc::PhysicalQuantities; +using namespace BlackMisc::Simulation; + +namespace BlackCore +{ + namespace Fsd + { + QString convertToUnicodeEscaped(const QString &str) + { + QString escaped; + for (const auto &ch : str) + { + const ushort code = ch.unicode(); + if (code < 0x80) + { + escaped += ch; + } + else + { + escaped += "\\u"; + escaped += QString::number(code, 16).rightJustified(4, '0'); + } + } + return escaped; + } + + FSDClient::FSDClient(IClientProvider *clientProvider, + IOwnAircraftProvider *ownAircraftProvider, + IRemoteAircraftProvider *remoteAircraftProvider, + QObject *parent) + : QObject(parent), + CClientAware(clientProvider), + COwnAircraftAware(ownAircraftProvider), + CRemoteAircraftAware(remoteAircraftProvider), + m_tokenBucket(10, CTime(5, CTimeUnit::s()), 1) + { + initializeMessageTypes(); + connect(&m_socket, &QTcpSocket::readyRead, this, &FSDClient::readDataFromSocket); + connect(&m_socket, qOverload(&QTcpSocket::error), this, &FSDClient::printSocketError); + + m_positionUpdateTimer.setObjectName(this->objectName().append(":m_positionUpdateTimer")); + connect(&m_positionUpdateTimer, &QTimer::timeout, this, &FSDClient::sendPilotDataUpdate); + + m_interimPositionUpdateTimer.setObjectName(this->objectName().append(":m_interimPositionUpdateTimer")); + connect(&m_interimPositionUpdateTimer, &QTimer::timeout, this, &FSDClient::sendInterimPilotDataUpdate); + + connect(&m_scheduledConfigUpdate, &QTimer::timeout, this, &FSDClient::sendIncrementalAircraftConfig); + m_scheduledConfigUpdate.setSingleShot(true); + + fsdMessageSettingsChanged(); + + if (!m_statistics && (CBuildConfig::isLocalDeveloperDebugBuild() || sApp->getOwnDistribution().isRestricted())) + { + CLogMessage("Enabled network statistics"); + m_statistics = true; + } + } + + void FSDClient::setClientIdAndKey(quint16 id, const QByteArray &key) + { + clientAuth = vatsim_auth_create(id, qPrintable(key)); + serverAuth = vatsim_auth_create(id, qPrintable(key)); + } + + void FSDClient::setServer(const CServer &server) + { + Q_ASSERT_X(m_connectionStatus.isDisconnected(), Q_FUNC_INFO, "Can't change server details while still connected"); + + m_server = server; + + if (m_server.getServerType() == CServer::FSDServerVatsim) + { + m_protocolRevision = PROTOCOL_REVISION_VATSIM_AUTH; + } + else + { + m_protocolRevision = PROTOCOL_REVISION_CLASSIC; + } + + const QString codecName(server.getFsdSetup().getTextCodec()); + Q_ASSERT_X(!codecName.isEmpty(), Q_FUNC_INFO, "Missing code name"); + m_fsdTextCodec = QTextCodec::codecForName(codecName.toLocal8Bit()); + if (!m_fsdTextCodec) { m_fsdTextCodec = QTextCodec::codecForName("utf-8"); } + } + + void FSDClient::setSimulatorInfo(const CSimulatorPluginInfo &simInfo) + { + Q_ASSERT_X(m_connectionStatus.isDisconnected(), Q_FUNC_INFO, "Can't change server details while still connected"); + m_simulatorInfo = simInfo; + } + + void FSDClient::setCallsign(const CCallsign &callsign) + { + Q_ASSERT_X(m_connectionStatus.isDisconnected(), Q_FUNC_INFO, "Can't change callsign while still connected"); + m_ownCallsign = callsign; + updateOwnCallsign(callsign); + } + + void FSDClient::setIcaoCodes(const CSimulatedAircraft &ownAircraft) + { + Q_ASSERT_X(m_connectionStatus.isDisconnected(), Q_FUNC_INFO, "Can't change ICAO codes while still connected"); + m_ownAircraftIcaoCode = ownAircraft.getAircraftIcaoCode(); + m_ownAirlineIcaoCode = ownAircraft.getAirlineIcaoCode(); + m_ownLivery = ownAircraft.getModel().getSwiftLiveryString(); + m_ownModelString = ownAircraft.getModelString(); + m_sendLiveryString = true; + m_sendMModelString = true; + updateOwnIcaoCodes(m_ownAircraftIcaoCode, m_ownAirlineIcaoCode); + } + + void FSDClient::setLiveryAndModelString(const QString &livery, bool sendLiveryString, const QString &modelString, bool sendModelString) + { + m_ownLivery = livery; + m_ownModelString = modelString; + m_sendLiveryString = sendLiveryString; + m_sendMModelString = sendModelString; + } + + void FSDClient::setSimType(const CSimulatorPluginInfo &simInfo) + { + //! \fixme Define recognized simulators somewhere */ + if (simInfo.getSimulator() == "fs9") + { + m_simType = SimType::MSFS2004; + } + else if (simInfo.getSimulator() == "fsx") + { + m_simType = SimType::MSFSX; + } + else if (simInfo.getSimulator() == "p3d") + { + m_simType = SimType::P3Dv4; + } + else if (simInfo.getSimulator() == "xplane") + { + m_simType = SimType::XPLANE11; + } + else + { + m_simType = SimType::Unknown; + } + } + + QStringList FSDClient::getPresetValues() const + { + const QStringList v = + { + m_ownModelString, + m_ownLivery, + m_ownAircraftIcaoCode.getDesignator(), + m_ownAirlineIcaoCode.getVDesignator(), + m_ownCallsign.asString(), + m_partnerCallsign.asString() + }; + return v; + } + + void FSDClient::connectToServer() + { + if (m_socket.isOpen()) { return; } + Q_ASSERT(! m_clientName.isEmpty()); + Q_ASSERT((m_versionMajor + m_versionMinor) > 0); + Q_ASSERT(m_capabilities != Capabilities::None); + + if (m_hostApplication.isEmpty()) { m_hostApplication = getSimulatorNameAndVersion().replace(':', ' '); } + + clearState(); + m_filterPasswordFromLogin = true; + + updateConnectionStatus(CConnectionStatus::Connecting); + + QString host = m_server.getAddress(); + quint16 port = m_server.getPort(); + m_socket.connectToHost(host, port); + startPositionTimers(); + } + + void FSDClient::disconnectFromServer() + { + this->stopPositionTimers(); + if (! m_socket.isOpen()) { return; } + + updateConnectionStatus(CConnectionStatus::Disconnecting); + + if (m_loginMode.isPilot()) { sendDeletePilot(); } + else if (m_loginMode.isObserver()) { sendDeleteAtc(); } + + m_socket.close(); + + updateConnectionStatus(CConnectionStatus::Disconnected); + this->clearState(); + } + + void FSDClient::sendLogin() + { + QString cid = m_server.getUser().getId(); + QString password = m_server.getUser().getPassword(); + QString realName = m_server.getUser().getRealName(); + + if (m_loginMode.isPilot()) + { + AddPilot pilotLogin(m_ownCallsign.asString(), cid, password, m_pilotRating, m_protocolRevision, m_simType, realName); + sendMessage(pilotLogin); + } + else if (m_loginMode.isObserver()) + { + AddAtc addAtc(m_ownCallsign.asString(), realName, cid, password, m_atcRating, m_protocolRevision); + sendMessage(addAtc); + } + } + + void FSDClient::sendDeletePilot() + { + QString cid = m_server.getUser().getId(); + DeletePilot deletePilot(m_ownCallsign.asString(), cid); + sendMessage(deletePilot); + } + + void FSDClient::sendDeleteAtc() + { + QString cid = m_server.getUser().getId(); + DeleteAtc deleteAtc(m_ownCallsign.asString(), cid); + sendMessage(deleteAtc); + } + + void FSDClient::sendPilotDataUpdate() + { + if (m_connectionStatus.isDisconnected() && ! m_unitTestMode) { return; } + CSimulatedAircraft myAircraft(getOwnAircraft()); + if (m_loginMode == BlackMisc::Network::CLoginMode::Observer) + { + sendAtcDataUpdate(myAircraft.latitude().value(CAngleUnit::deg()), myAircraft.longitude().value(CAngleUnit::deg())); + } + else + { + PilotDataUpdate pilotDataUpdate(myAircraft.getTransponderMode(), + m_ownCallsign.asString(), + static_cast(myAircraft.getTransponderCode()), + PilotRating::Unknown, + myAircraft.latitude().value(CAngleUnit::deg()), + myAircraft.longitude().value(CAngleUnit::deg()), + myAircraft.getAltitude().valueInteger(CLengthUnit::ft()), + myAircraft.getPressureAltitude().valueInteger(CLengthUnit::ft()), + myAircraft.getGroundSpeed().valueInteger(CSpeedUnit::kts()), + myAircraft.getPitch().value(CAngleUnit::deg()), + myAircraft.getBank().value(CAngleUnit::deg()), + myAircraft.getHeading().value(CAngleUnit::deg()), + myAircraft.getParts().isOnGround()); + sendMessage(pilotDataUpdate); + } + } + + void FSDClient::sendInterimPilotDataUpdate() + { + if (m_connectionStatus.isDisconnected()) { return; } + CSimulatedAircraft myAircraft(getOwnAircraft()); + InterimPilotDataUpdate interimPilotDataUpdate(m_ownCallsign.asString(), + QString(), + myAircraft.latitude().value(CAngleUnit::deg()), + myAircraft.longitude().value(CAngleUnit::deg()), + myAircraft.getAltitude().valueInteger(CLengthUnit::ft()), + myAircraft.getGroundSpeed().valueInteger(CSpeedUnit::kts()), + myAircraft.getPitch().value(CAngleUnit::deg()), + myAircraft.getBank().value(CAngleUnit::deg()), + myAircraft.getHeading().value(CAngleUnit::deg()), + myAircraft.getParts().isOnGround()); + + for (const auto &receiver : as_const(m_interimPositionReceivers)) + { + interimPilotDataUpdate.setReceiver(receiver.asString()); + sendMessage(interimPilotDataUpdate); + // statistics + } + } + + void FSDClient::sendAtcDataUpdate(double latitude, double longitude) + { + AtcDataUpdate atcDataUpdate(m_ownCallsign.asString(), 199998, CFacilityType::OBS, 10, AtcRating::Observer, latitude, longitude, 0); + sendMessage(atcDataUpdate); + } + + void FSDClient::sendPing(const QString &receiver) + { + qint64 msecSinceEpoch = QDateTime::currentMSecsSinceEpoch(); + QString timeString = QString::number(msecSinceEpoch); + + Ping ping(m_ownCallsign.asString(), receiver, timeString); + sendMessage(ping); + + // statistics + this->increaseStatisticsValue(QStringLiteral("sendPing")); + } + + void FSDClient::sendClientQueryIsValidAtc(const CCallsign &callsign) + { + sendClientQuery(ClientQueryType::IsValidATC, {}, { callsign.asString() }); + } + + void FSDClient::sendClientQueryCapabilities(const CCallsign &callsign) + { + sendClientQuery(ClientQueryType::Capabilities, callsign); + } + + void FSDClient::sendClientQueryCom1Freq(const CCallsign &callsign) + { + sendClientQuery(ClientQueryType::Com1Freq, callsign); + } + + void FSDClient::sendClientQueryRealName(const CCallsign &callsign) + { + sendClientQuery(ClientQueryType::RealName, callsign); + } + + void FSDClient::sendClientQueryServer(const CCallsign &callsign) + { + sendClientQuery(ClientQueryType::Server, callsign); + } + + void FSDClient::sendClientQueryAtis(const CCallsign &callsign) + { + sendClientQuery(ClientQueryType::ATIS, callsign); + } + + void FSDClient::sendClientQueryFlightPlan(const CCallsign callsign) + { + sendClientQuery(ClientQueryType::FP, {}, { callsign.toQString() } ); + } + + void FSDClient::sendClientQueryAircraftConfig(const CCallsign callsign) + { + QString data = QJsonDocument(JsonPackets::aircraftConfigRequest()).toJson(QJsonDocument::Compact); + data = convertToUnicodeEscaped(data); + sendClientQuery(ClientQueryType::AircraftConfig, callsign, { data }); + } + + void FSDClient::sendClientQuery(ClientQueryType queryType, const CCallsign &receiver, const QStringList &queryData) + { + if (queryType == ClientQueryType::Unknown) + { + return; + } + else if (queryType == ClientQueryType::IsValidATC) + { + ClientQuery clientQuery(m_ownCallsign.asString(), "SERVER", ClientQueryType::IsValidATC, queryData); + sendMessage(clientQuery); + return; + + } + else if (queryType == ClientQueryType::Capabilities) + { + ClientQuery clientQuery(m_ownCallsign.asString(), receiver.toQString(), ClientQueryType::Capabilities); + sendMessage(clientQuery); + return; + } + else if (queryType == ClientQueryType::Com1Freq) + { + ClientQuery clientQuery(m_ownCallsign.asString(), receiver.toQString(), ClientQueryType::Com1Freq); + sendMessage(clientQuery); + return; + } + else if (queryType == ClientQueryType::RealName) + { + ClientQuery clientQuery(m_ownCallsign.asString(), receiver.toQString(), ClientQueryType::RealName); + sendMessage(clientQuery); + return; + } + else if (queryType == ClientQueryType::Server) + { + ClientQuery clientQuery(m_ownCallsign.asString(), receiver.toQString(), ClientQueryType::Server); + sendMessage(clientQuery); + return; + } + else if (queryType == ClientQueryType::ATIS) + { + ClientQuery clientQuery(m_ownCallsign.asString(), receiver.toQString(), ClientQueryType::ATIS); + sendMessage(clientQuery); + if (m_serverType != ServerType::Vatsim) + { + m_pendingAtisQueries.insert(receiver, {}); + } + return; + } + else if (queryType == ClientQueryType::PublicIP) + { + ClientQuery clientQuery(m_ownCallsign.asString(), receiver.toQString(), ClientQueryType::PublicIP); + sendMessage(clientQuery); + return; + } + else if (queryType == ClientQueryType::INF) + { + ClientQuery clientQuery(m_ownCallsign.asString(), receiver.toQString(), ClientQueryType::INF); + sendMessage(clientQuery); + return; + } + else if (queryType == ClientQueryType::FP) + { + if (queryData.size() == 0) { return; } + ClientQuery clientQuery(m_ownCallsign.asString(), "SERVER", ClientQueryType::FP, queryData); + sendMessage(clientQuery); + return; + } + if (queryType == ClientQueryType::AircraftConfig) + { + if (queryData.size() == 0) { return; } + ClientQuery clientQuery(m_ownCallsign.asString(), receiver.toQString(), ClientQueryType::AircraftConfig, queryData); + sendMessage(clientQuery); + return; + } + + this->increaseStatisticsValue(QStringLiteral("sendClientQuery"), toQString(queryType)); + } + + void FSDClient::sendTextMessages(const CTextMessageList &messages) + { + if (messages.isEmpty()) { return; } + + CTextMessageList privateMessages = messages.getPrivateMessages(); + privateMessages.markAsSent(); + for (const auto &message : as_const(privateMessages)) + { + if (message.getRecipientCallsign().isEmpty()) { continue; } + TextMessage textMessage(m_ownCallsign.asString(), message.getRecipientCallsign().toQString(), message.getMessage()); + sendMessage(textMessage); + emit this->textMessageSent(message); + + this->increaseStatisticsValue(QStringLiteral("sendTextMessages")); + } + + CTextMessageList radioMessages = messages.getRadioMessages(); + radioMessages.markAsSent(); + QVector frequencies; + for (const auto &message : radioMessages) + { + // I could send the same message to n frequencies in one step + // if this is really required, I need to group by message + // currently I send individual messages + frequencies.clear(); + int freqkHz = message.getFrequency().valueInteger(CFrequencyUnit::kHz()); + if (m_server.getServerType() == CServer::FSDServerVatsim) + { + // VATSIM always drops the last 5 kHz. + freqkHz = freqkHz / 10 * 10; + } + frequencies.push_back(freqkHz); + sendRadioMessage(frequencies, message.getMessage()); + emit this->textMessageSent(message); + } + } + + void FSDClient::sendTextMessage(const CTextMessage &message) + { + sendTextMessages({message}); + } + + void FSDClient::sendTextMessage(TextMessageGroups receiverGroup, const QString &message) + { + QString receiver; + if (receiverGroup == TextMessageGroups::AllClients) { receiver = '*'; } + else if (receiverGroup == TextMessageGroups::AllAtcClients) { receiver = "*A"; } + else if (receiverGroup == TextMessageGroups::AllPilotClients) { receiver = "*P"; } + else if (receiverGroup == TextMessageGroups::AllSups) { receiver = "*S"; } + else { return; } + TextMessage textMessage(m_ownCallsign.asString(), receiver, message); + sendMessage(textMessage); + this->increaseStatisticsValue(QStringLiteral("sendTextMessages")); + } + + void FSDClient::sendTextMessage(const QString &receiver, const QString &message) + { + const CTextMessage msg (message, getOwnCallsign(), { receiver }); + sendTextMessage(msg); + } + + void FSDClient::sendRadioMessage(const QVector &frequencies, const QString &message) + { + QStringList receivers; + for (const int &frequency : frequencies) + { + receivers.push_back(QString("@%1").arg(frequency - 100000)); + } + + TextMessage radioMessage(m_ownCallsign.asString(), receivers.join('&'), message); + sendMessage(radioMessage); + this->increaseStatisticsValue(QStringLiteral("sendTextMessages")); + } + + void FSDClient::sendFlightPlan(const CFlightPlan &flightPlan) + { + // Removed with T353 although it is standard + // const QString route = QString(flightPlan.getRoute()).replace(" ", "."); + + const QString route = flightPlan.getRoute(); + const QString remarks = flightPlan.getRemarks(); + + //! \fixme that would be the official string, can this be used? + const QString alt = flightPlan.getCruiseAltitude().asFpVatsimAltitudeString(); + // const QString alt = flightPlan.getCruiseAltitude().asFpAltitudeString(); + + QString act = flightPlan.getCombinedPrefixIcaoSuffix(); + if (act.isEmpty()) { act = flightPlan.getAircraftIcao().getDesignator(); } // fallback + + FlightType flightType = FlightType::IFR; + switch (flightPlan.getFlightRules()) + { + case CFlightPlan::IFR: flightType = FlightType::IFR; break; + case CFlightPlan::VFR: flightType = FlightType::VFR; break; + case CFlightPlan::SVFR: flightType = FlightType::SVFR; break; + case CFlightPlan::DVFR: flightType = FlightType::DVFR; break; + } + + QList timePartsEnroute = flightPlan.getEnrouteTime().getHrsMinSecParts(); + QList timePartsFuel = flightPlan.getFuelTime().getHrsMinSecParts(); + + FlightPlan fp(m_ownCallsign.asString(), "SERVER", flightType, act, + flightPlan.getCruiseTrueAirspeed().valueInteger(CSpeedUnit::kts()), + flightPlan.getOriginAirportIcao().asString(), + flightPlan.getTakeoffTimePlanned().toUTC().toString("hhmm").toInt(), + flightPlan.getTakeoffTimeActual().toUTC().toString("hhmm").toInt(), + alt, + flightPlan.getDestinationAirportIcao().asString(), + timePartsEnroute[CTime::Hours], + timePartsEnroute[CTime::Minutes], + timePartsFuel[CTime::Hours], + timePartsFuel[CTime::Minutes], + flightPlan.getAlternateAirportIcao().asString(), + remarks, + route); + + sendMessage(fp); + this->increaseStatisticsValue(QStringLiteral("sendFlightPlan")); + } + + void FSDClient::sendPlaneInfoRequest(const BlackMisc::Aviation::CCallsign &receiver) + { + PlaneInfoRequest planeInfoRequest(m_ownCallsign.asString(), receiver.toQString()); + sendMessage(planeInfoRequest); + this->increaseStatisticsValue(QStringLiteral("sendPlaneInfoRequest")); + } + + void FSDClient::sendPlaneInfoRequestFsinn(const CCallsign &callsign) + { + Q_ASSERT_X(isConnected(), Q_FUNC_INFO, "Can't send to server when disconnected"); + const CSimulatedAircraft myAircraft(getOwnAircraft()); + QString modelString = m_ownModelString.isEmpty() ? myAircraft.getModelString() : m_ownModelString; + if (modelString.isEmpty()) { modelString = noModelString(); } + + PlaneInfoRequestFsinn planeInfoRequestFsinn(m_ownCallsign.asString(), callsign.toQString(), + myAircraft.getAirlineIcaoCodeDesignator(), + myAircraft.getAircraftIcaoCodeDesignator(), + myAircraft.getAircraftIcaoCombinedType(), + m_sendMModelString ? modelString : QString()); + sendMessage(planeInfoRequestFsinn); + this->increaseStatisticsValue(QStringLiteral("sendPlaneInfoRequestFsinn")); + } + + void FSDClient::sendPlaneInformation(const QString &receiver, const QString &aircraft, const QString &airline, const QString &livery) + { + PlaneInformation planeInformation(m_ownCallsign.asString(), receiver, aircraft, airline, livery); + sendMessage(planeInformation); + this->increaseStatisticsValue(QStringLiteral("sendPlaneInformation")); + } + + void FSDClient::sendPlaneInformationFsinn(const CCallsign &callsign) + { + if (m_connectionStatus.isDisconnected() && ! m_unitTestMode) { return; } + const CSimulatedAircraft myAircraft(getOwnAircraft()); + QString modelString = m_ownModelString.isEmpty() ? myAircraft.getModelString() : m_ownModelString; + if (modelString.isEmpty()) { modelString = noModelString(); } + + PlaneInformationFsinn planeInformationFsinn(m_ownCallsign.asString(), callsign.toQString(), + myAircraft.getAirlineIcaoCodeDesignator(), + myAircraft.getAircraftIcaoCodeDesignator(), + myAircraft.getAircraftIcaoCombinedType(), + m_sendMModelString ? modelString : QString()); + sendMessage(planeInformationFsinn); + this->increaseStatisticsValue(QStringLiteral("sendPlaneInformationFsinn")); + } + + void FSDClient::sendAircraftConfiguration(const QString &receiver, const QString &aircraftConfigJson) + { + sendClientQuery(ClientQueryType::AircraftConfig, receiver, { aircraftConfigJson }); + } + + void FSDClient::sendFsdMessage(const QString &message) + { + parseMessage(message); + } + + void FSDClient::sendAuthChallenge(const QString &challenge) + { + AuthChallenge pduAuthChallenge(m_ownCallsign.asString(), "SERVER", challenge); + sendMessage(pduAuthChallenge); + this->increaseStatisticsValue(QStringLiteral("sendAuthChallenge")); + } + + void FSDClient::sendAuthResponse(const QString &response) + { + AuthResponse pduAuthResponse(m_ownCallsign.asString(), "SERVER", response); + sendMessage(pduAuthResponse); + this->increaseStatisticsValue(QStringLiteral("sendAuthResponse")); + } + + void FSDClient::sendPong(const QString &receiver, const QString ×tamp) + { + Pong pong(m_ownCallsign.asString(), receiver, timestamp); + sendMessage(pong); + this->increaseStatisticsValue(QStringLiteral("sendPong")); + } + + void FSDClient::sendClientResponse(ClientQueryType queryType, const QString &receiver) + { + QStringList responseData; + if(queryType == ClientQueryType::IsValidATC) + { + qFatal("Never send that from the client"); + } + else if(queryType == ClientQueryType::Capabilities) + { + responseData.clear(); + if (m_capabilities & Capabilities::AtcInfo) + responseData.push_back(toQString(Capabilities::AtcInfo) + "=1"); + if (m_capabilities & Capabilities::SecondaryPos) + responseData.push_back(toQString(Capabilities::SecondaryPos) + "=1"); + if (m_capabilities & Capabilities::AircraftInfo) + responseData.push_back(toQString(Capabilities::AircraftInfo) + "=1"); + if (m_capabilities & Capabilities::OngoingCoord) + responseData.push_back(toQString(Capabilities::OngoingCoord) + "=1"); + if (m_capabilities & Capabilities::InterminPos) + responseData.push_back(toQString(Capabilities::InterminPos) + "=1"); + if (m_capabilities & Capabilities::FastPos) + responseData.push_back(toQString(Capabilities::FastPos) + "=1"); + if (m_capabilities & Capabilities::Stealth) + responseData.push_back(toQString(Capabilities::Stealth) + "=1"); + if (m_capabilities & Capabilities::AircraftConfig) + responseData.push_back(toQString(Capabilities::AircraftConfig) + "=1"); + ClientResponse clientResponse(m_ownCallsign.asString(), receiver, ClientQueryType::Capabilities, responseData); + sendMessage(clientResponse); + return; + } + else if(queryType == ClientQueryType::Com1Freq) + { + QString com1Frequency = QString::number(getOwnAircraft().getCom1System().getFrequencyActive().value(CFrequencyUnit::MHz()), 'f', 3); + responseData.push_back(com1Frequency); + ClientResponse pduClientResponse(m_ownCallsign.asString(), receiver, ClientQueryType::Com1Freq, responseData); + sendMessage(pduClientResponse); + return; + } + else if(queryType == ClientQueryType::RealName) + { + // real name + responseData.push_back(m_server.getUser().getRealName()); + // sector file in use (blank if pilot) + responseData.push_back({}); + // current user rating + if (m_loginMode.isObserver()) { responseData.push_back(toQString(m_atcRating)); } + else { responseData.push_back(toQString(m_pilotRating)); } + + ClientResponse pduClientQueryResponse(m_ownCallsign.asString(), receiver, ClientQueryType::RealName, responseData); + sendMessage(pduClientQueryResponse); + return; + } + else if(queryType == ClientQueryType::Server) + { + responseData.push_back(m_server.getAddress()); + ClientResponse pduClientQueryResponse(m_ownCallsign.asString(), receiver, ClientQueryType::Server, responseData); + sendMessage(pduClientQueryResponse); + } + else if(queryType == ClientQueryType::ATIS) + { + qFatal("Dont send this as pilot client!"); + } + else if(queryType == ClientQueryType::PublicIP) + { + qFatal("Dont send this as pilot client!"); + } + else if(queryType == ClientQueryType::INF) + { + QString userInfo; + QString cid = m_server.getUser().getId(); + CSimulatedAircraft myAircraft(getOwnAircraft()); + double latitude = getOwnAircraftPosition().latitude().value(CAngleUnit::deg()); + double longitude = getOwnAircraftPosition().longitude().value(CAngleUnit::deg()); + int altitude = getOwnAircraft().getAltitude().valueInteger(CLengthUnit::ft()); + QString realName = m_server.getUser().getRealName(); + + char sysuid[50]; + vatsim_get_system_unique_id(sysuid); + + userInfo += QString("CID=") % cid % " " % m_clientName % " IP=" % m_socket.localAddress().toString() % + " SYS_UID=" % sysuid % " FSVER=" % m_hostApplication % " LT=" % QString::number(latitude) % + " LO=" % QString::number(longitude) % " AL=" % QString::number(altitude) % + " " % realName; + + TextMessage textMessage(m_ownCallsign.asString(), receiver, userInfo); + sendMessage(textMessage); + return; + } + else if(queryType == ClientQueryType::FP) + { + qFatal("Dont send this as pilot client!"); + } + else if(queryType == ClientQueryType::AircraftConfig) + { + qFatal("This is not defined"); + } + + this->increaseStatisticsValue(QStringLiteral("sendClientResponse"), toQString(queryType)); + } + + void FSDClient::sendClientIdentification(const QString &fsdChallenge) + { + char sysuid[50]; + vatsim_get_system_unique_id(sysuid); + QString cid = m_server.getUser().getId(); + ClientIdentification clientIdentification(m_ownCallsign.asString(), vatsim_auth_get_client_id(clientAuth), m_clientName, m_versionMajor, m_versionMinor, cid, sysuid, fsdChallenge); + sendMessage(clientIdentification); + sendLogin(); + updateConnectionStatus(CConnectionStatus::Connected); + + this->increaseStatisticsValue(QStringLiteral("sendClientIdentification")); + } + + void FSDClient::sendIncrementalAircraftConfig() + { + if (!this->isConnected()) { return; } + if (!this->getSetupForServer().sendAircraftParts()) { return; } + const CAircraftParts currentParts(this->getOwnAircraftParts()); + + // If it hasn't changed, return + if (m_sentAircraftConfig == currentParts) { return; } + + if (!m_tokenBucket.tryConsume()) + { + // If timer is not yet active, start it + if (!m_scheduledConfigUpdate.isActive()) m_scheduledConfigUpdate.start(1000); + return; + } + + // Method could have been triggered by another change in aircraft config + // so a previous update might still be scheduled. Stop it. + if (m_scheduledConfigUpdate.isActive()) m_scheduledConfigUpdate.stop(); + const QJsonObject previousConfig = m_sentAircraftConfig.toJson(); + const QJsonObject currentConfig = currentParts.toJson(); + const QJsonObject incrementalConfig = getIncrementalObject(previousConfig, currentConfig); + + const QString dataStr = convertToUnicodeEscaped(QJsonDocument(QJsonObject { { "config", incrementalConfig } }).toJson(QJsonDocument::Compact)); + + sendAircraftConfiguration("@94835", dataStr); + m_sentAircraftConfig = currentParts; + } + + void FSDClient::initializeMessageTypes() + { + m_messageTypeMapping["#AA"] = MessageType::AddAtc; + m_messageTypeMapping["#AP"] = MessageType::AddPilot; + m_messageTypeMapping["%"] = MessageType::AtcDataUpdate; + m_messageTypeMapping["$ZC"] = MessageType::AuthChallenge; + m_messageTypeMapping["$ZR"] = MessageType::AuthResponse; + m_messageTypeMapping["$ID"] = MessageType::ClientIdentification; + m_messageTypeMapping["$CQ"] = MessageType::ClientQuery; + m_messageTypeMapping["$CR"] = MessageType::ClientResponse; + m_messageTypeMapping["#DA"] = MessageType::DeleteATC; + m_messageTypeMapping["#DP"] = MessageType::DeletePilot; + m_messageTypeMapping["$FP"] = MessageType::FlightPlan; + m_messageTypeMapping["$DI"] = MessageType::FsdIdentification; + m_messageTypeMapping["$!!"] = MessageType::KillRequest; + m_messageTypeMapping["@"] = MessageType::PilotDataUpdate; + m_messageTypeMapping["$PI"] = MessageType::Ping; + m_messageTypeMapping["$PO"] = MessageType::Pong; + m_messageTypeMapping["$ER"] = MessageType::ServerError; + m_messageTypeMapping["#TM"] = MessageType::TextMessage; + m_messageTypeMapping["#SB"] = MessageType::PilotClientCom; + } + + void FSDClient::handleAtcDataUpdate(const QStringList &tokens) + { + AtcDataUpdate atcDataUpdate = AtcDataUpdate::fromTokens(tokens); + + CFrequency freq(atcDataUpdate.m_frequencykHz, CFrequencyUnit::kHz()); + freq.switchUnit(CFrequencyUnit::MHz()); // we would not need to bother, but this makes it easier to identify + CLength networkRange(atcDataUpdate.m_visibleRange, CLengthUnit::NM()); + const CCallsign cs(atcDataUpdate.sender(), CCallsign::Atc); + + // Filter non-ATC like OBS stations, like pilots logging in as shared cockpit co-pilots. + if (atcDataUpdate.m_facility == CFacilityType::Unknown && !cs.isObserverCallsign()) { return; } + + const CLength range = fixAtcRange(networkRange, cs); + CCoordinateGeodetic position(atcDataUpdate.m_latitude, atcDataUpdate.m_longitude, 0); + + emit atcDataUpdateReceived(cs, freq, position, range); + } + + void FSDClient::handleAuthChallenge(const QStringList &tokens) + { + AuthChallenge authChallenge = AuthChallenge::fromTokens(tokens); + char response[33]; + vatsim_auth_generate_response(clientAuth, qPrintable(authChallenge.m_challengeKey), response); + sendAuthResponse(QString(response)); + + char challenge[33]; + vatsim_auth_generate_challenge(serverAuth, challenge); + m_lastServerAuthChallenge = QString(challenge); + sendAuthChallenge(m_lastServerAuthChallenge); + } + + void FSDClient::handleAuthResponse(const QStringList &tokens) + { + AuthResponse authResponse = AuthResponse::fromTokens(tokens); + + char expectedResponse[33]; + vatsim_auth_generate_response(serverAuth, qPrintable(m_lastServerAuthChallenge), expectedResponse); + if (authResponse.m_response != QString(expectedResponse)) + { + CLogMessage().error(u"The server you are connected to is not a VATSIM server. Disconnecting!"); + disconnectFromServer(); + } + } + + void FSDClient::handleDeleteATC(const QStringList &tokens) + { + DeleteAtc deleteAtc = DeleteAtc::fromTokens(tokens); + emit deleteAtcReceived(deleteAtc.m_cid); + } + + void FSDClient::handleDeletePilot(const QStringList &tokens) + { + DeletePilot deletePilot = DeletePilot::fromTokens(tokens); + const CCallsign cs(deletePilot.sender(), CCallsign::Aircraft); + clearState(cs); + emit deletePilotReceived(deletePilot.m_cid); + } + + void FSDClient::handleTextMessage(const QStringList &tokens) + { + TextMessage textMessage = TextMessage::fromTokens(tokens); + + const CCallsign sender(textMessage.sender()); + const CCallsign receiver(textMessage.receiver()); + + if (textMessage.m_type == TextMessage::PrivateMessage) + { + + // Other FSD servers send the controller ATIS as text message. The following conditions need to be met: + // * non-VATSIM server. VATSIM has a specific ATIS message + // * Receiver callsign must be owner callsign and not any type of broadcast. + // * We have requested the ATIS of this controller before. + if (m_server.getServerType() != CServer::FSDServerVatsim && + m_ownCallsign.asString() == textMessage.receiver() && + m_pendingAtisQueries.contains(sender)) + { + maybeHandleAtisReply(sender, receiver, textMessage.m_message); + return; + } + + CTextMessage tm(textMessage.m_message, sender, receiver); + tm.setCurrentUtcTime(); + emit textMessagesReceived({ tm }); + } + else if (textMessage.m_type == TextMessage::RadioMessage) + { + const CFrequency com1 = getOwnAircraft().getCom1System().getFrequencyActive(); + const CFrequency com2 = getOwnAircraft().getCom2System().getFrequencyActive(); + QList frequencies; + + for (int freqKhz : textMessage.m_frequencies) + { + CFrequency f(freqKhz, CFrequencyUnit::kHz()); + // VATSIM always drops the last 5 kHz. So round it to the correct channel spacing. + CComSystem::roundToChannelSpacing(f, CComSystem::ChannelSpacing25KHz); + if (f == com1 || f == com2) + { + frequencies.push_back(f); + } + } + if (frequencies.isEmpty()) { return; } + CTextMessageList messages(textMessage.m_message, frequencies, CCallsign(textMessage.sender())); + messages.setCurrentUtcTime(); + emit textMessagesReceived(messages); + } + } + + void FSDClient::handlePilotDataUpdate(const QStringList &tokens) + { + PilotDataUpdate dataUpdate = PilotDataUpdate::fromTokens(tokens); + const CCallsign callsign(dataUpdate.sender(), CCallsign::Aircraft); + + CAircraftSituation situation( + callsign, + CCoordinateGeodetic(dataUpdate.m_latitude, dataUpdate.m_longitude, dataUpdate.m_altitudeTrue), + CHeading(dataUpdate.m_heading, CHeading::True, CAngleUnit::deg()), + CAngle(dataUpdate.m_pitch, CAngleUnit::deg()), + CAngle(dataUpdate.m_bank, CAngleUnit::deg()), + CSpeed(dataUpdate.m_groundSpeed, CSpeedUnit::kts())); + situation.setPressureAltitude(CAltitude(dataUpdate.m_altitudePressure, CAltitude::MeanSeaLevel, CAltitude::PressureAltitude, CLengthUnit::ft())); + situation.setOnGround(dataUpdate.m_onGround); + + // Ref T297, default offset time + situation.setCurrentUtcTime(); + const qint64 offsetTimeMs = receivedPositionFixTsAndGetOffsetTime(situation.getCallsign(), situation.getMSecsSinceEpoch()); + situation.setTimeOffsetMs(offsetTimeMs); + + // I did have a situation where I got wrong transponder codes (KB) + // So I now check for a valid code in order to detect such codes + CTransponder transponder; + if (CTransponder::isValidTransponderCode(dataUpdate.m_transponderCode)) + { + transponder = CTransponder(dataUpdate.m_transponderCode, dataUpdate.m_transponderMode); + } + else + { + if (CBuildConfig::isLocalDeveloperDebugBuild()) + { + CLogMessage(this).debug(u"Wrong transponder code '%1' for '%2'") << dataUpdate.m_transponderCode << callsign; + } + + // I set a default: IFR standby is a reasonable default + transponder = CTransponder(2000, CTransponder::StateStandby); + } + emit pilotDataUpdateReceived(situation, transponder); + } + + void FSDClient::handlePing(const QStringList &tokens) + { + Ping ping = Ping::fromTokens(tokens); + sendPong(ping.sender(), ping.m_timestamp); + } + + void FSDClient::handlePong(const QStringList &tokens) + { + Pong pong = Pong::fromTokens(tokens); + qint64 msecSinceEpoch = QDateTime::currentMSecsSinceEpoch(); + qint64 elapsedTime = msecSinceEpoch - pong.m_timestamp.toLongLong(); + emit pongReceived(pong.sender(), elapsedTime); + } + + void FSDClient::handleKillRequest(const QStringList &tokens) + { + KillRequest killRequest = KillRequest::fromTokens(tokens); + emit killRequestReceived(killRequest.m_reason); + disconnectFromServer(); + } + + void FSDClient::handleFlightPlan(const QStringList &tokens) + { + FlightPlan fp = FlightPlan::fromTokens(tokens); + + CFlightPlan::FlightRules rules = CFlightPlan::VFR; + + switch (fp.m_flightType) + { + case FlightType::VFR: rules = CFlightPlan::VFR; break; + case FlightType::IFR: rules = CFlightPlan::IFR; break; + case FlightType::DVFR: rules = CFlightPlan::DVFR; break; + case FlightType::SVFR: rules = CFlightPlan::SVFR; break; + } + + QString cruiseAltString = fp.m_cruiseAlt.trimmed(); + if (!cruiseAltString.isEmpty() && is09OnlyString(cruiseAltString)) + { + int ca = cruiseAltString.toInt(); + // we have a 0-9 only string + // we assume values like 24000 as FL + // RefT323, also major tool such as PFPX and Simbrief do so + if (rules == CFlightPlan::IFR) + { + if (ca >= 1000) + { + cruiseAltString = u"FL" % QString::number(ca / 100); + } + else + { + cruiseAltString = u"FL" % cruiseAltString; + } + } + else // VFR + { + if (ca >= 5000) + { + cruiseAltString = u"FL" % QString::number(ca / 100); + } + else + { + cruiseAltString = cruiseAltString % u"ft"; + } + } + } + CAltitude cruiseAlt; + cruiseAlt.parseFromString(cruiseAltString, CPqString::SeparatorBestGuess); + + const QString depTimePlanned = QString("0000").append(QString::number(fp.m_estimatedDepTime)).right(4); + const QString depTimeActual = QString("0000").append(QString::number(fp.m_actualDepTime)).right(4); + + const CCallsign callsign(fp.sender(), CCallsign::Aircraft); + const CFlightPlan flightPlan( + callsign, + fp.m_aircraftIcaoType, + fp.m_depAirport, + fp.m_destAirport, + fp.m_altAirport, + fromStringUtc(depTimePlanned, "hhmm"), + fromStringUtc(depTimeActual, "hhmm"), + CTime(fp.m_hoursEnroute * 60 + fp.m_minutesEnroute, CTimeUnit::min()), + CTime(fp.m_fuelAvailHours * 60 + fp.m_fuelAvailMinutes, CTimeUnit::min()), + cruiseAlt, + CSpeed(fp.m_trueCruisingSpeed, CSpeedUnit::kts()), + rules, + fp.m_route, + fp.m_remarks + ); + + emit flightPlanReceived(callsign, flightPlan); + } + + void FSDClient::handleClientQuery(const QStringList &tokens) + { + ClientQuery clientQuery = ClientQuery::fromTokens(tokens); + + if (clientQuery.m_queryType == ClientQueryType::Unknown) + { + return; + } + else if (clientQuery.m_queryType == ClientQueryType::IsValidATC) + { + // This is usually sent to the server only. If it ever arrives here, just ignore it. + return; + } + else if (clientQuery.m_queryType == ClientQueryType::Capabilities) + { + sendClientResponse(ClientQueryType::Capabilities, clientQuery.sender()); + return; + } + else if (clientQuery.m_queryType == ClientQueryType::Com1Freq) + { + sendClientResponse(ClientQueryType::Com1Freq, clientQuery.sender()); + return; + } + else if (clientQuery.m_queryType == ClientQueryType::RealName) + { + sendClientResponse(ClientQueryType::RealName, clientQuery.sender()); + return; + } + else if (clientQuery.m_queryType == ClientQueryType::Server) + { + sendClientResponse(ClientQueryType::Server, clientQuery.sender()); + return; + } + else if (clientQuery.m_queryType == ClientQueryType::ATIS) + { + // This is answered by ATC clients only. If we get such a request, ignore it. + return; + } + else if (clientQuery.m_queryType == ClientQueryType::PublicIP) + { + // This is usually sent to the server only. If it ever arrives here, just ignore it. + return; + } + else if (clientQuery.m_queryType == ClientQueryType::INF) + { + sendClientResponse(ClientQueryType::INF, clientQuery.sender()); + return; + } + else if (clientQuery.m_queryType == ClientQueryType::FP) + { + // This is usually sent to the server only. If it ever arrives here, just ignore it. + return; + } + else if (clientQuery.m_queryType == ClientQueryType::AircraftConfig) + { + QStringList aircraftConfigTokens = tokens.mid(3); + QString aircraftConfigJson = aircraftConfigTokens.join(":"); + + const CCallsign callsign(clientQuery.sender(), CCallsign::Aircraft); + + QJsonParseError parserError; + const QByteArray json = aircraftConfigJson.toUtf8(); + const QJsonDocument doc = QJsonDocument::fromJson(json, &parserError); + + if (parserError.error != QJsonParseError::NoError) + { + CLogMessage(this).warning(u"Failed to parse aircraft config packet: '%1' packet: '%2'") << parserError.errorString() << QString(json); + return; // we cannot parse the packet, so we give up here + } + + const QJsonObject packet = doc.object(); + if (packet == JsonPackets::aircraftConfigRequest()) + { + // this MUST work for NOT IN RANGE aircraft as well + QJsonObject config = this->getOwnAircraftParts().toJson(); + config.insert(CAircraftParts::attributeNameIsFullJson(), true); + QString data = QJsonDocument(QJsonObject { { "config", config } }).toJson(QJsonDocument::Compact); + data = convertToUnicodeEscaped(data); + sendAircraftConfiguration(clientQuery.sender(), data); + return; + } + + const bool inRange = isAircraftInRange(callsign); + if (!inRange) { return; } // sort out all broadcasted we DO NOT NEED + if (!getSetupForServer().receiveAircraftParts()) { return; } + const QJsonObject config = doc.object().value("config").toObject(); + if (config.empty()) { return; } + + const qint64 offsetTimeMs = currentOffsetTime(callsign); + emit aircraftConfigReceived(clientQuery.sender(), config, offsetTimeMs); + return; + } + } + + void FSDClient::handleClientReponse(const QStringList &tokens) + { + ClientResponse clientResponse = ClientResponse::fromTokens(tokens); + if (clientResponse.isUnknownQuery()) { return; } + QString sender = clientResponse.sender(); + + QString responseData1; + QString responseData2; + if (clientResponse.m_responseData.size() > 0) + { + responseData1 = clientResponse.m_responseData.at(0); + } + + if (clientResponse.m_responseData.size() > 1) + { + responseData2 = clientResponse.m_responseData.at(1); + } + + if (clientResponse.m_queryType == ClientQueryType::IsValidATC) + { + emit validAtcResponseReceived(responseData2, responseData1 == u"Y"); + return; + } + else if (clientResponse.m_queryType == ClientQueryType::Capabilities) + { + Capabilities capabilities = Capabilities::None; + for (int i = 0; i < clientResponse.m_responseData.size(); ++i) + { + QString keyValuePair = clientResponse.m_responseData.at(i); + if (keyValuePair.count('=') != 1) { continue; } + + QStringList split = keyValuePair.split('='); + QString key = split.at(0); + QString value = split.at(1); + + if (value == "1") + { + capabilities |= fromQString(key); + } + } + + CClient::Capabilities caps = CClient::None; + if (capabilities & Capabilities::AtcInfo) { caps |= CClient::FsdAtisCanBeReceived; } + if (capabilities & Capabilities::FastPos) { caps |= CClient::FsdWithInterimPositions; } + if (capabilities & Capabilities::AircraftInfo) { caps |= CClient::FsdWithIcaoCodes; } + if (capabilities & Capabilities::AircraftConfig) { caps |= CClient::FsdWithAircraftConfig; } + + emit capabilityResponseReceived(clientResponse.sender(), caps); + return; + } + else if (clientResponse.m_queryType == ClientQueryType::Com1Freq) + { + emit com1FrequencyResponseReceived(clientResponse.sender(), responseData1); + return; + } + else if (clientResponse.m_queryType == ClientQueryType::RealName) + { + // The response also includes sector name and pilot rating, but we ignore them here. + emit realNameResponseReceived(clientResponse.sender(), responseData1); + return; + } + else if (clientResponse.m_queryType == ClientQueryType::Server) + { + emit serverResponseReceived(clientResponse.sender(), responseData1); + return; + } + else if (clientResponse.m_queryType == ClientQueryType::ATIS) + { + if (responseData1.isEmpty()) + { + // networkLog(vatSeverityDebug, "VatFsdClient::handleClientQueryReponse", "ATIS line type cannot be empty!"); + return; + } + updateAtisMap(clientResponse.sender(), fromQString(responseData1), responseData2); + return; + } + else if (clientResponse.m_queryType == ClientQueryType::PublicIP) + { + // To be implemented if needed + return; + } + else if (clientResponse.m_queryType == ClientQueryType::INF) + { + // To be implemented if needed + return; + } + else if (clientResponse.m_queryType == ClientQueryType::FP) + { + // FP is sent back as a $FP answer from the server and never as part of a client response. + return; + } + else if (clientResponse.m_queryType == ClientQueryType::AircraftConfig) + { + // Currently not existing. + return; + } + } + + void FSDClient::handleServerError(const QStringList &tokens) + { + ServerError serverError = ServerError::fromTokens(tokens); + switch (serverError.m_errorNumber) + { + case ServerErrorCode::CallsignInUse: CLogMessage(this).error(u"The requested callsign is already taken"); break; + case ServerErrorCode::InvalidCallsign: CLogMessage(this).error(u"The requested callsign is not valid"); break; + case ServerErrorCode::InvalidCidPassword: CLogMessage(this).error(u"Wrong user ID or password, inactive account"); break; + case ServerErrorCode::InvalidRevision: CLogMessage(this).error(u"This server does not support our protocol version"); break; + case ServerErrorCode::RequestedLevelTooHigh: CLogMessage(this).error(u"You are not authorized to use the requested pilot rating"); break; + case ServerErrorCode::ServerFull: CLogMessage(this).error(u"The server is full"); break; + case ServerErrorCode::CidSuspended: CLogMessage(this).error(u"Your user account is suspended"); break; + case ServerErrorCode::RatingTooLow: CLogMessage(this).error(u"You are not authorized to use the requested rating"); break; + case ServerErrorCode::InvalidClient: CLogMessage(this).error(u"This software is not authorized for use on this network"); break; + + case ServerErrorCode::NoError: CLogMessage(this).info(u"OK"); break; + case ServerErrorCode::SyntaxError: CLogMessage(this).info(u"Malformed packet: Syntax error: %1") << serverError.m_causingParameter; break; + case ServerErrorCode::InvalidSrcCallsign: CLogMessage(this).info(u"FSD message was using an invalid callsign: %1 (%2)") << serverError.m_causingParameter << serverError.m_description; break; + case ServerErrorCode::NoSuchCallsign: CLogMessage(this).info(u"FSD Server: no such callsign: %1 %2") << serverError.m_causingParameter << serverError.m_description; break; + case ServerErrorCode::NoFlightPlan: CLogMessage(this).info(u"FSD Server: no flight plan"); break; + case ServerErrorCode::NoWeatherProfile: CLogMessage(this).info(u"FSD Server: requested weather profile does not exist"); break; + + // we have no idea what these mean + case ServerErrorCode::AlreadyRegistered: CLogMessage(this).info(u"Server says already registered: %1") << serverError.m_description; break; + case ServerErrorCode::InvalidCtrl: CLogMessage(this).info(u"Server invalid control: %1") << serverError.m_description; break; + case ServerErrorCode::Unknown: CLogMessage(this).info(u"Server sent unknown error code: %1 (%2)") << serverError.m_causingParameter << serverError.m_description; break; + case ServerErrorCode::AuthTimeout: CLogMessage(this).info(u"Client did not authenticate in time"); break; + } + if (serverError.isFatalError()) { disconnectFromServer(); } + } + + void FSDClient::handleCustomPilotPacket(const QStringList &tokens) + { + const QString subType = tokens.at(2); + + if (subType == u"PIR") + { + PlaneInfoRequest planeInfoRequest = PlaneInfoRequest::fromTokens(tokens); + + const QString airlineIcao = m_server.getFsdSetup().force3LetterAirlineCodes() ? getOwnAircraft().getAirlineIcaoCode().getDesignator() + : getOwnAircraft().getAirlineIcaoCode().getVDesignator(); + const QString acTypeICAO = getOwnAircraft().getAircraftIcaoCode().getDesignator(); + const QString livery = getOwnAircraft().getModel().getSwiftLiveryString(); + + sendPlaneInformation(planeInfoRequest.sender(), acTypeICAO, airlineIcao, livery); + } + else if (subType == "PI") + { + if (tokens.size() > 6 && tokens.at(3) == "X") + { + // This is the old version of a plane info request and no active client should ever send it. + } + else if (tokens.size() > 4 && tokens.at(3) == "GEN") + { + PlaneInformation planeInformation = PlaneInformation::fromTokens(tokens); + emit planeInformationReceived(planeInformation.sender(), planeInformation.m_aircraft, planeInformation.m_airline, planeInformation.m_livery); + } + } + else if (subType == "I") + { + // SquawkBox' interim pilot position. This one is producing too many precision errors. Therefore ignore it. + } + else if (subType == "VI") + { + // swift's updated interim pilot update. + if (!isInterimPositionReceivingEnabledForServer()) { return; } + + + InterimPilotDataUpdate interimPilotDataUpdate = InterimPilotDataUpdate::fromTokens(tokens); + const CCallsign callsign(interimPilotDataUpdate.sender(), CCallsign::Aircraft); + + CAircraftSituation situation( + callsign, + CCoordinateGeodetic(interimPilotDataUpdate.m_latitude, interimPilotDataUpdate.m_longitude, interimPilotDataUpdate.m_altitudeTrue), + CHeading(interimPilotDataUpdate.m_heading, CHeading::True, CAngleUnit::deg()), + CAngle(interimPilotDataUpdate.m_pitch, CAngleUnit::deg()), + CAngle(interimPilotDataUpdate.m_bank, CAngleUnit::deg()), + CSpeed(interimPilotDataUpdate.m_groundSpeed, CSpeedUnit::kts())); + situation.setOnGround(interimPilotDataUpdate.m_onGround); + + // Ref T297, default offset time + situation.setCurrentUtcTime(); + const qint64 offsetTimeMs = receivedPositionFixTsAndGetOffsetTime(situation.getCallsign(), situation.getMSecsSinceEpoch()); + situation.setTimeOffsetMs(offsetTimeMs); + + emit interimPilotDataUpdatedReceived(situation); + } + else if (subType == "FSIPI") + { + PlaneInformationFsinn planeInformationFsinn = PlaneInformationFsinn::fromTokens(tokens); + emit planeInformationFsinnReceived(planeInformationFsinn.sender(), + planeInformationFsinn.m_airlineIcao, + planeInformationFsinn.m_aircraftIcao, + planeInformationFsinn.m_aircraftIcaoCombinedType, + planeInformationFsinn.m_sendMModelString); + } + else if (subType == "FSIPIR") + { + PlaneInfoRequestFsinn planeInfoRequestFsinn = PlaneInfoRequestFsinn::fromTokens(tokens); + sendPlaneInformationFsinn(planeInfoRequestFsinn.sender()); + emit planeInformationFsinnReceived(planeInfoRequestFsinn.sender(), + planeInfoRequestFsinn.m_airlineIcao, + planeInfoRequestFsinn.m_aircraftIcao, + planeInfoRequestFsinn.m_aircraftIcaoCombinedType, + planeInfoRequestFsinn.m_sendMModelString); + } + else + { + // Unknown #SB opcode, just pass it on. + const QString sender = tokens.at(0); + const QStringList data = tokens.mid(3); + emit customPilotPacketReceived(sender, data); + } + } + + void FSDClient::handleFsdIdentification(const QStringList &tokens) + { + if (m_protocolRevision >= PROTOCOL_REVISION_VATSIM_AUTH) + { + FSDIdentification fsdIdentification = FSDIdentification::fromTokens(tokens); + vatsim_auth_set_initial_challenge(clientAuth, qPrintable(fsdIdentification.m_initialChallenge)); + + char fsdChallenge[33]; + vatsim_auth_generate_challenge(serverAuth, fsdChallenge); + vatsim_auth_set_initial_challenge(serverAuth, fsdChallenge); + sendClientIdentification(QString(fsdChallenge)); + } + else + { + CLogMessage(this).error(u"You tried to connect to a VATSIM server without using VATSIM protocol, disconnecting!"); + disconnectFromServer(); + } + } + + void FSDClient::handleUnknownPacket(const QStringList &tokens) + { + qDebug() << "handleUnknownPacket:" << tokens; + } + + void FSDClient::printSocketError(QAbstractSocket::SocketError) + { + qDebug() << m_socket.errorString(); + } + + void FSDClient::updateConnectionStatus(CConnectionStatus newStatus) + { + if (m_connectionStatus != newStatus) + { + + if (newStatus.isConnected()) + { + m_server.setConnectedSinceNow(); + this->setCurrentEcosystem(m_server.getEcosystem()); + } + else + { + m_server.markAsDisconnected(); + } + + if (newStatus.isDisconnected()) + { + this->stopPositionTimers(); + this->clearState(); + this->setLastEcosystem(m_server.getEcosystem()); + this->setCurrentEcosystem(CEcosystem::NoSystem); + this->saveNetworkStatistics(m_server.getName()); + } + + emit this->connectionStatusChanged(m_connectionStatus, newStatus); + qSwap(m_connectionStatus, newStatus); + } + } + + void FSDClient::consolidateTextMessage(const CTextMessage &textMessage) + { + if (textMessage.isSupervisorMessage()) + { + emit this->textMessagesReceived(textMessage); + } + else + { + m_textMessagesToConsolidate.addConsolidatedTextMessage(textMessage); + m_dsSendTextMessage.inputSignal(); // trigger + } + } + + void FSDClient::emitConsolidatedTextMessages() + { + emit this->textMessagesReceived(m_textMessagesToConsolidate); + m_textMessagesToConsolidate.clear(); + } + + qint64 FSDClient::receivedPositionFixTsAndGetOffsetTime(const CCallsign &callsign, qint64 markerTs) + { + Q_ASSERT_X(!callsign.isEmpty(), Q_FUNC_INFO, "Need callsign"); + + if (markerTs < 0) { markerTs = QDateTime::currentMSecsSinceEpoch(); } + if (!m_lastPositionUpdate.contains(callsign)) + { + m_lastPositionUpdate.insert(callsign, markerTs); + return CFsdSetup::c_positionTimeOffsetMsec; + } + const qint64 oldTs = m_lastPositionUpdate.value(callsign); + m_lastPositionUpdate[callsign] = markerTs; + + // Ref T297, dynamic offsets + const qint64 diff = qAbs(markerTs - oldTs); + this->insertLatestOffsetTime(callsign, diff); + + int count = 0; + static const qint64 minOffsetTime = CFsdSetup::c_interimPositionTimeOffsetMsec; // no longer needed with C++17 + const qint64 avgTimeMs = this->averageOffsetTimeMs(callsign, count, 3); // latest average + qint64 offsetTime = CFsdSetup::c_positionTimeOffsetMsec; + + if (avgTimeMs < minOffsetTime && count >= 3) + { + offsetTime = CFsdSetup::c_interimPositionTimeOffsetMsec; + } + + return m_additionalOffsetTime + offsetTime; + } + + qint64 FSDClient::currentOffsetTime(const CCallsign &callsign) const + { + Q_ASSERT_X(!callsign.isEmpty(), Q_FUNC_INFO, "Need callsign"); + + if (!m_lastOffsetTimes.contains(callsign) || m_lastOffsetTimes[callsign].isEmpty()) { return CFsdSetup::c_positionTimeOffsetMsec; } + return m_lastOffsetTimes[callsign].front(); + } + + void FSDClient::clearState() + { + m_textMessagesToConsolidate.clear(); + m_pendingAtisQueries.clear(); + m_lastPositionUpdate.clear(); + m_lastOffsetTimes.clear(); + m_sentAircraftConfig = CAircraftParts::null(); + } + + void FSDClient::clearState(const CCallsign &callsign) + { + if (callsign.isEmpty()) { return; } + m_pendingAtisQueries.remove(callsign); + m_lastPositionUpdate.remove(callsign); + m_interimPositionReceivers.remove(callsign); + m_lastOffsetTimes.remove(callsign); + } + + void FSDClient::insertLatestOffsetTime(const CCallsign &callsign, qint64 offsetMs) + { + QList &offsets = m_lastOffsetTimes[callsign]; + offsets.push_front(offsetMs); + if (offsets.size() > MaxOffseTimes) { offsets.removeLast(); } + } + + qint64 FSDClient::averageOffsetTimeMs(const CCallsign &callsign, int &count, int maxLastValues) const + { + const QList &offsets = m_lastOffsetTimes[callsign]; + if (offsets.size() < 1) { return -1; } + qint64 sum = 0; + count = 0; + for (qint64 v : offsets) + { + count++; + sum += v; + if (count > maxLastValues) { break; } + } + return qRound(static_cast(sum) / count); + } + + qint64 FSDClient::averageOffsetTimeMs(const CCallsign &callsign, int maxLastValues) const + { + int count = 0; + return this->averageOffsetTimeMs(callsign, maxLastValues, count); + } + + bool FSDClient::isInterimPositionSendingEnabledForServer() const + { + const CFsdSetup::SendReceiveDetails d = this->getSetupForServer().getSendReceiveDetails(); + return (d & CFsdSetup::SendInterimPositions); + } + + bool FSDClient::isInterimPositionReceivingEnabledForServer() const + { + const CFsdSetup::SendReceiveDetails d = this->getSetupForServer().getSendReceiveDetails(); + return (d & CFsdSetup::ReceiveInterimPositions); + } + + const CFsdSetup &FSDClient::getSetupForServer() const + { + return m_server.getFsdSetup(); + } + + void FSDClient::maybeHandleAtisReply(const CCallsign &sender, const CCallsign &receiver, const QString &message) + { + Q_ASSERT(m_pendingAtisQueries.contains(sender)); + PendingAtisQuery &pendingQuery = m_pendingAtisQueries[sender]; + pendingQuery.m_atisMessage.push_back(message); + + // Wait maximum 5 seconds for the reply and release as text message after + if (pendingQuery.m_queryTime.secsTo(QDateTime::currentDateTimeUtc()) > 5) + { + const QString atisMessage(pendingQuery.m_atisMessage.join(QChar::LineFeed)); + CTextMessage tm(atisMessage, sender, receiver); + tm.setCurrentUtcTime(); + emit textMessagesReceived(tm); + m_pendingAtisQueries.remove(sender); + return; + } + + // 4 digits followed by z (e.g. 0200z) is always the last atis line. + // Some controllers leave the logoff time empty. Hence we accept anything + // between 0-4 digits. + thread_local const QRegularExpression reLogoff("^\\d{0,4}z$"); + if (reLogoff.match(message).hasMatch()) + { + emit atisLogoffTimeReplyReceived(sender, message); + CInformationMessage atisMessage; + atisMessage.setType(CInformationMessage::ATIS); + for (const auto &line : as_const(pendingQuery.m_atisMessage)) + { + if (!atisMessage.isEmpty()) atisMessage.appendMessage("\n"); + atisMessage.appendMessage(line); + } + emit atisReplyReceived(CCallsign(sender.toQString(), CCallsign::Atc), atisMessage); + m_pendingAtisQueries.remove(sender); + return; + } + } + + void FSDClient::fsdMessageSettingsChanged() + { + if (m_rawFsdMessageLogFile.isOpen()) { m_rawFsdMessageLogFile.close(); } + const CRawFsdMessageSettings setting = m_fsdMessageSetting.get(); + m_rawFsdMessagesEnabled = setting.areRawFsdMessagesEnabled(); + + if (setting.getFileWriteMode() == CRawFsdMessageSettings::None || setting.getFileDir().isEmpty()) { return; } + if (setting.getFileWriteMode() == CRawFsdMessageSettings::Truncate) + { + QString filePath = CFileUtils::appendFilePaths(setting.getFileDir(), "rawfsdmessages.log"); + m_rawFsdMessageLogFile.setFileName(filePath); + m_rawFsdMessageLogFile.open(QIODevice::Text | QIODevice::WriteOnly); + } + else if (setting.getFileWriteMode() == CRawFsdMessageSettings::Append) + { + QString filePath = CFileUtils::appendFilePaths(setting.getFileDir(), "rawfsdmessages.log"); + m_rawFsdMessageLogFile.setFileName(filePath); + m_rawFsdMessageLogFile.open(QIODevice::Text | QIODevice::WriteOnly | QIODevice::Append); + } + else if (setting.getFileWriteMode() == CRawFsdMessageSettings::Timestamped) + { + QString filename("rawfsdmessages"); + filename += QLatin1String("_"); + filename += QDateTime::currentDateTime().toString(QStringLiteral("yyMMddhhmmss")); + filename += QLatin1String(".log"); + QString filePath = CFileUtils::appendFilePaths(setting.getFileDir(), filename); + m_rawFsdMessageLogFile.setFileName(filePath); + m_rawFsdMessageLogFile.open(QIODevice::Text | QIODevice::WriteOnly); + } + } + + BlackMisc::Aviation::CCallsignSet FSDClient::getInterimPositionReceivers() const + { + return m_interimPositionReceivers; + } + + void FSDClient::setInterimPositionReceivers(const BlackMisc::Aviation::CCallsignSet &interimPositionReceivers) + { + m_interimPositionReceivers = interimPositionReceivers; + } + + int FSDClient::increaseStatisticsValue(const QString &identifier, const QString &appendix) + { + if (identifier.isEmpty() || !m_statistics) { return -1; } + const QString i = appendix.isEmpty() ? identifier : identifier % u"." % appendix; + int &v = m_callStatistics[i]; + v++; + + constexpr int MaxTimeValues = 50; + m_callByTime.push_front(QPair(QDateTime::currentMSecsSinceEpoch(), i)); + if (m_callByTime.size() > MaxTimeValues) { m_callByTime.removeLast(); } + return v; + } + + int FSDClient::increaseStatisticsValue(const QString &identifier, int value) + { + return this->increaseStatisticsValue(identifier, QString::number(value)); + } + + void FSDClient::clearStatistics() + { + m_callStatistics.clear(); + m_callByTime.clear(); + } + + QString FSDClient::getNetworkStatisticsAsText(bool reset, const QString &separator) + { + QVector> transformed; + if (m_callStatistics.isEmpty()) { return QString(); } + + for (const auto pair : makePairsRange(as_const(m_callStatistics))) + { + // key is pair.first, value is pair.second + transformed.push_back({ pair.second, pair.first }); + } + + // sorted by value + std::sort(transformed.begin(), transformed.end(), std::greater<>()); + QString stats; + for (const auto &pair : transformed) + { + stats += + (stats.isEmpty() ? QString() : separator) % + pair.second % u": " % QString::number(pair.first); + } + + for (const auto &pair : transformed) + { + stats += + (stats.isEmpty() ? QString() : separator) % + pair.second % u": " % QString::number(pair.first); + } + + if (!m_callByTime.isEmpty()) + { + const qint64 lastTs = m_callByTime.front().first; + for (const auto &pair : m_callByTime) + { + const qint64 deltaTs = lastTs - pair.first; + stats += separator % QStringLiteral("%1").arg(deltaTs, 5, 10, QChar('0')) % u": " % pair.second; + } + } + + if (reset) { this->clearStatistics(); } + return stats; + } + + CLoginMode FSDClient::getLoginMode() const + { + return m_loginMode; + } + + void FSDClient::readDataFromSocket() + { + while (m_socket.canReadLine()) + { + QByteArray dataEncoded = m_socket.readLine(); + QString data = m_fsdTextCodec->toUnicode(dataEncoded); + parseMessage(data); + } + } + + void FSDClient::parseMessage(const QString &line) + { + MessageType messageType = MessageType::Unknown; + QString cmd; + + if (m_printToConsole) { qDebug() << "FSD Recv=>" << line; } + emitRawFsdMessage(line.trimmed(), false); + + for (const auto &str : makeKeysRange(as_const(m_messageTypeMapping))) + { + if (line.startsWith(str)) + { + cmd = str; + messageType = m_messageTypeMapping[str]; + break; + } + } + + if (messageType != MessageType::Unknown) + { + // Cutoff the cmd from the beginning + QString payload = line.mid(cmd.size()).trimmed(); + + // We expected a payload, but there is nothing + if (payload.length() == 0) return; + + QStringList tokens = payload.split(':'); + + switch (messageType) + { + case MessageType::AddAtc: /* ignore */ return; + case MessageType::AddPilot: /* ignore */ return; + case MessageType::AtcDataUpdate: handleAtcDataUpdate(tokens); return; + case MessageType::AuthChallenge: handleAuthChallenge(tokens); return; + case MessageType::AuthResponse: handleAuthResponse(tokens); return; + case MessageType::ClientIdentification: /* do nothing */ return; + case MessageType::ClientQuery: handleClientQuery(tokens); return; + case MessageType::ClientResponse: handleClientReponse(tokens); return; + case MessageType::DeleteATC: handleDeleteATC(tokens); return; + case MessageType::DeletePilot: handleDeletePilot(tokens); return; + case MessageType::FlightPlan: handleFlightPlan(tokens); return; + case MessageType::FsdIdentification: handleFsdIdentification(tokens); return; + case MessageType::KillRequest: handleKillRequest(tokens); return; + case MessageType::PilotDataUpdate: handlePilotDataUpdate(tokens); return; + case MessageType::Ping: handlePing(tokens); return; + case MessageType::Pong: handlePong(tokens); return; + case MessageType::ServerError: handleServerError(tokens); return; + case MessageType::TextMessage: handleTextMessage(tokens); return; + case MessageType::PilotClientCom: handleCustomPilotPacket(tokens); return; + case MessageType::Unknown: handleUnknownPacket(tokens); return; + } + } + } + + void FSDClient::emitRawFsdMessage(const QString &fsdMessage, bool isSent) + { + if (!m_unitTestMode && !m_rawFsdMessagesEnabled) { return; } + QString fsdMessageFiltered(fsdMessage); + if (m_filterPasswordFromLogin) + { + if (fsdMessageFiltered.startsWith("#AP")) + { + thread_local const QRegularExpression re("^(#AP\\w+:SERVER:\\d+:)[^:]+(:\\d+:\\d+:\\d+:.+)$"); + fsdMessageFiltered.replace(re, "\\1\\2"); + m_filterPasswordFromLogin = false; + } + } + + QString prefix = isSent ? "FSD Sent=>" : "FSD Recv=>"; + CRawFsdMessage rawMessage(prefix + fsdMessageFiltered); + rawMessage.setCurrentUtcTime(); + if (m_rawFsdMessageLogFile.isOpen()) + { + QTextStream stream(&m_rawFsdMessageLogFile); + stream << rawMessage.toQString().trimmed() << endl; + } + emit rawFsdMessage(rawMessage); + } + + bool FSDClient::saveNetworkStatistics(const QString &server) + { + if (m_callStatistics.isEmpty()) { return false; } + + const QString s = this->getNetworkStatisticsAsText(false, "\n"); + if (s.isEmpty()) { return false; } + const QString fn = QStringLiteral("networkstatistics_%1_%2.log").arg(QDateTime::currentDateTimeUtc().toString("yyMMddhhmmss"), server); + const QString fp = CFileUtils::appendFilePaths(CDirectoryUtils::logDirectory(), fn); + return CFileUtils::writeStringToFile(s, fp); + } + + void FSDClient::startPositionTimers() + { + m_positionUpdateTimer.start(c_updatePostionIntervalMsec); + if (this->isInterimPositionSendingEnabledForServer()) { m_interimPositionUpdateTimer.start(c_updateInterimPostionIntervalMsec); } + } + + void FSDClient::stopPositionTimers() + { + m_positionUpdateTimer.stop(); + m_interimPositionUpdateTimer.stop(); + } + + void FSDClient::updateAtisMap(const QString &callsign, AtisLineType type, const QString &line) + { + if (type == AtisLineType::VoiceRoom) + { + m_mapAtisMessages[callsign].voiceRoom = line; + m_mapAtisMessages[callsign].lineCount++; + return; + } + else if (type == AtisLineType::TextMessage) + { + m_mapAtisMessages[callsign].textLines.push_back(line); + m_mapAtisMessages[callsign].lineCount++; + return; + } + else if (type == AtisLineType::ZuluLogoff) + { + m_mapAtisMessages[callsign].zuluLogoff = line; + m_mapAtisMessages[callsign].lineCount++; + return; + } + else + { + if (! m_mapAtisMessages.contains(callsign)) { return; } + + // Ignore the check for line count. + m_mapAtisMessages[callsign].lineCount++; + + const CCallsign cs(callsign, CCallsign::Atc); + emit atisVoiceRoomReplyReceived(cs, m_mapAtisMessages[callsign].voiceRoom); + emit atisLogoffTimeReplyReceived(cs, m_mapAtisMessages[callsign].zuluLogoff); + + CInformationMessage atisMessage; + atisMessage.setType(CInformationMessage::ATIS); + + for (const QString &tm : m_mapAtisMessages[callsign].textLines) + { + const QString fixed = tm.trimmed(); + if (!fixed.isEmpty()) + { + // detect the stupid z1, z2, z3 placeholders + //! \fixme: Anything better as this stupid code here? + thread_local const QRegularExpression RegExp("[\\n\\t\\r]"); + const QString test = fixed.toLower().remove(RegExp); + if (test == "z") return; + if (test.startsWith("z") && test.length() == 2) return; // z1, z2, .. + if (test.length() == 1) return; // sometimes just z + + // append + if (!atisMessage.isEmpty()) atisMessage.appendMessage("\n"); + atisMessage.appendMessage(fixed); + } + } + + emit atisReplyReceived(cs, atisMessage); + + m_mapAtisMessages.remove(callsign); + return; + } + } + + const CLength &FSDClient::fixAtcRange(const CLength &networkRange, const CCallsign &cs) + { + /** T702, https://discordapp.com/channels/539048679160676382/539846348275449887/597814208125730826 + DEL 5 NM + GND 10 NM + TWR 25 NM + DEP/APP 150 NM + CTR 300 NM + FSS fixed 1500NM, no minimum + **/ + + // ATIS often have a range of 0 nm. Correct this to a proper value. + const QString suffix = cs.getSuffix(); + if (suffix.contains(QStringLiteral("ATIS"), Qt::CaseInsensitive)) { static const CLength l_Atis(150.0, CLengthUnit::NM()); return maxOrNotNull(networkRange, l_Atis); } + if (suffix.contains(QStringLiteral("GND"), Qt::CaseInsensitive)) { static const CLength l_Gnd(10.0, CLengthUnit::NM()); return maxOrNotNull(networkRange, l_Gnd); } + if (suffix.contains(QStringLiteral("TWR"), Qt::CaseInsensitive)) { static const CLength l_Twr(25.0, CLengthUnit::NM()); return maxOrNotNull(networkRange, l_Twr); } + if (suffix.contains(QStringLiteral("DEP"), Qt::CaseInsensitive)) { static const CLength l_Dep(150.0, CLengthUnit::NM()); return maxOrNotNull(networkRange, l_Dep); } + if (suffix.contains(QStringLiteral("APP"), Qt::CaseInsensitive)) { static const CLength l_App(150.0, CLengthUnit::NM()); return maxOrNotNull(networkRange, l_App); } + if (suffix.contains(QStringLiteral("CTR"), Qt::CaseInsensitive)) { static const CLength l_Ctr(300.0, CLengthUnit::NM()); return maxOrNotNull(networkRange, l_Ctr); } + if (suffix.contains(QStringLiteral("FSS"), Qt::CaseInsensitive)) { static const CLength l_Fss(1500.0, CLengthUnit::NM()); return maxOrNotNull(networkRange, l_Fss); } + + return networkRange; + } + + const CLength &FSDClient::maxOrNotNull(const CLength &l1, const CLength &l2) + { + if (l1.isNull()) { return l2; } + if (l2.isNull()) { return l1; } + return (l2 > l1) ? l2 : l1; + } + + const QJsonObject &FSDClient::JsonPackets::aircraftConfigRequest() + { + static const QJsonObject jsonObject{ { "request", "full" } }; + return jsonObject; + } + } +} diff --git a/src/blackcore/fsd/fsdclient.h b/src/blackcore/fsd/fsdclient.h new file mode 100644 index 000000000..63faed72d --- /dev/null +++ b/src/blackcore/fsd/fsdclient.h @@ -0,0 +1,442 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKCORE_FSD_CLIENT_H +#define BLACKCORE_FSD_CLIENT_H + + +#include "blackcore/blackcoreexport.h" +#include "blackcore/vatsim/vatsimsettings.h" +#include "blackcore/fsd/enums.h" +#include "blackcore/fsd/messagebase.h" + +#include "blackmisc/aviation/callsign.h" +#include "blackmisc/aviation/informationmessage.h" +#include "blackmisc/aviation/aircrafticaocode.h" +#include "blackmisc/digestsignal.h" +#include "blackmisc/network/connectionstatus.h" +#include "blackmisc/network/loginmode.h" +#include "blackmisc/network/server.h" +#include "blackmisc/network/ecosystemprovider.h" +#include "blackmisc/network/clientprovider.h" +#include "blackmisc/network/textmessagelist.h" +#include "blackmisc/simulation/ownaircraftprovider.h" +#include "blackmisc/simulation/remoteaircraftprovider.h" +#include "blackmisc/simulation/simulationenvironmentprovider.h" +#include "blackmisc/tokenbucket.h" + +#include "vatsim/vatsimauth.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#define PROTOCOL_REVISION_CLASSIC 9 +#define PROTOCOL_REVISION_VATSIM_ATC 10 +#define PROTOCOL_REVISION_VATSIM_AUTH 100 + +namespace BlackFsdTest { class CTestFSDClient; } + +namespace BlackCore +{ + namespace Fsd + { + enum class TextMessageGroups + { + AllClients, + AllAtcClients, + AllPilotClients, + AllSups + }; + + //! TODO: + //! Send (interim) data updates automatically + //! Check ':' in FSD messages. Disconnect if there is a wrong one + + class BLACKCORE_EXPORT FSDClient : + public QObject, + public BlackMisc::Network::IEcosystemProvider, // provide info about used ecosystem + public BlackMisc::Network::CClientAware, // network can set client information + public BlackMisc::Simulation::COwnAircraftAware, // network vatlib consumes own aircraft data and sets ICAO/callsign data + public BlackMisc::Simulation::CRemoteAircraftAware, // check if we really need to process network packets (e.g. parts) + public BlackMisc::Simulation::CSimulationEnvironmentAware // allows to consume ground elevations + { + Q_OBJECT + Q_INTERFACES(BlackMisc::Network::IEcosystemProvider) + + public: + FSDClient(BlackMisc::Network::IClientProvider *clientProvider, + BlackMisc::Simulation::IOwnAircraftProvider *ownAircraftProvider, + BlackMisc::Simulation::IRemoteAircraftProvider *remoteAircraftProvider, + QObject *parent = nullptr); + + // Necessary functions to setup client. Set them all! + void setClientName(const QString &clientName) { m_clientName = clientName; } + void setHostApplication(const QString &hostApplication) { m_hostApplication = hostApplication; } + void setVersion(int major, int minor) { m_versionMajor = major; m_versionMinor = minor; } + void setClientIdAndKey(quint16 id, const QByteArray &key); + void setClientCapabilities(Capabilities capabilities) { m_capabilities = capabilities; } + void setServer(const BlackMisc::Network::CServer &server); + void setSimulatorInfo(const BlackMisc::Simulation::CSimulatorPluginInfo &simInfo); + const BlackMisc::Network::CServer &getServer() const { return m_server; } + void setLoginMode(const BlackMisc::Network::CLoginMode &mode) { m_loginMode = mode; } + void setCallsign(const BlackMisc::Aviation::CCallsign &callsign); + void setPartnerCallsign(const BlackMisc::Aviation::CCallsign &callsign) { m_partnerCallsign = callsign; } + void setIcaoCodes(const BlackMisc::Simulation::CSimulatedAircraft &ownAircraft); + void setLiveryAndModelString(const QString &livery, bool sendLiveryString, const QString &modelString, bool sendModelString); + + void setSimType(SimType type) { m_simType = type; } + void setSimType(const BlackMisc::Simulation::CSimulatorPluginInfo &simInfo); + void setPilotRating(PilotRating rating) { m_pilotRating = rating; } + void setAtcRating(AtcRating rating) { m_atcRating = rating; } + + QStringList getPresetValues() const; + BlackMisc::Aviation::CCallsign getPresetPartnerCallsign() const { return m_partnerCallsign; } + + void connectToServer(); + void disconnectFromServer(); + + void addInterimPositionReceiver(const BlackMisc::Aviation::CCallsign &receiver) { m_interimPositionReceivers.push_back(receiver); } + void removeInterimPositionReceiver(const BlackMisc::Aviation::CCallsign &receiver) { m_interimPositionReceivers.remove(receiver); } + + // Private: + + void sendLogin(); + void sendDeletePilot(); + void sendDeleteAtc(); + void sendPilotDataUpdate(); + + void sendInterimPilotDataUpdate(); + + void sendAtcDataUpdate(double latitude, double longitude); + void sendPing(const QString &receiver); + + // Convenience functions for sendClientQuery + void sendClientQueryIsValidAtc(const BlackMisc::Aviation::CCallsign &callsign); + void sendClientQueryCapabilities(const BlackMisc::Aviation::CCallsign &callsign); + void sendClientQueryCom1Freq(const BlackMisc::Aviation::CCallsign &callsign); + void sendClientQueryRealName(const BlackMisc::Aviation::CCallsign &callsign); + void sendClientQueryServer(const BlackMisc::Aviation::CCallsign &callsign); + void sendClientQueryAtis(const BlackMisc::Aviation::CCallsign &callsign); + void sendClientQueryFlightPlan(const BlackMisc::Aviation::CCallsign callsign); + void sendClientQueryAircraftConfig(const BlackMisc::Aviation::CCallsign callsign); + + void sendClientQuery(ClientQueryType queryType, const BlackMisc::Aviation::CCallsign &receiver, const QStringList &queryData = {}); + void sendTextMessages(const BlackMisc::Network::CTextMessageList &messages); + void sendTextMessage(const BlackMisc::Network::CTextMessage &message); + void sendTextMessage(TextMessageGroups receiverGroup, const QString &message); + void sendTextMessage(const QString &receiver, const QString &message); + void sendRadioMessage(const QVector &frequencies, const QString &message); + void sendFlightPlan(const BlackMisc::Aviation::CFlightPlan &flightPlan); + void sendPlaneInfoRequest(const BlackMisc::Aviation::CCallsign &receiver); + void sendPlaneInfoRequestFsinn(const BlackMisc::Aviation::CCallsign &callsign); + void sendPlaneInformation(const QString &receiver, const QString &aircraft, const QString &airline = {}, const QString &livery = {}); + void sendPlaneInformationFsinn(const BlackMisc::Aviation::CCallsign &callsign); + void sendCustomPilotPacket(const QString &receiver, const QString &subType, const std::vector &payload); + void sendAircraftConfiguration(const QString &receiver, const QString &aircraftConfigJson); + + void sendFsdMessage(const QString &message); + + void setUnitTestMode(bool on) { m_unitTestMode = on; } + void printToConsole(bool on) { m_printToConsole = on; } + + BlackMisc::Network::CConnectionStatus getConnectionStatus() const { return m_connectionStatus; } + + BlackMisc::Network::CLoginMode getLoginMode() const; + + BlackMisc::Aviation::CCallsignSet getInterimPositionReceivers() const; + void setInterimPositionReceivers(const BlackMisc::Aviation::CCallsignSet &interimPositionReceivers); + + bool isConnected() const { return m_connectionStatus.isConnected(); } + bool isPendingConnection() const { return m_connectionStatus.isConnecting() || + m_connectionStatus.isDisconnecting(); } + + //! Statistics enable functions @{ + bool setStatisticsEnable(bool enabled) { m_statistics = enabled; return enabled; } + bool isStatisticsEnabled() const { return m_statistics; } + //! @} + + //! Increase the statistics value for given identifier @{ + int increaseStatisticsValue(const QString &identifier, const QString &appendix = {}); + int increaseStatisticsValue(const QString &identifier, int value); + //! @} + + //! Clear the statistics + void clearStatistics(); + + //! Text statistics + QString getNetworkStatisticsAsText(bool reset, const QString &separator = "\n"); + + signals: + void atcDataUpdateReceived(const BlackMisc::Aviation::CCallsign &callsign, const BlackMisc::PhysicalQuantities::CFrequency &freq, + const BlackMisc::Geo::CCoordinateGeodetic &pos, const BlackMisc::PhysicalQuantities::CLength &range); + void deleteAtcReceived(const QString &cid); + void deletePilotReceived(const QString &cid); + void pilotDataUpdateReceived(const BlackMisc::Aviation::CAircraftSituation &situation, const BlackMisc::Aviation::CTransponder &transponder); + void pongReceived(const QString &sender, double elapsedTimeMs); + void killRequestReceived(const QString &reason); + void flightPlanReceived(const BlackMisc::Aviation::CCallsign &callsign, const BlackMisc::Aviation::CFlightPlan &flightPlan); + + void textMessagesReceived(const BlackMisc::Network::CTextMessageList &messages); + void aircraftConfigReceived(const QString &sender, const QJsonObject &config, qint64 currentOffsetTimeMs); + + // Client responses + void validAtcResponseReceived(const QString &callsign, bool isValidAtc); + void capabilityResponseReceived(const BlackMisc::Aviation::CCallsign &sender, BlackMisc::Network::CClient::Capabilities capabilities); + void com1FrequencyResponseReceived(const QString &sender, const QString &frequency); + void realNameResponseReceived(const QString &sender, const QString &realName); + void serverResponseReceived(const QString &sender, const QString &hostName); + void planeInformationReceived(const QString &sender, const QString &aircraft, const QString &airline, const QString &livery); + void customPilotPacketReceived(const QString &sender, const QStringList &data); + void interimPilotDataUpdatedReceived(const BlackMisc::Aviation::CAircraftSituation &situation); + void rawFsdMessage(const BlackMisc::Network::CRawFsdMessage &rawFsdMessage); + void planeInformationFsinnReceived(const BlackMisc::Aviation::CCallsign &callsign, const QString &airlineIcaoDesignator, const QString &aircraftDesignator, const QString &combinedAircraftType, const QString &modelString); + + //! We received a reply to one of our ATIS queries. + void atisReplyReceived(const BlackMisc::Aviation::CCallsign &callsign, const BlackMisc::Aviation::CInformationMessage &atis); + + //! We received a reply to one of our ATIS queries, containing the controller's voice room URL. + void atisVoiceRoomReplyReceived(const BlackMisc::Aviation::CCallsign &callsign, const QString &url); + + //! We received a reply to one of our ATIS queries, containing the controller's planned logoff time. + void atisLogoffTimeReplyReceived(const BlackMisc::Aviation::CCallsign &callsign, const QString &zuluTime); + + //! We have sent a text message. + void textMessageSent(const BlackMisc::Network::CTextMessage &sentMessage); + + void connectionStatusChanged(BlackMisc::Network::CConnectionStatus oldStatus, BlackMisc::Network::CConnectionStatus newStatus); + + private: + friend BlackFsdTest::CTestFSDClient; + + template + void sendMessage(const T &message) + { + if (!message.isValid()) return; + + QString payload = message.toTokens().join(':'); + QString line = message.pdu() + payload; + QString buffer = line + "\r\n"; + QByteArray bufferEncoded = m_fsdTextCodec->fromUnicode(buffer); + emitRawFsdMessage(buffer.trimmed(), true); + if (m_printToConsole) { qDebug() << "FSD Sent=>" << bufferEncoded; } + if (! m_unitTestMode) { m_socket.write(bufferEncoded); } + } + + //! Default model string + static const QString &defaultModelString() + { + static const QString dm("Cessna Skyhawk 172SP"); + return dm; + } + + //! Send if no model string is available + static const QString &noModelString() + { + static const QString noms("swift empty string"); + return noms; + } + + struct JsonPackets + { + static const QJsonObject &aircraftConfigRequest(); + }; + + void sendAuthChallenge(const QString &challenge); + void sendAuthResponse(const QString &response); + void sendPong(const QString &receiver, const QString ×tamp); + void sendClientResponse(ClientQueryType queryType, const QString &receiver); + void sendClientIdentification(const QString &fsdChallenge); + void sendIncrementalAircraftConfig(); + + void readDataFromSocket(); + void parseMessage(const QString &line); + + void initializeMessageTypes(); + void handleAtcDataUpdate(const QStringList &tokens); + void handleAuthChallenge(const QStringList &tokens); + void handleAuthResponse(const QStringList &tokens); + void handleDeleteATC(const QStringList &tokens); + void handleDeletePilot(const QStringList &tokens); + void handleTextMessage(const QStringList &tokens); + void handlePilotDataUpdate(const QStringList &tokens); + void handlePing(const QStringList &tokens); + void handlePong(const QStringList &tokens); + void handleKillRequest(const QStringList &tokens); + void handleFlightPlan(const QStringList &tokens); + void handleClientQuery(const QStringList &tokens); + void handleClientReponse(const QStringList &tokens); + void handleServerError(const QStringList &tokens); + void handleCustomPilotPacket(const QStringList &tokens); + void handleFsdIdentification(const QStringList &tokens); + void handleUnknownPacket(const QStringList &tokens); + + void printSocketError(QAbstractSocket::SocketError socketError); + + void updateConnectionStatus(BlackMisc::Network::CConnectionStatus newStatus); + + //! Consolidate text messages if we receive multiple messages which belong together + //! \remark causes a slight delay + void consolidateTextMessage(const BlackMisc::Network::CTextMessage &textMessage); + + //! Send the consolidatedTextMessages + void emitConsolidatedTextMessages(); + + //! Remember when last position was received + qint64 receivedPositionFixTsAndGetOffsetTime(const BlackMisc::Aviation::CCallsign &callsign, qint64 markerTs = -1); + + //! Current offset time + qint64 currentOffsetTime(const BlackMisc::Aviation::CCallsign &callsign) const; + + //! Clear state when connection is terminated + void clearState(); + + //! Clear state for callsign + void clearState(const BlackMisc::Aviation::CCallsign &callsign); + + //! Insert as first value + void insertLatestOffsetTime(const BlackMisc::Aviation::CCallsign &callsign, qint64 offsetMs); + + //! Average offset time in ms + qint64 averageOffsetTimeMs(const BlackMisc::Aviation::CCallsign &callsign, int &count, int maxLastValues = MaxOffseTimes) const; + + //! Average offset time in ms + qint64 averageOffsetTimeMs(const BlackMisc::Aviation::CCallsign &callsign, int maxLastValues = MaxOffseTimes) const; + + bool isInterimPositionSendingEnabledForServer() const; + bool isInterimPositionReceivingEnabledForServer() const; + const BlackMisc::Network::CFsdSetup &getSetupForServer() const; + + //! Handles ATIS replies from non-VATSIM servers. If the conditions are not met, the message is + //! released as normal text message. + void maybeHandleAtisReply(const BlackMisc::Aviation::CCallsign &sender, const BlackMisc::Aviation::CCallsign &receiver, const QString &message); + + void fsdMessageSettingsChanged(); + void emitRawFsdMessage(const QString &fsdMessage, bool isSent); + + //! Additional offset time @{ + qint64 getAdditionalOffsetTime() const; + void setAdditionalOffsetTime(qint64 addOffset); + //! @} + + //! Save the statistics + bool saveNetworkStatistics(const QString &server); + + void startPositionTimers(); + void stopPositionTimers(); + + void updateAtisMap(const QString &callsign, AtisLineType type, const QString &line); + + //! Fix ATC station range + static const BlackMisc::PhysicalQuantities::CLength &fixAtcRange(const BlackMisc::PhysicalQuantities::CLength &networkRange, const BlackMisc::Aviation::CCallsign &cs); + + //! Max or 1st non-null value + static const BlackMisc::PhysicalQuantities::CLength &maxOrNotNull(const BlackMisc::PhysicalQuantities::CLength &l1, const BlackMisc::PhysicalQuantities::CLength &l2); + + // Client data + QString m_clientName; + QString m_hostApplication; + int m_versionMajor = 0; + int m_versionMinor = 0; + ServerType m_serverType = ServerType::LegacyFsd; + int m_protocolRevision = 0; + Capabilities m_capabilities = Capabilities::None; + + vatsim_auth *clientAuth = nullptr; + vatsim_auth *serverAuth = nullptr; + QString m_lastServerAuthChallenge; + + // User data + BlackMisc::Network::CServer m_server; + BlackMisc::Network::CLoginMode m_loginMode; + SimType m_simType = SimType::Unknown; + PilotRating m_pilotRating = PilotRating::Unknown; + AtcRating m_atcRating = AtcRating::Unknown; + QString m_com1Frequency; + + // Parser + QHash m_messageTypeMapping; + + QTcpSocket m_socket; + + bool m_unitTestMode = false; + bool m_printToConsole = false; + + BlackMisc::Network::CConnectionStatus m_connectionStatus; + + BlackMisc::Aviation::CAircraftParts m_sentAircraftConfig; //!< aircraft parts sent + BlackMisc::CTokenBucket m_tokenBucket; //!< used with aircraft parts messages + BlackMisc::Aviation::CCallsignSet m_interimPositionReceivers; //!< all aircraft receiving interim positions + + BlackMisc::CDigestSignal m_dsSendTextMessage { this, &FSDClient::emitConsolidatedTextMessages, 500, 10 }; + BlackMisc::Network::CTextMessageList m_textMessagesToConsolidate; + + struct AtisMessage + { + QString voiceRoom; + QStringList textLines; + QString zuluLogoff; + int lineCount = 0; + }; + + QMap m_mapAtisMessages; + + //! Pending ATIS query since + struct PendingAtisQuery + { + QDateTime m_queryTime = QDateTime::currentDateTimeUtc(); + QStringList m_atisMessage; + }; + + QHash m_pendingAtisQueries; + QHash m_lastPositionUpdate; + QHash> m_lastOffsetTimes; //!< latest offset first + + BlackMisc::CSettingReadOnly m_fsdMessageSetting { this, &FSDClient::fsdMessageSettingsChanged }; + QFile m_rawFsdMessageLogFile; + bool m_rawFsdMessagesEnabled = false; + bool m_filterPasswordFromLogin = false; + + QTimer m_scheduledConfigUpdate; + QTimer m_positionUpdateTimer; //!< sending positions + QTimer m_interimPositionUpdateTimer; //!< sending interim positions + + qint64 m_additionalOffsetTime = 0; //!< additional offset time + + bool m_statistics = false; + QMap m_callStatistics; + QVector > m_callByTime; + + BlackMisc::Simulation::CSimulatorPluginInfo m_simulatorInfo; //!< used simulator + BlackMisc::Aviation::CCallsign m_ownCallsign; //!< "buffered callsign", as this must not change when connected + BlackMisc::Aviation::CCallsign m_partnerCallsign; //!< callsign of partner flying in shared cockpit + BlackMisc::Aviation::CAircraftIcaoCode m_ownAircraftIcaoCode; //!< "buffered icao", as this must not change when connected + BlackMisc::Aviation::CAirlineIcaoCode m_ownAirlineIcaoCode; //!< "buffered icao", as this must not change when connected + QString m_ownLivery; //!< "buffered livery", as this must not change when connected + QString m_ownModelString; //!< "buffered model string", as this must not change when connected + bool m_sendLiveryString = true; + bool m_sendMModelString = true; + + QTextCodec *m_fsdTextCodec = nullptr; + + static const int MaxOffseTimes = 6; //!< Max offset times kept + static int constexpr c_processingIntervalMsec = 100; //!< interval for the processing timer + static int constexpr c_updatePostionIntervalMsec = 5000; //!< interval for the position update timer (send our position to network) + static int constexpr c_updateInterimPostionIntervalMsec = 1000; //!< interval for iterim position updates (send our position as interim position) + }; + } +} + +#endif diff --git a/src/blackcore/fsd/fsdidentification.cpp b/src/blackcore/fsd/fsdidentification.cpp new file mode 100644 index 000000000..1668ba227 --- /dev/null +++ b/src/blackcore/fsd/fsdidentification.cpp @@ -0,0 +1,46 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +#include "fsdidentification.h" + +#include "blackmisc/logmessage.h" + +namespace BlackCore +{ + namespace Fsd + { + FSDIdentification::FSDIdentification() : MessageBase() + { } + + FSDIdentification::FSDIdentification(const QString &callsign, const QString &receiver, const QString &serverVersion, const QString &initialChallenge) + : MessageBase(callsign, receiver), + m_serverVersion(serverVersion), + m_initialChallenge(initialChallenge) + { } + + QStringList FSDIdentification::toTokens() const + { + QStringList tokens; + tokens.push_back(m_sender); + tokens.push_back(m_receiver); + tokens.push_back(m_serverVersion); + tokens.push_back(m_initialChallenge); + return tokens; + } + + FSDIdentification FSDIdentification::fromTokens(const QStringList &tokens) + { + if (tokens.size() < 4) + { + BlackMisc::CLogMessage(static_cast(nullptr)).debug(u"Wrong number of arguments."); + return {}; + }; + return FSDIdentification(tokens[0], tokens[1], tokens[2], tokens[3]); + } + } +} diff --git a/src/blackcore/fsd/fsdidentification.h b/src/blackcore/fsd/fsdidentification.h new file mode 100644 index 000000000..b329da482 --- /dev/null +++ b/src/blackcore/fsd/fsdidentification.h @@ -0,0 +1,41 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKCORE_FSD_FSDIDENTIFICATION_H +#define BLACKCORE_FSD_FSDIDENTIFICATION_H + +#include "messagebase.h" + +namespace BlackCore +{ + namespace Fsd + { + //! This packet is sent by the server immediately after a new client connection is made. + class BLACKCORE_EXPORT FSDIdentification : public MessageBase + { + public: + FSDIdentification(const QString &callsign, const QString &receiver, const QString &serverVersion, const QString &initialChallenge); + + virtual ~FSDIdentification() {} + + QStringList toTokens() const; + static FSDIdentification fromTokens(const QStringList &tokens); + static QString pdu() { return "$DI"; } + + QString m_serverVersion; + QString m_initialChallenge; + + private: + FSDIdentification(); + }; + } +} + +#endif // guard diff --git a/src/blackcore/fsd/interimpilotdataupdate.cpp b/src/blackcore/fsd/interimpilotdataupdate.cpp new file mode 100644 index 000000000..cadf1be0a --- /dev/null +++ b/src/blackcore/fsd/interimpilotdataupdate.cpp @@ -0,0 +1,73 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +#include "interimpilotdataupdate.h" +#include "pbh.h" + +#include "blackmisc/logmessage.h" + +namespace BlackCore +{ + namespace Fsd + { + InterimPilotDataUpdate::InterimPilotDataUpdate() : MessageBase() + { } + + InterimPilotDataUpdate::InterimPilotDataUpdate(const QString &sender, const QString &receiver, double latitude, double longitude, int altitudeTrue, + int groundSpeed, double pitch, double bank, double heading, bool onGround) + : MessageBase(sender, receiver), + m_latitude(latitude), + m_longitude(longitude), + m_altitudeTrue(altitudeTrue), + m_groundSpeed(groundSpeed), + m_pitch(pitch), + m_bank(bank), + m_heading(heading), + m_onGround(onGround) + { } + + QStringList InterimPilotDataUpdate::toTokens() const + { + std::uint32_t pbh; + packPBH(m_pitch, m_bank, m_heading, m_onGround, pbh); + + auto tokens = QStringList {}; + tokens.push_back(m_sender); + tokens.push_back(m_receiver); + + // SquawkBox used "I", but since we are using a different message format, + // we also need to use a different subtype. + // VI = vatlib interim + tokens.push_back("VI"); + tokens.push_back(QString::number(m_latitude, 'f', 5)); + tokens.push_back(QString::number(m_longitude, 'f', 5)); + tokens.push_back(QString::number(m_altitudeTrue)); + tokens.push_back(QString::number(m_groundSpeed)); + tokens.push_back(QString::number(pbh)); + return tokens; + } + + InterimPilotDataUpdate InterimPilotDataUpdate::fromTokens(const QStringList &tokens) + { + if (tokens.size() < 8) + { + BlackMisc::CLogMessage(static_cast(nullptr)).debug(u"Wrong number of arguments."); + return {}; + }; + + double pitch = 0.0; + double bank = 0.0; + double heading = 0.0; + bool onGround = false; + unpackPBH(tokens[7].toUInt(), pitch, bank, heading, onGround); + + return InterimPilotDataUpdate(tokens[0], tokens[1], tokens[3].toDouble(), tokens[4].toDouble(), tokens[5].toInt(), tokens[6].toInt(), + pitch, bank, heading, onGround); + } + } +} diff --git a/src/blackcore/fsd/interimpilotdataupdate.h b/src/blackcore/fsd/interimpilotdataupdate.h new file mode 100644 index 000000000..b5acb3a68 --- /dev/null +++ b/src/blackcore/fsd/interimpilotdataupdate.h @@ -0,0 +1,67 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKCORE_FSD_INTERIMPILOTDATAUPDATE_H +#define BLACKCORE_FSD_INTERIMPILOTDATAUPDATE_H + +#include "messagebase.h" + +namespace BlackCore +{ + namespace Fsd + { + class BLACKCORE_EXPORT InterimPilotDataUpdate : public MessageBase + { + public: + + InterimPilotDataUpdate(const QString &sender, const QString &receiver, double latitude, double longitude, int altitudeTrue, + int groundSpeed, double pitch, double bank, double heading, bool onGround); + + virtual ~InterimPilotDataUpdate() {} + + QStringList toTokens() const; + + static InterimPilotDataUpdate fromTokens(const QStringList &tokens); + static QString pdu() { return "#SB"; } + + double m_latitude = 0.0; + double m_longitude = 0.0; + int m_altitudeTrue = 0.0; + int m_groundSpeed = 0.0; + double m_pitch = 0.0; + double m_bank = 0.0; + double m_heading = 0.0; + bool m_onGround = false; + + private: + InterimPilotDataUpdate(); + }; + + inline bool operator==(const InterimPilotDataUpdate &lhs, const InterimPilotDataUpdate &rhs) + { + return lhs.sender() == rhs.sender() && + lhs.receiver() == rhs.receiver() && + lhs.m_latitude == rhs.m_latitude && + lhs.m_longitude == rhs.m_longitude && + lhs.m_altitudeTrue == rhs.m_altitudeTrue && + lhs.m_pitch == rhs.m_pitch && + lhs.m_bank == rhs.m_bank && + lhs.m_heading == rhs.m_heading && + lhs.m_onGround == rhs.m_onGround; + } + + inline bool operator!=(const InterimPilotDataUpdate &lhs, const InterimPilotDataUpdate &rhs) + { + return !(lhs == rhs); + } + } +} + +#endif // guard diff --git a/src/blackcore/fsd/killrequest.cpp b/src/blackcore/fsd/killrequest.cpp new file mode 100644 index 000000000..84d7bcd2b --- /dev/null +++ b/src/blackcore/fsd/killrequest.cpp @@ -0,0 +1,44 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +#include "killrequest.h" + +#include "blackmisc/logmessage.h" + +namespace BlackCore +{ + namespace Fsd + { + KillRequest::KillRequest() : MessageBase() + { } + + KillRequest::KillRequest(const QString &callsign, const QString &receiver, const QString &reason) + : MessageBase(callsign, receiver), + m_reason(reason) + { } + + QStringList KillRequest::toTokens() const + { + auto tokens = QStringList {}; + tokens.push_back(m_sender); + tokens.push_back(m_receiver); + tokens.push_back(m_reason); + return tokens; + } + + KillRequest KillRequest::fromTokens(const QStringList &tokens) + { + if (tokens.size() < 2) + { + BlackMisc::CLogMessage(static_cast(nullptr)).debug(u"Wrong number of arguments."); + return {}; + }; + return KillRequest(tokens[0], tokens[1], tokens.size() > 2 ? tokens[2] : QString()); + } + } +} diff --git a/src/blackcore/fsd/killrequest.h b/src/blackcore/fsd/killrequest.h new file mode 100644 index 000000000..1b05764b6 --- /dev/null +++ b/src/blackcore/fsd/killrequest.h @@ -0,0 +1,51 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKCORE_FSD_KILLREQUEST_H +#define BLACKCORE_FSD_KILLREQUEST_H + +#include "messagebase.h" + +namespace BlackCore +{ + namespace Fsd + { + class BLACKCORE_EXPORT KillRequest : public MessageBase + { + public: + KillRequest(const QString &sender, const QString &receiver, const QString &reason); + + virtual ~KillRequest() {} + + QStringList toTokens() const; + static KillRequest fromTokens(const QStringList &tokens); + static QString pdu() { return "$!!"; } + + QString m_reason; + + private: + KillRequest(); + }; + + inline bool operator==(const KillRequest &lhs, const KillRequest &rhs) + { + return lhs.sender() == rhs.sender() && + lhs.receiver() == rhs.receiver() && + lhs.m_reason == rhs.m_reason; + } + + inline bool operator!=(const KillRequest &lhs, const KillRequest &rhs) + { + return !(lhs == rhs); + } + } +} + +#endif // guard diff --git a/src/blackcore/fsd/messagebase.cpp b/src/blackcore/fsd/messagebase.cpp new file mode 100644 index 000000000..e1cd43c61 --- /dev/null +++ b/src/blackcore/fsd/messagebase.cpp @@ -0,0 +1,26 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +#include "messagebase.h" + +namespace BlackCore +{ + namespace Fsd + { + MessageBase::MessageBase(const QString &sender) + : m_sender(sender) + { + } + + MessageBase::MessageBase(const QString &sender, const QString &receiver) + : m_sender(sender), + m_receiver(receiver) + { + } + } +} diff --git a/src/blackcore/fsd/messagebase.h b/src/blackcore/fsd/messagebase.h new file mode 100644 index 000000000..54736d6d3 --- /dev/null +++ b/src/blackcore/fsd/messagebase.h @@ -0,0 +1,78 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKCORE_FSD_MESSAGEBASE_H +#define BLACKCORE_FSD_MESSAGEBASE_H + +#include "blackcore/blackcoreexport.h" + +#include +#include + +enum class MessageType +{ + Unknown, + AddAtc, + AddPilot, + AtcDataUpdate, + AuthChallenge, + AuthResponse, + ClientIdentification, + ClientQuery, + ClientResponse, + DeleteATC, + DeletePilot, + FlightPlan, + FsdIdentification, + KillRequest, + PilotDataUpdate, + Ping, + Pong, + ServerError, + TextMessage, + PilotClientCom, +}; + +namespace BlackCore +{ + namespace Fsd + { + class BLACKCORE_EXPORT MessageBase + { + public: + MessageBase() {} + MessageBase(const QString &sender); + MessageBase(const QString &sender, const QString &receiver); + virtual ~MessageBase() {} + + void setCallsign(const QString &sender) { m_sender = sender; } + QString sender() const { return m_sender; } + + void setReceiver(const QString &receiver) { m_receiver = receiver; } + QString receiver() const { return m_receiver; } + + bool isValid() const { return m_isValid; } + + void setValid(bool isValid) { m_isValid = isValid; } + + protected: + + // Meta data + // MessageType messageType = MessageType::Unknown; + + QString m_sender; + QString m_receiver; + + bool m_isValid = true; + }; + } +} + +#endif // guard diff --git a/src/blackcore/fsd/pbh.h b/src/blackcore/fsd/pbh.h new file mode 100644 index 000000000..98f39f453 --- /dev/null +++ b/src/blackcore/fsd/pbh.h @@ -0,0 +1,86 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKCORE_FSD_PBH_H +#define BLACKCORE_FSD_PBH_H + +#include +#include + +namespace BlackCore +{ + namespace Fsd + { + + union PBH + { + unsigned int pbh = 0; //!< Pitch/Bank/Heading as integer value + struct + { + unsigned int unused : 1; //!< unused bit + unsigned int onground : 1; //!< Onground flag + unsigned int hdg : 10; //!< Heading + int bank : 10; //!< Bank + int pitch : 10; //!< Pitch + }; + }; + + constexpr double pitchMultiplier () + { + return 256.0 / 90.0; + } + + constexpr double bankMultiplier () + { + return 512.0 / 180.0; + } + + constexpr double headingMultiplier () + { + return 1024.0 / 360.0; + } + + inline void packPBH(double pitch, double bank, double heading, bool onGround, quint32 &pbh) + { + PBH pbhstrct; + pbhstrct.pitch = qFloor(pitch * pitchMultiplier()); + pbhstrct.bank = qFloor(bank * bankMultiplier()); + pbhstrct.hdg = static_cast(heading * headingMultiplier()); + + // FSD has inverted pitch and bank angles + pbhstrct.pitch = ~pbhstrct.pitch; + pbhstrct.bank = ~pbhstrct.bank; + + pbhstrct.onground = onGround ? 1 : 0; + + pbh = pbhstrct.pbh; + } + + inline void unpackPBH(quint32 pbh, double &pitch, double &bank, double &heading, bool &onGround) + { + PBH pbhstrct; + pbhstrct.pbh = pbh; + int iPitch = qFloor(pbhstrct.pitch / pitchMultiplier()); + int iBank = qFloor(pbhstrct.bank / bankMultiplier()); + + // MSFS has inverted pitch and bank angles + iPitch = ~iPitch; + iBank = ~iBank; + + pitch = iPitch; + bank = iBank; + heading = pbhstrct.hdg / headingMultiplier(); + + onGround = pbhstrct.onground == 1 ? true : false; + } + } +} + +#endif // guard diff --git a/src/blackcore/fsd/pilotdataupdate.cpp b/src/blackcore/fsd/pilotdataupdate.cpp new file mode 100644 index 000000000..d38d6c71e --- /dev/null +++ b/src/blackcore/fsd/pilotdataupdate.cpp @@ -0,0 +1,79 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +#include "pilotdataupdate.h" +#include "pbh.h" +#include "serializer.h" + +#include "blackmisc/logmessage.h" + +using namespace BlackMisc::Aviation; + +namespace BlackCore +{ + namespace Fsd + { + PilotDataUpdate::PilotDataUpdate() : MessageBase() + { } + + PilotDataUpdate::PilotDataUpdate(CTransponder::TransponderMode transponderMode, const QString &sender, int transponderCode, PilotRating rating, double latitude, double longitude, int altitudeTrue, int altitudePressure, int groundSpeed, + double pitch, double bank, double heading, bool onGround) + : MessageBase(sender, {}), + m_transponderMode(transponderMode), + m_transponderCode(transponderCode), + m_rating(rating), + m_latitude(latitude), + m_longitude(longitude), + m_altitudeTrue(altitudeTrue), + m_altitudePressure(altitudePressure), + m_groundSpeed(groundSpeed), + m_pitch(pitch), + m_bank(bank), + m_heading(heading), + m_onGround(onGround) + { } + + QStringList PilotDataUpdate::toTokens() const + { + std::uint32_t pbh; + packPBH(m_pitch, m_bank, m_heading, m_onGround, pbh); + + QStringList tokens; + tokens.push_back(toQString(m_transponderMode)); + tokens.push_back(m_sender); + tokens.push_back(QString::number(m_transponderCode)); + tokens.push_back(toQString(m_rating)); + tokens.push_back(QString::number(m_latitude, 'f', 5)); + tokens.push_back(QString::number(m_longitude, 'f', 5)); + tokens.push_back(QString::number(m_altitudeTrue)); + tokens.push_back(QString::number(m_groundSpeed)); + tokens.push_back(QString::number(pbh)); + tokens.push_back(QString::number(m_altitudePressure - m_altitudeTrue)); + return tokens; + } + + PilotDataUpdate PilotDataUpdate::fromTokens(const QStringList &tokens) + { + if (tokens.size() < 10) + { + BlackMisc::CLogMessage(static_cast(nullptr)).debug(u"Wrong number of arguments."); + return {}; + }; + + double pitch = 0.0; + double bank = 0.0; + double heading = 0.0; + bool onGround = false; + unpackPBH(tokens[8].toUInt(), pitch, bank, heading, onGround); + + return PilotDataUpdate(fromQString(tokens[0]), tokens[1], tokens[2].toInt(), fromQString(tokens[3]), + tokens[4].toDouble(), tokens[5].toDouble(), tokens[6].toInt(), tokens[6].toInt() + tokens[9].toInt(), tokens[7].toInt(), + pitch, bank, heading, onGround); + } + } +} diff --git a/src/blackcore/fsd/pilotdataupdate.h b/src/blackcore/fsd/pilotdataupdate.h new file mode 100644 index 000000000..6b823791e --- /dev/null +++ b/src/blackcore/fsd/pilotdataupdate.h @@ -0,0 +1,77 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKCORE_FSD_PILOTDATAUPDATE_H +#define BLACKCORE_FSD_PILOTDATAUPDATE_H + +#include "messagebase.h" +#include "enums.h" +#include "blackmisc/aviation/transponder.h" + +namespace BlackCore +{ + namespace Fsd + { + class BLACKCORE_EXPORT PilotDataUpdate : public MessageBase + { + public: + + PilotDataUpdate(BlackMisc::Aviation::CTransponder::TransponderMode transponderMode, const QString &sender, int transponderCode, PilotRating rating, double latitude, double longitude, int altitudeTrue, int altitudePressure, int groundSpeed, + double pitch, double bank, double heading, bool onGround); + + virtual ~PilotDataUpdate() {} + QStringList toTokens() const; + + static PilotDataUpdate fromTokens(const QStringList &tokens); + static QString pdu() { return "@"; } + + BlackMisc::Aviation::CTransponder::TransponderMode m_transponderMode = BlackMisc::Aviation::CTransponder::StateStandby; + int m_transponderCode = 0; + PilotRating m_rating = PilotRating::Unknown; + double m_latitude = 0.0; + double m_longitude = 0.0; + int m_altitudeTrue = 0.0; + int m_altitudePressure = 0.0; + int m_groundSpeed = 0; + double m_pitch = 0.0; + double m_bank = 0.0; + double m_heading = 0.0; + bool m_onGround = false; + + private: + PilotDataUpdate(); + }; + + inline bool operator==(const PilotDataUpdate &lhs, const PilotDataUpdate &rhs) + { + return lhs.sender() == rhs.sender() && + lhs.receiver() == rhs.receiver() && + lhs.m_transponderMode == rhs.m_transponderMode && + lhs.m_transponderCode == rhs.m_transponderCode && + lhs.m_rating == rhs.m_rating && + lhs.m_latitude == rhs.m_latitude && + lhs.m_longitude == rhs.m_longitude && + lhs.m_altitudeTrue == rhs.m_altitudeTrue && + lhs.m_altitudePressure == rhs.m_altitudePressure && + lhs.m_groundSpeed == rhs.m_groundSpeed && + lhs.m_pitch == rhs.m_pitch && + lhs.m_bank == rhs.m_bank && + lhs.m_heading == rhs.m_heading && + lhs.m_onGround == rhs.m_onGround; + } + + inline bool operator!=(const PilotDataUpdate &lhs, const PilotDataUpdate &rhs) + { + return !(lhs == rhs); + } + } +} + +#endif // guard diff --git a/src/blackcore/fsd/ping.cpp b/src/blackcore/fsd/ping.cpp new file mode 100644 index 000000000..a37de7474 --- /dev/null +++ b/src/blackcore/fsd/ping.cpp @@ -0,0 +1,44 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +#include "ping.h" + +#include "blackmisc/logmessage.h" + +namespace BlackCore +{ + namespace Fsd + { + Ping::Ping() : MessageBase() + { } + + Ping::Ping(const QString &sender, const QString &receiver, const QString ×tamp) + : MessageBase(sender, receiver), + m_timestamp(timestamp) + { } + + QStringList Ping::toTokens() const + { + QStringList tokens; + tokens.push_back(m_sender); + tokens.push_back(m_receiver); + tokens.push_back(m_timestamp); + return tokens; + } + + Ping Ping::fromTokens(const QStringList &tokens) + { + if (tokens.size() < 3) + { + BlackMisc::CLogMessage(static_cast(nullptr)).debug(u"Wrong number of arguments."); + return {}; + }; + return Ping(tokens[0], tokens[1], tokens[2]); + } + } +} diff --git a/src/blackcore/fsd/ping.h b/src/blackcore/fsd/ping.h new file mode 100644 index 000000000..9a3afb9dd --- /dev/null +++ b/src/blackcore/fsd/ping.h @@ -0,0 +1,50 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKCORE_FSD_PING_H +#define BLACKCORE_FSD_PING_H + +#include "messagebase.h" + +namespace BlackCore +{ + namespace Fsd + { + class BLACKCORE_EXPORT Ping : public MessageBase + { + public: + Ping(const QString &sender, const QString &receiver, const QString ×tamp); + virtual ~Ping() {} + + QStringList toTokens() const; + static Ping fromTokens(const QStringList &tokens); + static QString pdu() { return "$PI"; } + + QString m_timestamp; + + private: + Ping(); + }; + + inline bool operator==(const Ping &lhs, const Ping &rhs) + { + return lhs.sender() == rhs.sender() && + lhs.receiver() == rhs.receiver() && + lhs.m_timestamp == rhs.m_timestamp; + } + + inline bool operator!=(const Ping &lhs, const Ping &rhs) + { + return !(lhs == rhs); + } + } +} + +#endif // guard diff --git a/src/blackcore/fsd/planeinforequest.cpp b/src/blackcore/fsd/planeinforequest.cpp new file mode 100644 index 000000000..bd2a52290 --- /dev/null +++ b/src/blackcore/fsd/planeinforequest.cpp @@ -0,0 +1,43 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +#include "planeinforequest.h" + +#include "blackmisc/logmessage.h" + +namespace BlackCore +{ + namespace Fsd + { + PlaneInfoRequest::PlaneInfoRequest() : MessageBase() + { } + + PlaneInfoRequest::PlaneInfoRequest(const QString &sender, const QString &receiver) + : MessageBase(sender, receiver) + { } + + QStringList PlaneInfoRequest::toTokens() const + { + auto tokens = QStringList {}; + tokens.push_back(m_sender); + tokens.push_back(m_receiver); + tokens.push_back("PIR"); + return tokens; + } + + PlaneInfoRequest PlaneInfoRequest::fromTokens(const QStringList &tokens) + { + if (tokens.size() < 3) + { + BlackMisc::CLogMessage(static_cast(nullptr)).debug(u"Wrong number of arguments."); + return {}; + }; + return PlaneInfoRequest(tokens[0], tokens[1]); + } + } +} diff --git a/src/blackcore/fsd/planeinforequest.h b/src/blackcore/fsd/planeinforequest.h new file mode 100644 index 000000000..5cdbfce48 --- /dev/null +++ b/src/blackcore/fsd/planeinforequest.h @@ -0,0 +1,48 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKCORE_FSD_PLANEINFOREQUEST_H +#define BLACKCORE_FSD_PLANEINFOREQUEST_H + +#include "messagebase.h" + +namespace BlackCore +{ + namespace Fsd + { + class BLACKCORE_EXPORT PlaneInfoRequest : public MessageBase + { + public: + PlaneInfoRequest(const QString &sender, const QString &receiver); + + virtual ~PlaneInfoRequest() {} + + QStringList toTokens() const; + static PlaneInfoRequest fromTokens(const QStringList &tokens); + static QString pdu() { return QStringLiteral("#SB"); } + + private: + PlaneInfoRequest(); + }; + + inline bool operator==(const PlaneInfoRequest &lhs, const PlaneInfoRequest &rhs) + { + return lhs.sender() == rhs.sender() && + lhs.receiver() == rhs.receiver(); + } + + inline bool operator!=(const PlaneInfoRequest &lhs, const PlaneInfoRequest &rhs) + { + return !(lhs == rhs); + } + } +} + +#endif // guard diff --git a/src/blackcore/fsd/planeinforequestfsinn.cpp b/src/blackcore/fsd/planeinforequestfsinn.cpp new file mode 100644 index 000000000..9b0795890 --- /dev/null +++ b/src/blackcore/fsd/planeinforequestfsinn.cpp @@ -0,0 +1,61 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +#include "planeinforequestfsinn.h" + +#include "blackmisc/logmessage.h" + +namespace BlackCore +{ + namespace Fsd + { + PlaneInfoRequestFsinn::PlaneInfoRequestFsinn() : MessageBase() + { } + + PlaneInfoRequestFsinn::PlaneInfoRequestFsinn(const QString &sender, + const QString &receiver, + const QString &airlineIcao, + const QString &aircraftIcao, + const QString &aircraftIcaoCombinedType, + const QString &sendMModelString) + : MessageBase(sender, receiver), + m_airlineIcao(airlineIcao), + m_aircraftIcao(aircraftIcao), + m_aircraftIcaoCombinedType(aircraftIcaoCombinedType), + m_sendMModelString(sendMModelString) + { } + + QStringList PlaneInfoRequestFsinn::toTokens() const + { + auto tokens = QStringList {}; + tokens.push_back(m_sender); + tokens.push_back(m_receiver); + tokens.push_back("FSIPIR"); + tokens.push_back("0"); + tokens.push_back(m_airlineIcao); + tokens.push_back(m_aircraftIcao); + tokens.push_back({}); + tokens.push_back({}); + tokens.push_back({}); + tokens.push_back({}); + tokens.push_back(m_aircraftIcaoCombinedType); + tokens.push_back(m_sendMModelString); + return tokens; + } + + PlaneInfoRequestFsinn PlaneInfoRequestFsinn::fromTokens(const QStringList &tokens) + { + if (tokens.size() != 12) + { + BlackMisc::CLogMessage(static_cast(nullptr)).debug(u"Wrong number of arguments."); + return {}; + }; + return PlaneInfoRequestFsinn(tokens[0], tokens[1], tokens[4], tokens[5], tokens[10], tokens[11]); + } + } +} diff --git a/src/blackcore/fsd/planeinforequestfsinn.h b/src/blackcore/fsd/planeinforequestfsinn.h new file mode 100644 index 000000000..02693e6d7 --- /dev/null +++ b/src/blackcore/fsd/planeinforequestfsinn.h @@ -0,0 +1,62 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKCORE_FSD_PLANEINFOREQUESTFSINN_H +#define BLACKCORE_FSD_PLANEINFOREQUESTFSINN_H + +#include "messagebase.h" + +namespace BlackCore +{ + namespace Fsd + { + class BLACKCORE_EXPORT PlaneInfoRequestFsinn : public MessageBase + { + public: + PlaneInfoRequestFsinn(const QString &sender, + const QString &receiver, + const QString &airlineIcao, + const QString &aircraftIcao, + const QString &aircraftIcaoCombinedType, + const QString &sendMModelString); + + virtual ~PlaneInfoRequestFsinn() {} + + QStringList toTokens() const; + static PlaneInfoRequestFsinn fromTokens(const QStringList &tokens); + static QString pdu() { return QStringLiteral("#SB"); } + + QString m_airlineIcao; + QString m_aircraftIcao; + QString m_aircraftIcaoCombinedType; + QString m_sendMModelString; + + private: + PlaneInfoRequestFsinn(); + }; + + inline bool operator==(const PlaneInfoRequestFsinn &lhs, const PlaneInfoRequestFsinn &rhs) + { + return lhs.sender() == rhs.sender() && + lhs.receiver() == rhs.receiver() && + lhs.m_airlineIcao == rhs.m_airlineIcao && + lhs.m_aircraftIcao == rhs.m_aircraftIcao && + lhs.m_aircraftIcaoCombinedType == rhs.m_aircraftIcaoCombinedType && + lhs.m_sendMModelString == rhs.m_sendMModelString; + } + + inline bool operator!=(const PlaneInfoRequestFsinn &lhs, const PlaneInfoRequestFsinn &rhs) + { + return !(lhs == rhs); + } + } +} + +#endif // guard diff --git a/src/blackcore/fsd/planeinformation.cpp b/src/blackcore/fsd/planeinformation.cpp new file mode 100644 index 000000000..1cbaadf1c --- /dev/null +++ b/src/blackcore/fsd/planeinformation.cpp @@ -0,0 +1,70 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +#include "planeinformation.h" + +#include "blackmisc/logmessage.h" + +namespace BlackCore +{ + namespace Fsd + { + PlaneInformation::PlaneInformation() : MessageBase() + { } + + PlaneInformation::PlaneInformation(const QString &sender, const QString &receiver, const QString &aircraft, + const QString &airline = QString(), const QString &livery = QString()) + : MessageBase(sender, receiver), + m_aircraft(aircraft), + m_airline(airline), + m_livery(livery) + { } + + QStringList PlaneInformation::toTokens() const + { + QStringList pairs; + if (!m_aircraft.isEmpty()) { pairs << QString("EQUIPMENT=" + m_aircraft); } + if (!m_airline.isEmpty()) { pairs << QString("AIRLINE=" + m_airline); } + if (!m_livery.isEmpty()) { pairs << QString("LIVERY=" + m_livery); } + + QStringList tokens; + tokens.push_back(m_sender); + tokens.push_back(m_receiver); + tokens.push_back("PI"); + tokens.push_back("GEN"); + tokens.push_back(pairs.join(":")); + return tokens; + } + + PlaneInformation PlaneInformation::fromTokens(const QStringList &tokens) + { + if (tokens.size() < 5) + { + BlackMisc::CLogMessage(static_cast(nullptr)).debug(u"Wrong number of arguments."); + return {}; + }; + + QString aircraft; + QString airline; + QString livery; + + for (int i = 4; i < tokens.size(); ++i) + { + QStringList pair = tokens.at(i).split("="); + if (pair.size() == 2) + { + if (pair[0] == QLatin1String("EQUIPMENT")) { aircraft = pair[1]; } + else if (pair[0] == QLatin1String("AIRLINE")) { airline = pair[1]; } + else if (pair[0] == QLatin1String("LIVERY")) { livery = pair[1]; } + } + } + return PlaneInformation(tokens[0], tokens[1], aircraft, airline, livery); + } + } +} + diff --git a/src/blackcore/fsd/planeinformation.h b/src/blackcore/fsd/planeinformation.h new file mode 100644 index 000000000..0bac3b3fb --- /dev/null +++ b/src/blackcore/fsd/planeinformation.h @@ -0,0 +1,58 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKCORE_FSD_PLANEINFORMATION_H +#define BLACKCORE_FSD_PLANEINFORMATION_H + +#include "messagebase.h" + +namespace BlackCore +{ + namespace Fsd + { + //! This packet is sent in reply to a PIR request to inform the client which multiplayer model to use. + //! The airline and livery fields are optional. + class BLACKCORE_EXPORT PlaneInformation : public MessageBase + { + public: + PlaneInformation(const QString &sender, const QString &receiver, const QString &aircraft, const QString &airline, const QString &livery); + + virtual ~PlaneInformation() {} + + QStringList toTokens() const; + static PlaneInformation fromTokens(const QStringList &tokens); + static QString pdu() { return "#SB"; } + + QString m_aircraft; + QString m_airline; + QString m_livery; + + private: + + PlaneInformation(); + }; + + inline bool operator==(const PlaneInformation &lhs, const PlaneInformation &rhs) + { + return lhs.sender() == rhs.sender() && + lhs.receiver() == rhs.receiver() && + lhs.m_aircraft == rhs.m_aircraft && + lhs.m_airline == rhs.m_airline && + lhs.m_livery == rhs.m_livery; + } + + inline bool operator!=(const PlaneInformation &lhs, const PlaneInformation &rhs) + { + return !(lhs == rhs); + } + } +} + +#endif // guard diff --git a/src/blackcore/fsd/planeinformationfsinn.cpp b/src/blackcore/fsd/planeinformationfsinn.cpp new file mode 100644 index 000000000..cd376a147 --- /dev/null +++ b/src/blackcore/fsd/planeinformationfsinn.cpp @@ -0,0 +1,63 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +#include "planeinformationfsinn.h" + +#include "blackmisc/logmessage.h" + +namespace BlackCore +{ + namespace Fsd + { + PlaneInformationFsinn::PlaneInformationFsinn() : MessageBase() + { } + + PlaneInformationFsinn::PlaneInformationFsinn(const QString &sender, + const QString &receiver, + const QString &airlineIcao, + const QString &aircraftIcao, + const QString &aircraftIcaoCombinedType, + const QString &sendMModelString) + : MessageBase(sender, receiver), + m_airlineIcao(airlineIcao), + m_aircraftIcao(aircraftIcao), + m_aircraftIcaoCombinedType(aircraftIcaoCombinedType), + m_sendMModelString(sendMModelString) + { } + + QStringList PlaneInformationFsinn::toTokens() const + { + auto tokens = QStringList {}; + tokens.push_back(m_sender); + tokens.push_back(m_receiver); + tokens.push_back("FSIPI"); + tokens.push_back("0"); + tokens.push_back(m_airlineIcao); + tokens.push_back(m_aircraftIcao); + tokens.push_back({}); + tokens.push_back({}); + tokens.push_back({}); + tokens.push_back({}); + tokens.push_back(m_aircraftIcaoCombinedType); + tokens.push_back(m_sendMModelString); + return tokens; + } + + PlaneInformationFsinn PlaneInformationFsinn::fromTokens(const QStringList &tokens) + { + if (tokens.size() != 12) + { + BlackMisc::CLogMessage(static_cast(nullptr)).debug(u"Wrong number of arguments."); + return {}; + }; + + return PlaneInformationFsinn(tokens[0], tokens[1], tokens[4], tokens[5], tokens[10], tokens[11]); + } + } +} + diff --git a/src/blackcore/fsd/planeinformationfsinn.h b/src/blackcore/fsd/planeinformationfsinn.h new file mode 100644 index 000000000..72c7f877d --- /dev/null +++ b/src/blackcore/fsd/planeinformationfsinn.h @@ -0,0 +1,63 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKCORE_FSD_PLANEINFORMATIONFSINN_H +#define BLACKCORE_FSD_PLANEINFORMATIONFSINN_H + +#include "messagebase.h" + +namespace BlackCore +{ + namespace Fsd + { + //! This packet is sent in reply to a FSinn request (FSIPIR) + class BLACKCORE_EXPORT PlaneInformationFsinn : public MessageBase + { + public: + PlaneInformationFsinn(const QString &sender, + const QString &receiver, + const QString &airlineIcao, + const QString &aircraftIcao, + const QString &aircraftIcaoCombinedType, + const QString &sendMModelString); + + virtual ~PlaneInformationFsinn() {} + + QStringList toTokens() const; + static PlaneInformationFsinn fromTokens(const QStringList &tokens); + static QString pdu() { return "#SB"; } + + QString m_airlineIcao; + QString m_aircraftIcao; + QString m_aircraftIcaoCombinedType; + QString m_sendMModelString; + + private: + PlaneInformationFsinn(); + }; + + inline bool operator==(const PlaneInformationFsinn &lhs, const PlaneInformationFsinn &rhs) + { + return lhs.sender() == rhs.sender() && + lhs.receiver() == rhs.receiver() && + lhs.m_airlineIcao == rhs.m_airlineIcao && + lhs.m_aircraftIcao == rhs.m_aircraftIcao && + lhs.m_aircraftIcaoCombinedType == rhs.m_aircraftIcaoCombinedType && + lhs.m_sendMModelString == rhs.m_sendMModelString; + } + + inline bool operator!=(const PlaneInformationFsinn &lhs, const PlaneInformationFsinn &rhs) + { + return !(lhs == rhs); + } + } +} + +#endif // guard diff --git a/src/blackcore/fsd/pong.cpp b/src/blackcore/fsd/pong.cpp new file mode 100644 index 000000000..c3dbd99b4 --- /dev/null +++ b/src/blackcore/fsd/pong.cpp @@ -0,0 +1,44 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +#include "pong.h" + +#include "blackmisc/logmessage.h" + +namespace BlackCore +{ + namespace Fsd + { + Pong::Pong() : MessageBase() + { } + + Pong::Pong(const QString &sender, const QString &receiver, const QString ×tamp) + : MessageBase(sender, receiver), + m_timestamp(timestamp) + { } + + QStringList Pong::toTokens() const + { + QStringList tokens; + tokens.push_back(m_sender); + tokens.push_back(m_receiver); + tokens.push_back(m_timestamp); + return tokens; + } + + Pong Pong::fromTokens(const QStringList &tokens) + { + if (tokens.size() < 3) + { + BlackMisc::CLogMessage(static_cast(nullptr)).debug(u"Wrong number of arguments."); + return {}; + }; + return Pong(tokens[0], tokens[1], tokens[2]); + } + } +} diff --git a/src/blackcore/fsd/pong.h b/src/blackcore/fsd/pong.h new file mode 100644 index 000000000..135b25188 --- /dev/null +++ b/src/blackcore/fsd/pong.h @@ -0,0 +1,51 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKCORE_FSD_PONG_H +#define BLACKCORE_FSD_PONG_H + +#include "messagebase.h" + +namespace BlackCore +{ + namespace Fsd + { + class BLACKCORE_EXPORT Pong : public MessageBase + { + public: + Pong(const QString &sender, const QString &receiver, const QString ×tamp); + + virtual ~Pong() {} + + QStringList toTokens() const; + static Pong fromTokens(const QStringList &tokens); + static QString pdu() { return "$PO"; } + + QString m_timestamp; + + private: + Pong(); + }; + + inline bool operator==(const Pong &lhs, const Pong &rhs) + { + return lhs.sender() == rhs.sender() && + lhs.receiver() == rhs.receiver() && + lhs.m_timestamp == rhs.m_timestamp; + } + + inline bool operator!=(const Pong &lhs, const Pong &rhs) + { + return !(lhs == rhs); + } + } +} + +#endif // guard diff --git a/src/blackcore/fsd/serializer.cpp b/src/blackcore/fsd/serializer.cpp new file mode 100644 index 000000000..5937590ba --- /dev/null +++ b/src/blackcore/fsd/serializer.cpp @@ -0,0 +1,311 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +#include "serializer.h" + +using namespace BlackMisc::Aviation; + +namespace BlackCore +{ + namespace Fsd + { + template<> + QString toQString(const AtcRating &value) + { + switch (value) + { + case AtcRating::Unknown: return "0"; + case AtcRating::Observer: return "1"; + case AtcRating::Student: return "2"; + case AtcRating::Student2: return "3"; + case AtcRating::Student3: return "4"; + case AtcRating::Controller1: return "5"; + case AtcRating::Controller2: return "6"; + case AtcRating::Controller3: return "7"; + case AtcRating::Instructor1: return "8"; + case AtcRating::Instructor2: return "9"; + case AtcRating::Instructor3: return "10"; + case AtcRating::Supervisor: return "11"; + case AtcRating::Administrator: return "12"; + } + Q_UNREACHABLE(); + return {}; + } + + template<> + AtcRating fromQString(const QString &str) + { + if (str == "1") return AtcRating::Observer; + else if (str == "2") return AtcRating::Student; + else if (str == "3") return AtcRating::Student2; + else if (str == "4") return AtcRating::Student3; + else if (str == "5") return AtcRating::Controller1; + else if (str == "6") return AtcRating::Controller2; + else if (str == "7") return AtcRating::Controller3; + else if (str == "8") return AtcRating::Instructor1; + else if (str == "9") return AtcRating::Instructor2; + else if (str == "10") return AtcRating::Instructor3; + else if (str == "11") return AtcRating::Supervisor; + else if (str == "12") return AtcRating::Administrator; + else return AtcRating::Unknown; + } + + template<> + QString toQString(const PilotRating &value) + { + switch (value) + { + case PilotRating::Unknown: return "0"; + case PilotRating::Student: return "1"; + case PilotRating::VFR: return "2"; + case PilotRating::IFR: return "3"; + case PilotRating::Instructor: return "4"; + case PilotRating::Supervisor: return "5"; + } + Q_UNREACHABLE(); + return {}; + } + + template<> + PilotRating fromQString(const QString &str) + { + if (str == "0") return PilotRating::Unknown; + else if (str == "1") return PilotRating::Student; + else if (str == "2") return PilotRating::VFR; + else if (str == "3") return PilotRating::IFR; + else if (str == "4") return PilotRating::Instructor; + else if (str == "5") return PilotRating::Supervisor; + Q_UNREACHABLE(); + return {}; + } + + template<> + QString toQString(const SimType &value) + { + switch (value) + { + case SimType::Unknown: return "0"; + case SimType::MSFS95: return "1"; + case SimType::MSFS98: return "2"; + case SimType::MSCFS: return "3"; + case SimType::MSFS2000: return "4"; + case SimType::MSCFS2: return "5"; + case SimType::MSFS2002: return "6"; + case SimType::MSCFS3: return "7"; + case SimType::MSFS2004: return "8"; + case SimType::MSFSX: return "9"; + case SimType::XPLANE8: return "12"; + case SimType::XPLANE9: return "13"; + case SimType::XPLANE10: return "14"; + case SimType::XPLANE11: return "16"; + case SimType::FlightGear: return "25"; + case SimType::P3Dv1: return "30"; + case SimType::P3Dv2: return "30"; + case SimType::P3Dv3: return "30"; + case SimType::P3Dv4: return "30"; + } + Q_UNREACHABLE(); + return {}; + } + + template<> + SimType fromQString(const QString &str) + { + if (str == "0") return SimType::Unknown; + else if (str == "1") return SimType::MSFS95; + else if (str == "2") return SimType::MSFS98; + else if (str == "3") return SimType::MSCFS; + else if (str == "4") return SimType::MSFS2000; + else if (str == "5") return SimType::MSCFS2; + else if (str == "6") return SimType::MSFS2002; + else if (str == "7") return SimType::MSCFS3; + else if (str == "8") return SimType::MSFS2004; + else if (str == "9") return SimType::MSFSX; + else if (str == "12") return SimType::XPLANE8; + else if (str == "13") return SimType::XPLANE9; + else if (str == "14") return SimType::XPLANE10; + else if (str == "16") return SimType::XPLANE11; + else if (str == "25") return SimType::FlightGear; + // Still ambigious which P3D version. No standard defined yet. + else if (str == "30") return SimType::P3Dv4; + else return SimType::Unknown; + } + + template<> + QString toQString(const BlackMisc::Network::CFacilityType &value) + { + using namespace BlackMisc::Network; + switch (value.getFacilityType()) + { + case CFacilityType::OBS: return "0"; + case CFacilityType::FSS: return "1"; + case CFacilityType::DEL: return "2"; + case CFacilityType::GND: return "3"; + case CFacilityType::TWR: return "4"; + case CFacilityType::APP: return "5"; + case CFacilityType::CTR: return "6"; + case CFacilityType::Unknown: return {}; + } + Q_UNREACHABLE(); + return {}; + } + + template<> + BlackMisc::Network::CFacilityType fromQString(const QString &str) + { + using namespace BlackMisc::Network; + if (str == "0") return CFacilityType::OBS; + if (str == "1") return CFacilityType::FSS; + else if (str == "2") return CFacilityType::DEL; + else if (str == "3") return CFacilityType::GND; + else if (str == "4") return CFacilityType::TWR; + else if (str == "5") return CFacilityType::APP; + else if (str == "6") return CFacilityType::CTR; + else return CFacilityType::Unknown; + } + + template<> + QString toQString(const ClientQueryType &value) + { + switch (value) + { + case ClientQueryType::IsValidATC: return "ATC"; + case ClientQueryType::Capabilities: return "CAPS"; + case ClientQueryType::Com1Freq: return "C?"; + case ClientQueryType::RealName: return "RN"; + case ClientQueryType::Server: return "SV"; + case ClientQueryType::ATIS: return "ATIS"; + case ClientQueryType::PublicIP: return "IP"; + case ClientQueryType::INF: return "INF"; + case ClientQueryType::FP: return "FP"; + case ClientQueryType::AircraftConfig: return "ACC"; + case ClientQueryType::Unknown: qFatal("Don't serialize ClientQueryType::Unknown!"); + } + Q_UNREACHABLE(); + return {}; + } + + template<> + ClientQueryType fromQString(const QString &str) + { + if (str == "ATC") return ClientQueryType::IsValidATC; + else if (str == "CAPS") return ClientQueryType::Capabilities; + else if (str == "C?") return ClientQueryType::Com1Freq; + else if (str == "RN") return ClientQueryType::RealName; + else if (str == "SV") return ClientQueryType::Server; + else if (str == "ATIS") return ClientQueryType::ATIS; + else if (str == "IP") return ClientQueryType::PublicIP; + else if (str == "INF") return ClientQueryType::INF; + else if (str == "FP") return ClientQueryType::FP; + else if (str == "ACC") return ClientQueryType::AircraftConfig; + else + { + // networkLog(vatSeverityDebug, "ClientQueryType fromString(str)", "Unknown client query type"); + return ClientQueryType::Unknown; + } + } + + template<> + QString toQString(const FlightType &value) + { + switch (value) + { + case FlightType::IFR: return "I"; + case FlightType::VFR: return "V"; + case FlightType::SVFR: return "S"; + case FlightType::DVFR: return "D"; + } + Q_UNREACHABLE(); + return {}; + } + + template<> + FlightType fromQString(const QString &str) + { + if (str == QLatin1String("I")) return FlightType::IFR; + else if (str == QLatin1String("V")) return FlightType::VFR; + else if (str == QLatin1String("S")) return FlightType::SVFR; + else if (str == QLatin1String("D")) return FlightType::DVFR; + else return FlightType::IFR; + } + + template<> + QString toQString(const CTransponder::TransponderMode &value) + { + switch (value) + { + case CTransponder::StateStandby: + return QStringLiteral("S"); + case CTransponder::ModeMil1: + case CTransponder::ModeMil2: + case CTransponder::ModeMil3: + case CTransponder::ModeMil4: + case CTransponder::ModeMil5: + case CTransponder::ModeA: + case CTransponder::ModeC: + case CTransponder::ModeS: + return QStringLiteral("N"); + case CTransponder::StateIdent: + return QStringLiteral("Y"); + } + Q_UNREACHABLE(); + return {}; + } + + template<> + CTransponder::TransponderMode fromQString(const QString &str) + { + if (str == "S") return CTransponder::StateStandby; + else if (str == "N") return CTransponder::ModeC; + else if (str == "Y") return CTransponder::StateIdent; + else return CTransponder::StateStandby; + } + + template<> + QString toQString(const Capabilities& value) + { + switch (value) + { + case Capabilities::None: return {}; + case Capabilities::AtcInfo: return "ATCINFO"; + case Capabilities::SecondaryPos: return "SECPOS"; + case Capabilities::AircraftInfo: return "MODELDESC"; + case Capabilities::OngoingCoord: return "ONGOINGCOORD"; + case Capabilities::InterminPos: return "INTERIMPOS"; + case Capabilities::FastPos: return "FASTPOS"; + case Capabilities::Stealth: return "STEALTH"; + case Capabilities::AircraftConfig: return "ACCONFIG"; + } + return {}; + } + + template<> + Capabilities fromQString(const QString &str) + { + if (str == "ATCINFO") return Capabilities::AtcInfo; + else if (str == "SECPOS") return Capabilities::SecondaryPos; + else if (str == "MODELDESC") return Capabilities::AircraftInfo; + else if (str == "ONGOINGCOORD") return Capabilities::OngoingCoord; + else if (str == "INTERIMPOS") return Capabilities::InterminPos; + else if (str == "FASTPOS") return Capabilities::FastPos; + else if (str == "STEALTH") return Capabilities::Stealth; + else if (str == "ACCONFIG") return Capabilities::AircraftConfig; + else return Capabilities::None; + } + + template<> + AtisLineType fromQString(const QString &str) + { + if (str == "V") return AtisLineType::VoiceRoom; + else if (str == "Z") return AtisLineType::ZuluLogoff; + else if (str == "T") return AtisLineType::TextMessage; + else if (str == "E") return AtisLineType::LineCount; + else return AtisLineType::Unknown; + } + } +} diff --git a/src/blackcore/fsd/serializer.h b/src/blackcore/fsd/serializer.h new file mode 100644 index 000000000..91135d826 --- /dev/null +++ b/src/blackcore/fsd/serializer.h @@ -0,0 +1,85 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKCORE_FSD_SERIALIZER_H +#define BLACKCORE_FSD_SERIALIZER_H + +#include "enums.h" +#include "blackmisc/aviation/transponder.h" +#include "blackmisc/network/facilitytype.h" + +#include +#include + +namespace BlackCore +{ + namespace Fsd + { + template + inline QString toQString(const T& value); + + template + T fromQString(const QString &str); + + template<> + QString toQString(const AtcRating &value); + + template<> + AtcRating fromQString(const QString &str); + + template<> + QString toQString(const PilotRating &value); + + template<> + PilotRating fromQString(const QString &str); + + template<> + QString toQString(const SimType &value); + + template<> + SimType fromQString(const QString &str); + + template<> + QString toQString(const BlackMisc::Network::CFacilityType &value); + + template<> + BlackMisc::Network::CFacilityType fromQString(const QString &str); + + template<> + QString toQString(const ClientQueryType &value); + + template<> + ClientQueryType fromQString(const QString &str); + + template<> + QString toQString(const FlightType &value); + + template<> + FlightType fromQString(const QString &str); + + template<> + QString toQString(const BlackMisc::Aviation::CTransponder::TransponderMode &value); + + template<> + BlackMisc::Aviation::CTransponder::TransponderMode fromQString(const QString &str); + + template<> + QString toQString(const Capabilities& value); + + template<> + Capabilities fromQString(const QString &str); + + template<> + AtisLineType fromQString(const QString &str); + + } +} + +#endif // guard diff --git a/src/blackcore/fsd/servererror.cpp b/src/blackcore/fsd/servererror.cpp new file mode 100644 index 000000000..ccc83374c --- /dev/null +++ b/src/blackcore/fsd/servererror.cpp @@ -0,0 +1,71 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +#include "servererror.h" +#include + +#include "blackmisc/logmessage.h" + +namespace BlackCore +{ + namespace Fsd + { + ServerError::ServerError() : MessageBase() + { } + + ServerError::ServerError(const QString &sender, const QString &receiver, ServerErrorCode errorCode, const QString &causingParameter, const QString &description) + : MessageBase(sender, receiver), + m_errorNumber(errorCode), + m_causingParameter(causingParameter), + m_description(description) + { } + + bool ServerError::isFatalError() const + { + static const QVector fatalErrors + { + ServerErrorCode::CallsignInUse, + ServerErrorCode::InvalidCallsign, + ServerErrorCode::AlreadyRegistered, + ServerErrorCode::InvalidCidPassword, + ServerErrorCode::InvalidRevision, + ServerErrorCode::RequestedLevelTooHigh, + ServerErrorCode::ServerFull, + ServerErrorCode::CidSuspended, + ServerErrorCode::RatingTooLow, + ServerErrorCode::InvalidClient, + ServerErrorCode::AuthTimeout, + }; + + if(fatalErrors.contains(m_errorNumber)) { return true; } + else { return false; } + } + + QStringList ServerError::toTokens() const + { + auto tokens = QStringList {}; + tokens.push_back(m_sender); + tokens.push_back(m_receiver); + tokens.push_back(QString::number(static_cast(m_errorNumber))); + tokens.push_back(m_causingParameter); + tokens.push_back(m_description); + return tokens; + } + + ServerError ServerError::fromTokens(const QStringList &tokens) + { + if (tokens.size() < 5) + { + BlackMisc::CLogMessage(static_cast(nullptr)).debug(u"Wrong number of arguments."); + return {}; + }; + return ServerError(tokens[0], tokens[1], static_cast(tokens[2].toInt()), tokens[3], tokens[4]); + } + } +} + diff --git a/src/blackcore/fsd/servererror.h b/src/blackcore/fsd/servererror.h new file mode 100644 index 000000000..d883f8363 --- /dev/null +++ b/src/blackcore/fsd/servererror.h @@ -0,0 +1,58 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKCORE_FSD_SERVERERROR_H +#define BLACKCORE_FSD_SERVERERROR_H + +#include "messagebase.h" +#include "enums.h" + +namespace BlackCore +{ + namespace Fsd + { + class BLACKCORE_EXPORT ServerError : public MessageBase + { + public: + ServerError(const QString &sender, const QString &receiver, ServerErrorCode errorCode, const QString &causingParameter, const QString &description); + + virtual ~ServerError() {} + + bool isFatalError () const; + + QStringList toTokens() const; + static ServerError fromTokens(const QStringList &tokens); + static QString pdu() { return "$ER"; } + + ServerErrorCode m_errorNumber; + QString m_causingParameter; + QString m_description; + + private: + ServerError(); + }; + + inline bool operator==(const ServerError &lhs, const ServerError &rhs) + { + return lhs.sender() == rhs.sender() && + lhs.receiver() == rhs.receiver() && + lhs.m_errorNumber == rhs.m_errorNumber && + lhs.m_causingParameter == rhs.m_causingParameter && + lhs.m_description == rhs.m_description; + } + + inline bool operator!=(const ServerError &lhs, const ServerError &rhs) + { + return !(lhs == rhs); + } + } +} + +#endif // guard diff --git a/src/blackcore/fsd/textmessage.cpp b/src/blackcore/fsd/textmessage.cpp new file mode 100644 index 000000000..45afb100a --- /dev/null +++ b/src/blackcore/fsd/textmessage.cpp @@ -0,0 +1,61 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +#include "textmessage.h" + +#include "blackmisc/logmessage.h" + +namespace BlackCore +{ + namespace Fsd + { + TextMessage::TextMessage() : MessageBase() + { } + + TextMessage::TextMessage(const QString &sender, const QString &receiver, const QString &message) + : MessageBase(sender, receiver), + m_message(message) + { + if (receiver.startsWith('@')) + { + m_type = RadioMessage; + const QStringList frequencyStrings = receiver.split('&'); + if (! frequencyStrings.isEmpty()) + { + for (QString frequencyString : frequencyStrings) + { + frequencyString.remove(0, 1); + int frequency = frequencyString.toInt() + 100000; + m_frequencies.push_back(frequency); + } + } + } + } + + QStringList TextMessage::toTokens() const + { + QStringList tokens; + tokens.push_back(m_sender); + tokens.push_back(m_receiver); + tokens.push_back(m_message); + return tokens; + } + + TextMessage TextMessage::fromTokens(const QStringList &tokens) + { + if (tokens.size() < 3) + { + BlackMisc::CLogMessage(static_cast(nullptr)).warning(u"Wrong number of arguments."); + return {}; + }; + + QStringList messageTokens = tokens.mid(2); + return TextMessage(tokens[0], tokens[1], messageTokens.join(":")); + } + } +} diff --git a/src/blackcore/fsd/textmessage.h b/src/blackcore/fsd/textmessage.h new file mode 100644 index 000000000..43c1e1101 --- /dev/null +++ b/src/blackcore/fsd/textmessage.h @@ -0,0 +1,50 @@ +/* Copyright (C) 2019 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKCORE_FSD_TEXTMESSAGE_H +#define BLACKCORE_FSD_TEXTMESSAGE_H + +#include "messagebase.h" + +#include +#include +#include + +namespace BlackCore +{ + namespace Fsd + { + class BLACKCORE_EXPORT TextMessage : public MessageBase + { + public: + enum Type + { + PrivateMessage, + RadioMessage, + }; + + TextMessage(const QString &sender, const QString &receiver, const QString &message); + virtual ~TextMessage() {} + + QStringList toTokens() const; + static TextMessage fromTokens(const QStringList &tokens); + static QString pdu() { return "#TM"; } + + QString m_message; + Type m_type = PrivateMessage; + QVector m_frequencies; + + private: + TextMessage(); + }; + } +} + +#endif // guard diff --git a/src/blackcore/network.cpp b/src/blackcore/network.cpp deleted file mode 100644 index 65612d855..000000000 --- a/src/blackcore/network.cpp +++ /dev/null @@ -1,147 +0,0 @@ -/* Copyright (C) 2019 - * swift Project Community / Contributors - * - * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level - * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, - * or distributed except according to the terms contained in the LICENSE file. - */ - -#include "network.h" -#include "blackcore/application.h" -#include "blackmisc/fileutils.h" -#include "blackmisc/directoryutils.h" -#include "blackmisc/logmessage.h" -#include "blackmisc/range.h" -#include "blackconfig/buildconfig.h" - -#include - -using namespace BlackConfig; -using namespace BlackMisc; -using namespace BlackMisc::Network; -using namespace BlackMisc::Simulation; - -namespace BlackCore -{ - INetwork::INetwork(IClientProvider *clientProvider, IOwnAircraftProvider *ownAircraftProvider, IRemoteAircraftProvider *remoteAircraftProvider, QObject *parent) : - QObject(parent), - CClientAware(clientProvider), - COwnAircraftAware(ownAircraftProvider), - CRemoteAircraftAware(remoteAircraftProvider) - { - if (CBuildConfig::isLocalDeveloperDebugBuild()) - { - CLogMessage("Enabled network statistics"); - m_statistics = true; - } - } - - INetwork::~INetwork() - { } - - const QString &INetwork::modeAsString(INetwork::LoginMode mode) - { - static const QString n("normal"); - static const QString o("observer"); - static const QString s("stealth"); - - switch (mode) - { - case LoginNormal: return n; - case LoginAsObserver: return o; - case LoginStealth: return s; - default: break; - } - static const QString unknown("????"); - return unknown; - } - - int INetwork::increaseStatisticsValue(const QString &identifier, const QString &appendix) - { - if (identifier.isEmpty() || !m_statistics) { return -1; } - const QString i = appendix.isEmpty() ? identifier : identifier % u"." % appendix; - int &v = m_callStatistics[i]; - v++; - - constexpr int MaxTimeValues = 50; - m_callByTime.push_front(QPair(QDateTime::currentMSecsSinceEpoch(), i)); - if (m_callByTime.size() > MaxTimeValues) { m_callByTime.removeLast(); } - return v; - } - - int INetwork::increaseStatisticsValue(const QString &identifier, int value) - { - return this->increaseStatisticsValue(identifier, QString::number(value)); - } - - void INetwork::clearStatistics() - { - m_callStatistics.clear(); - m_callByTime.clear(); - } - - QString INetwork::getNetworkStatisticsAsText(bool reset, const QString &separator) - { - QVector> transformed; - if (m_callStatistics.isEmpty()) { return QString(); } - - for (const auto pair : makePairsRange(as_const(m_callStatistics))) - { - // key is pair.first, value is pair.second - transformed.push_back({ pair.second, pair.first }); - } - - // sorted by value - std::sort(transformed.begin(), transformed.end(), std::greater<>()); - QString stats; - for (const auto &pair : transformed) - { - stats += - (stats.isEmpty() ? QString() : separator) % - pair.second % u": " % QString::number(pair.first); - } - - for (const auto &pair : transformed) - { - stats += - (stats.isEmpty() ? QString() : separator) % - pair.second % u": " % QString::number(pair.first); - } - - if (!m_callByTime.isEmpty()) - { - const qint64 lastTs = m_callByTime.front().first; - for (const auto &pair : m_callByTime) - { - const qint64 deltaTs = lastTs - pair.first; - stats += separator % QStringLiteral("%1").arg(deltaTs, 5, 10, QChar('0')) % u": " % pair.second; - } - } - - if (reset) { this->clearStatistics(); } - return stats; - } - - bool INetwork::saveNetworkStatistics(const QString &server) - { - if (m_callStatistics.isEmpty()) { return false; } - - const QString s = this->getNetworkStatisticsAsText(false, "\n"); - if (s.isEmpty()) { return false; } - const QString fn = QStringLiteral("networkstatistics_%1_%2.log").arg(QDateTime::currentDateTimeUtc().toString("yyMMddhhmmss"), server); - const QString fp = CFileUtils::appendFilePaths(CDirectoryUtils::logDirectory(), fn); - return CFileUtils::writeStringToFile(s, fp); - } - - void INetwork::connectedToNewtork() - { - if (!sApp || sApp->isShuttingDown()) { return; } - if (!m_statistics && (CBuildConfig::isLocalDeveloperDebugBuild() || sApp->getOwnDistribution().isRestricted())) - { - // enable for local and restricted versions (alpha, ...) - CLogMessage("Enabled network statistics"); - m_statistics = true; - } - } - -} // ns diff --git a/src/blackcore/network.h b/src/blackcore/network.h deleted file mode 100644 index 63723b704..000000000 --- a/src/blackcore/network.h +++ /dev/null @@ -1,644 +0,0 @@ -/* Copyright (C) 2013 - * swift Project Community / Contributors - * - * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level - * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, - * or distributed except according to the terms contained in the LICENSE file. - */ - -//! \file - -#ifndef BLACKCORE_NETWORK_H -#define BLACKCORE_NETWORK_H - -#include "blackcoreexport.h" -#include "blackmisc/simulation/ownaircraftprovider.h" -#include "blackmisc/simulation/remoteaircraftprovider.h" -#include "blackmisc/simulation/simulationenvironmentprovider.h" -#include "blackmisc/simulation/simulatorplugininfo.h" -#include "blackmisc/simulation/simulatedaircraft.h" -#include "blackmisc/network/rawfsdmessage.h" -#include "blackmisc/network/serverlist.h" -#include "blackmisc/network/clientprovider.h" -#include "blackmisc/network/ecosystemprovider.h" -#include "blackmisc/network/clientlist.h" -#include "blackmisc/network/textmessagelist.h" -#include "blackmisc/aviation/informationmessage.h" -#include "blackmisc/aviation/flightplan.h" -#include "blackmisc/aviation/callsignset.h" -#include "blackmisc/geo/coordinategeodetic.h" -#include "blackmisc/pq/frequency.h" -#include "blackmisc/pq/length.h" -#include "blackmisc/pq/time.h" - -#include -#include -#include -#include -#include -#include -#include - -namespace BlackCore -{ - /*! - * Interface for a connection to a multi-user flight simulation and ATC network. - * - * The connection can be in one of three essential states: disconnected, connecting, and - * connected. (There is a fourth state, disconnected due to error, which is a substate of - * disconnected.) Some slots may only be called when connected, and some may only be called - * when disconnected; there is a naming convention to highlight this fact using prefixes: - * "preset" slots are only callable when disconnected, "send" slots are only callable when - * connected, and "set" slots are callable in any state. - * - * Slots with the word "query" in their names are handled asynchronously, with one or more - * "reply" signals being sent in response to each invokation of a query slot. - * - * \warning If an INetwork signal is connected to a slot, and that slot emits a signal - * which is connected to an INetwork slot, then at least one of those connections - * must be a Qt::QueuedConnection. - */ - class BLACKCORE_EXPORT INetwork : - public QObject, - public BlackMisc::Network::IEcosystemProvider, // provide info about used ecosystem - public BlackMisc::Network::CClientAware, // network can set client information - public BlackMisc::Simulation::COwnAircraftAware, // network vatlib consumes own aircraft data and sets ICAO/callsign data - public BlackMisc::Simulation::CRemoteAircraftAware, // check if we really need to process network packets (e.g. parts) - public BlackMisc::Simulation::CSimulationEnvironmentAware // allows to consume ground elevations - - { - Q_OBJECT - Q_INTERFACES(BlackMisc::Network::IEcosystemProvider) - - protected: - //! Constructor - INetwork( - BlackMisc::Network::IClientProvider *clientProvider, - BlackMisc::Simulation::IOwnAircraftProvider *ownAircraftProvider, - BlackMisc::Simulation::IRemoteAircraftProvider *remoteAircraftProvider, - QObject *parent = nullptr); - - public: - //! Destructor - virtual ~INetwork(); - - //! Underlying library info. - virtual const QString &getLibraryInfo(bool detailed) const = 0; - - //! Login modes - enum LoginMode - { - LoginNormal = 0, //!< Normal login - LoginAsObserver, //!< Login as observer - LoginStealth //!< Login stealth mode - }; - - //! Mode as string - static const QString &modeAsString(LoginMode mode); - - //! Status of the connection. - enum ConnectionStatus - { - Disconnected = 0, //!< Not connected - Disconnecting, //!< In transition to disconnected - DisconnectedError, //!< Disconnected due to socket error - DisconnectedFailed, //!< A connection was not established due to socket error - DisconnectedLost, //!< Connection lost due to socket error - Connecting, //!< Connection initiated but not established - Connected //!< Connection established - }; - Q_ENUM(ConnectionStatus) - - //! Statistics enable functions @{ - bool setStatisticsEnable(bool enabled) { m_statistics = enabled; return enabled; } - bool isStatisticsEnabled() const { return m_statistics; } - //! @} - - //! Increase the statistics value for given identifier @{ - int increaseStatisticsValue(const QString &identifier, const QString &appendix = {}); - int increaseStatisticsValue(const QString &identifier, int value); - //! @} - - //! Clear the statistics - void clearStatistics(); - - //! Text statistics - QString getNetworkStatisticsAsText(bool reset, const QString &separator = "\n"); - - //! Convert a ConnectionStatus to a string. - static QString connectionStatusToString(ConnectionStatus status) - { - const int index = staticMetaObject.indexOfEnumerator("ConnectionStatus"); - const QMetaEnum metaEnum = staticMetaObject.enumerator(index); - return metaEnum.valueToKey(status); - } - - /*! - * Returns true if the given ConnectionStatus represents an error state. - */ - static bool isErrorStatus(ConnectionStatus status) - { - return status == DisconnectedError; - } - - /*! - * Returns true if the given ConnectionStatus represents a pending state. - */ - static bool isPendingStatus(ConnectionStatus status) - { - return status == Disconnecting || status == Connecting; - } - - /*! - * Returns true if the given ConnectionStatus represents a disconnected state. - */ - static bool isDisconnectedStatus(ConnectionStatus status) - { - return status == Disconnected || status == DisconnectedError || - status == DisconnectedFailed || status == DisconnectedLost; - } - - /*! - * Returns true if the given ConnectionStatus represents a connected state. - */ - static bool isConnectedStatus(ConnectionStatus status) - { - return status == Connected; - } - - //////////////////////////////////////////////////////////////// - //! \name Network slots - //! @{ - //////////////////////////////////////////////////////////////// - - /*! - * Returns true if the current ConnectionStatus is a connected state. - */ - virtual bool isConnected() const = 0; - - /*! - * Get preset server. - */ - virtual const BlackMisc::Network::CServer &getPresetServer() const = 0; - - /*! - * Get preset values. - */ - virtual QStringList getPresetValues() const = 0; - - /*! - * Returns true if the current ConnectionStatus is in transition, e.g. connecting. - */ - virtual bool isPendingConnection() const = 0; - - /*! - * Set the server which will be connected to. - * \pre Network must be disconnected when calling this function. - */ - virtual void presetServer(const BlackMisc::Network::CServer &server) = 0; - - /*! - * Set our own callsign before connecting. - * \pre Network must be disconnected when calling this function. - */ - virtual void presetCallsign(const BlackMisc::Aviation::CCallsign &callsign) = 0; - - /*! - * Set the partner callsign (co-pilot/pilot in shared cockpit) before connecting. - * \pre Network must be disconnected when calling this function. - */ - virtual void presetPartnerCallsign(const BlackMisc::Aviation::CCallsign &callsign) = 0; - - /*! - * Get the partner callsign (co-pilot/pilot in shared cockpit) before connecting. - */ - virtual const BlackMisc::Aviation::CCallsign &getPresetPartnerCallsign() const = 0; - - /*! - * Set our own aircraft ICAO codes before connecting. - * \pre Network must be disconnected when calling this function. - */ - virtual void presetIcaoCodes(const BlackMisc::Simulation::CSimulatedAircraft &ownAircraft) = 0; - - /*! - * Select a login mode before connecting. - * \pre Network must be disconnected when calling this function. - */ - virtual void presetLoginMode(BlackCore::INetwork::LoginMode mode) = 0; - - /*! - * Set the model string and livery string before connecting. - * \pre Network must be disconnected when calling this function. - */ - virtual void presetLiveryAndModelString(const QString &livery, bool sendLiveryString, const QString &modelString, bool sendModelString) = 0; - - /*! - * Set simulator info before connecting. - * \pre Network must be disconnected when calling this function. - */ - virtual void presetSimulatorInfo(const BlackMisc::Simulation::CSimulatorPluginInfo &simInfo) = 0; - - /*! - * Initiate a connection to the network server. - * \pre Network must be disconnected when calling this function. - * \post Connection status changes from Disconnected to either Connecting or DisconnectedError. - */ - virtual void initiateConnection() = 0; - - /*! - * Ask the connection to the network server to terminate itself. - * \pre It is not legal to call this function when already disconnected. - * \post Connection status changes to Disconnected, but maybe not immediately. - */ - virtual void terminateConnection() = 0; - - /*! - * Send a ping message to a user with a specific callsign. - * \pre Network must be connected when calling this function. - * \sa pongReceived - */ - virtual void sendPing(const BlackMisc::Aviation::CCallsign &callsign) = 0; - - /*! - * Send a message querying the real name of the user with a specific callsign. - * \pre Network must be connected when calling this function. - * \sa realNameReplyReceived - */ - virtual void sendRealNameQuery(const BlackMisc::Aviation::CCallsign &callsign) = 0; - - /*! - * Send a message querying our own IP address as reported by the server. - * \pre Network must be connected when calling this function. - * \sa ipReplyReceived - */ - virtual void sendIpQuery() = 0; - - /*! - * Send a message querying which server the user with a specific callsign is connected to. - * \pre Network must be connected when calling this function. - * \sa serverReplyReceived - */ - virtual void sendServerQuery(const BlackMisc::Aviation::CCallsign &callsign) = 0; - - /*! - * Send one or more text messages. - * \pre Network must be connected when calling this function. - */ - virtual void sendTextMessages(const BlackMisc::Network::CTextMessageList &messages) = 0; - - /*! - * Send a wallop message. - * \pre Network must be connected when calling this function. - */ - virtual void sendWallopMessage(const QString &message) = 0; - - //! @} - //////////////////////////////////////////////////////////////// - //! \name Custom packets - //! @{ - //////////////////////////////////////////////////////////////// - - /*! - * Send a FSInn custom packet. - * \details FSIPI(R) queries, some example data below: - *
- * index 0 .. 0/1 ??? - * 1 .. MQT, GEC, DLH -> Airline ICAO, most of the time empty - * 2 .. AIRCRAFT ICAO "B747" - * 3 .. 10.64195 - * 4 .. 1.06080: - * 5 .. 5825.00000 - * 6 .. 4.DD89CCB6.EC9BB7D7 - * 7 .. 3-letter combined L2J - * 8 .. Model string - *
- */ - //! @{ - virtual void sendCustomFsinnQuery(const BlackMisc::Aviation::CCallsign &callsign) = 0; - virtual void sendCustomFsinnReponse(const BlackMisc::Aviation::CCallsign &callsign) = 0; - //! @} - - //! Broadcast an incremental aircraft config - virtual void broadcastAircraftConfig(const QJsonObject &config) = 0; - - //! Query callsign for its current full aircraft config - virtual void sendAircraftConfigQuery(const BlackMisc::Aviation::CCallsign &callsign) = 0; - - //! @} - //////////////////////////////////////////////////////////////// - //! \name ATC slots - //! @{ - //////////////////////////////////////////////////////////////// - - /*! - * Send a message querying whether or not the user with a specific callsign is an ATC station. - * \pre Network must be connected when calling this function. - * \sa atcReplyReceived - */ - virtual void sendAtcQuery(const BlackMisc::Aviation::CCallsign &callsign) = 0; - - /*! - * Send a message querying the ATIS for the ATC station with a specific callsign. - * \pre Network must be connected when calling this function. - * \sa atisReplyReceived - * \sa atisVoiceRoomReplyReceived - * \sa atisLogoffTimeReplyReceived - */ - virtual void sendAtisQuery(const BlackMisc::Aviation::CCallsign &callsign) = 0; - - /*! - * Send a message to file a flight plan. - * \pre Network must be connected when calling this function. - */ - virtual void sendFlightPlan(const BlackMisc::Aviation::CFlightPlan &flightPlan) = 0; - - /*! - * Send a message querying our currently filed flight plan, which may have been amended by a controller. - * \pre Network must be connected when calling this function. - * \sa flightPlanReplyReceived - */ - virtual void sendFlightPlanQuery(const BlackMisc::Aviation::CCallsign &callsign) = 0; - - //! @} - //////////////////////////////////////////////////////////////// - //! \name Aircraft slots - //! @{ - //////////////////////////////////////////////////////////////// - - /*! - * Send a message querying the capabilities of the client software of the user with a specific callsign. - * \pre Network must be connected when calling this function. - * \sa capabilitiesReplyReceived - */ - virtual void sendCapabilitiesQuery(const BlackMisc::Aviation::CCallsign &callsign) = 0; - - /*! - * Send a message querying the ICAO codes of the aircraft of the user with a specific callsign. - * \pre Network must be connected when calling this function. - * \sa icaoCodesReplyReceived - */ - virtual void sendIcaoCodesQuery(const BlackMisc::Aviation::CCallsign &callsign) = 0; - - /*! - * Send a message querying the COM frequency of the user with a specific callsign. - * \pre Network must be connected when calling this function. - * \sa frequencyReplyReceived - */ - virtual void sendFrequencyQuery(const BlackMisc::Aviation::CCallsign &callsign) = 0; - - /*! - * Send a user info query, which only shall be sent by supervisiors. Reply is received as - * text message - * \pre Network must be connected when calling this function. - */ - virtual void sendUserInfoQuery(const BlackMisc::Aviation::CCallsign &callsign) = 0; - - /*! - * Set the group of callsigns receiving regular interim position updates. - */ - virtual void setInterimPositionReceivers(const BlackMisc::Aviation::CCallsignSet &receiver) = 0; - - /*! - * Get the group of callsigns receiving regular interim position updates. - */ - virtual const BlackMisc::Aviation::CCallsignSet &getInterimPositionReceivers() const = 0; - - /*! - * Add callsign receiving regular interim position updates. - */ - void addInterimPositionReceiver(const BlackMisc::Aviation::CCallsign &receiver) - { - BlackMisc::Aviation::CCallsignSet set = this->getInterimPositionReceivers(); - if (set.contains(receiver)) { return; } // already in set - set.push_back(receiver); - this->setInterimPositionReceivers(set); - } - - /*! - * Remove callsign receiving regular interim position updates. - */ - void removeInterimPositionReceiver(const BlackMisc::Aviation::CCallsign &receiver) - { - BlackMisc::Aviation::CCallsignSet set = this->getInterimPositionReceivers(); - if (!set.contains(receiver)) { return; } // nothing to remove - set.remove(receiver); - this->setInterimPositionReceivers(set); - } - - //! @} - //////////////////////////////////////////////////////////////// - //! \name Weather slots - //! @{ - //////////////////////////////////////////////////////////////// - - /*! - * Send a message querying the METAR for the airport with a specific ICAO code. - * \pre Network must be connected when calling this function. - * \sa metarReplyReceived - */ - virtual void sendMetarQuery(const BlackMisc::Aviation::CAirportIcaoCode &airportIcao) = 0; - //! @} - - - //! @} - //////////////////////////////////////////////////////////////// - //! \name Others - //! @{ - //////////////////////////////////////////////////////////////// - - //! Additional offset time @{ - virtual qint64 getAdditionalOffsetTime() const = 0; - virtual void setAdditionalOffsetTime(qint64 addOffset) = 0; - //! @} - - signals: - //! @} - //////////////////////////////////////////////////////////////// - //! \name ATC signals - //! @{ - //////////////////////////////////////////////////////////////// - - /*! - * We received a notification of the state of an ATC station on the network. - */ - void atcPositionUpdate(const BlackMisc::Aviation::CCallsign &callsign, const BlackMisc::PhysicalQuantities::CFrequency &freq, - const BlackMisc::Geo::CCoordinateGeodetic &pos, const BlackMisc::PhysicalQuantities::CLength &range); - - /*! - * We received a notification that an ATC station has disconnected from the network. - */ - void atcDisconnected(const BlackMisc::Aviation::CCallsign &callsign); - - /*! - * We received a reply to one of our queries. - * \sa sendAtcQuery - */ - void atcReplyReceived(const BlackMisc::Aviation::CCallsign &callsign, bool isATC); - - /*! - * We received a reply to one of our ATIS queries. - * \sa sendAtisQuery - */ - void atisReplyReceived(const BlackMisc::Aviation::CCallsign &callsign, const BlackMisc::Aviation::CInformationMessage &atis); - - /*! - * We received a reply to one of our ATIS queries, containing the controller's voice room URL. - * \sa sendAtisQuery - */ - void atisVoiceRoomReplyReceived(const BlackMisc::Aviation::CCallsign &callsign, const QString &url); - - /*! - * We received a reply to one of our ATIS queries, containing the controller's planned logoff time. - * \sa sendAtisQuery - */ - void atisLogoffTimeReplyReceived(const BlackMisc::Aviation::CCallsign &callsign, const QString &zuluTime); - - /*! - * We received a reply to one of our flight plan queries, containing our up-to-date flight plan. - * \sa sendFlightPlanQuery - */ - void flightPlanReplyReceived(const BlackMisc::Aviation::CCallsign &callsign, const BlackMisc::Aviation::CFlightPlan &flightPlan); - - //! @} - //////////////////////////////////////////////////////////////// - //! \name Aircraft signals - //! @{ - //////////////////////////////////////////////////////////////// - - /*! - * We received a notification that a pilot has disconnected from the network. - */ - void pilotDisconnected(const BlackMisc::Aviation::CCallsign &callsign); - - /*! - * We received a reply to one of our queries. - * \sa sendIcaoCodesQuery - */ - void icaoCodesReplyReceived(const BlackMisc::Aviation::CCallsign &callsign, const QString &aircraftIcaoDesignator, const QString &airlineIcaoDesignator, const QString &livery); - - /*! - * We received a notification of the state of another aircraft on the network. - * Corresponding callsign in \sa CAircraftSituation::getCallsign . - */ - void aircraftPositionUpdate(const BlackMisc::Aviation::CAircraftSituation &situation, - const BlackMisc::Aviation::CTransponder &transponder); - - /*! - * We received a interim notification of the state of another aircraft on the network. - * Corresponding callsign in \sa CAircraftSituation::getCallsign . - */ - void aircraftInterimPositionUpdate(const BlackMisc::Aviation::CAircraftSituation &situation); - - /*! - * We received a reply to one of our queries. - * \sa sendFrequencyQuery - */ - void frequencyReplyReceived(const BlackMisc::Aviation::CCallsign &callsign, const BlackMisc::PhysicalQuantities::CFrequency &freq); - - //! @} - //////////////////////////////////////////////////////////////// - //! \name Network signals - //! @{ - //////////////////////////////////////////////////////////////// - - /*! - * We were kicked from the network. (A connectionStatusChanged signal will also be sent.) - */ - void kicked(const QString &msg); - - /*! - * The status of our connection has changed. - */ - void connectionStatusChanged(BlackCore::INetwork::ConnectionStatus oldStatus, BlackCore::INetwork::ConnectionStatus newStatus); - - /*! - * We received a reply to one of our pings. - * \sa sendPing - */ - void pongReceived(const BlackMisc::Aviation::CCallsign &callsign, const BlackMisc::PhysicalQuantities::CTime &elapsedTime); - - /*! - * We received a reply to one of our queries. - * \sa sendCapabilitiesQuery - * @param callsign - * @param capabilitiesFlags as in BlackMisc::Network::CClient - */ - void capabilitiesReplyReceived(const BlackMisc::Aviation::CCallsign &callsign, int capabilitiesFlags); - - /*! - * We received a reply to one of our queries. - * \param ip Our IP address, as seen by the server. - * \sa sendIpQuery - */ - void ipReplyReceived(const QString &ip); - - /*! - * We received a reply to one of our queries. - * \sa sendServerQuery - */ - void serverReplyReceived(const BlackMisc::Aviation::CCallsign &callsign, const QString &server); - - /*! - * We received a reply to one of our queries. - * \sa sendRealNameQuery - */ - void realNameReplyReceived(const BlackMisc::Aviation::CCallsign &callsign, const QString &realname); - - /*! - * We received one or more text messages from another user. - */ - void textMessagesReceived(const BlackMisc::Network::CTextMessageList &messages); - - /*! - * We have sent a text message. - */ - void textMessageSent(const BlackMisc::Network::CTextMessage &sentMessage); - - /*! - * We received a FSInn custom packet. - */ - void customFSInnPacketReceived(const BlackMisc::Aviation::CCallsign &callsign, const QString &airlineDesignator, - const QString &aircraftDesignator, const QString &combinedType, const QString &modelString); - - /*! - * We received an aircraft config packet. - */ - void aircraftConfigPacketReceived(const BlackMisc::Aviation::CCallsign &callsign, const QJsonObject &incremental, qint64 currentOffsetTimeMs); - - /*! - * We received a raw message for debugging purposes - */ - void rawFsdMessageReceived(const BlackMisc::Network::CRawFsdMessage &rawFsdMessage); - - //! @} - //////////////////////////////////////////////////////////////// - //! \name Weather signals - //! @{ - //////////////////////////////////////////////////////////////// - - /*! - * We received a reply to one of our METAR queries. - * \sa sendMetarQuery - */ - void metarReplyReceived(const QString &data); - - //! @} - - protected: - //! Save the statistics - bool saveNetworkStatistics(const QString &server); - - //! Connected to network - void connectedToNewtork(); - - private: - bool m_statistics = false; - QMap m_callStatistics; - QVector > m_callByTime; - }; -} // namespace - -Q_DECLARE_METATYPE(BlackCore::INetwork::ConnectionStatus) -Q_DECLARE_METATYPE(BlackCore::INetwork::LoginMode) - -#endif // guard diff --git a/src/blackcore/registermetadata.cpp b/src/blackcore/registermetadata.cpp index 271a11861..dfe67c08e 100644 --- a/src/blackcore/registermetadata.cpp +++ b/src/blackcore/registermetadata.cpp @@ -13,7 +13,7 @@ #include "blackcore/data/vatsimsetup.h" #include "blackcore/db/databasereader.h" #include "blackcore/vatsim/vatsimsettings.h" -#include "blackcore/network.h" +#include "blackcore/fsd/fsdclient.h" #include "blackcore/voicechannel.h" #include "blackcore/webreaderflags.h" #include "blackcore/aircraftmatcher.h" @@ -24,6 +24,8 @@ #include #include +using namespace BlackCore::Fsd; + namespace BlackCore { void registerMetadata() @@ -37,7 +39,6 @@ namespace BlackCore qDBusRegisterMetaType(); qDBusRegisterMetaType(); qDBusRegisterMetaType(); - qDBusRegisterMetaType(); qDBusRegisterMetaType(); qDBusRegisterMetaType(); @@ -45,7 +46,6 @@ namespace BlackCore qRegisterMetaTypeStreamOperators(); qRegisterMetaTypeStreamOperators(); qRegisterMetaTypeStreamOperators(); - qRegisterMetaTypeStreamOperators(); qRegisterMetaTypeStreamOperators(); Db::CDatabaseReaderConfig::registerMetadata(); @@ -55,5 +55,16 @@ namespace BlackCore Data::CLauncherSetup::registerMetadata(); Vatsim::CReaderSettings::registerMetadata(); Vatsim::CRawFsdMessageSettings::registerMetadata(); + + // not really clear when a type here has to be registered with qRegisterMetaType + // however, does not harm if it is redundant + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); } } // namespace diff --git a/src/blackcore/vatsim/networkvatlib.cpp b/src/blackcore/vatsim/networkvatlib.cpp deleted file mode 100644 index b5dc484a8..000000000 --- a/src/blackcore/vatsim/networkvatlib.cpp +++ /dev/null @@ -1,1820 +0,0 @@ -/* Copyright (C) 2013 - * swift Project Community / Contributors - * - * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level - * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, - * or distributed except according to the terms contained in the LICENSE file. - */ - -//! \cond PRIVATE - -#include "blackconfig/buildconfig.h" -#include "blackcore/application.h" -#include "blackcore/vatsim/networkvatlib.h" -#include "blackmisc/simulation/aircraftmodel.h" -#include "blackmisc/simulation/simulatedaircraft.h" -#include "blackmisc/aviation/aircraftsituation.h" -#include "blackmisc/aviation/altitude.h" -#include "blackmisc/aviation/comsystem.h" -#include "blackmisc/aviation/flightplan.h" -#include "blackmisc/aviation/heading.h" -#include "blackmisc/aviation/informationmessage.h" -#include "blackmisc/aviation/livery.h" -#include "blackmisc/aviation/transponder.h" -#include "blackmisc/geo/coordinategeodetic.h" -#include "blackmisc/geo/latitude.h" -#include "blackmisc/geo/longitude.h" -#include "blackmisc/network/fsdsetup.h" -#include "blackmisc/network/textmessage.h" -#include "blackmisc/network/user.h" -#include "blackmisc/pq/angle.h" -#include "blackmisc/pq/frequency.h" -#include "blackmisc/pq/length.h" -#include "blackmisc/pq/speed.h" -#include "blackmisc/pq/time.h" -#include "blackmisc/pq/units.h" -#include "blackmisc/stringutils.h" -#include "blackmisc/compare.h" -#include "blackmisc/json.h" -#include "blackmisc/logmessage.h" -#include "blackmisc/logcategory.h" -#include "blackmisc/statusmessage.h" -#include "blackmisc/verify.h" -#include "vatlib/vatlib.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static_assert(! std::is_abstract::value, "Must implement all pure virtuals"); -static_assert(VAT_LIBVATLIB_VERSION == 908, "Wrong vatlib header installed"); - -using namespace BlackConfig; -using namespace BlackMisc; -using namespace BlackMisc::Aviation; -using namespace BlackMisc::Geo; -using namespace BlackMisc::Json; -using namespace BlackMisc::Network; -using namespace BlackMisc::PhysicalQuantities; -using namespace BlackMisc::Simulation; - -namespace BlackCore -{ - namespace Vatsim - { - const CLogCategoryList &CNetworkVatlib::getLogCategories() - { - static const CLogCategoryList cats({ CLogCategory::vatsimSpecific(), CLogCategory::network() }); - return cats; - } - - CNetworkVatlib::CNetworkVatlib(IClientProvider *clientProvider, - IOwnAircraftProvider *ownAircraftProvider, - IRemoteAircraftProvider *remoteAircraftProvider, - QObject *parent) - : INetwork(clientProvider, ownAircraftProvider, remoteAircraftProvider, parent), - m_loginMode(LoginNormal), - m_status(vatStatusDisconnected), - m_tokenBucket(10, CTime(5, CTimeUnit::s()), 1) - { - connect(this, &CNetworkVatlib::terminate, this, &INetwork::terminateConnection, Qt::QueuedConnection); - - Q_ASSERT_X(Vat_GetVersion() == VAT_LIBVATLIB_VERSION, "swift.network", "Wrong vatlib shared library installed"); - - Vat_SetNetworkLogHandler(vatSeverityError, CNetworkVatlib::networkLogHandler); - - m_processingTimer.setObjectName(this->objectName().append(":m_processingTimer")); - m_positionUpdateTimer.setObjectName(this->objectName().append(":m_positionUpdateTimer")); - m_interimPositionUpdateTimer.setObjectName(this->objectName().append(":m_interimPositionUpdateTimer")); - - connect(&m_processingTimer, &QTimer::timeout, this, &CNetworkVatlib::process); - connect(&m_positionUpdateTimer, &QTimer::timeout, this, &CNetworkVatlib::sendPositionUpdate); - connect(&m_interimPositionUpdateTimer, &QTimer::timeout, this, &CNetworkVatlib::sendInterimPositions); - connect(&m_scheduledConfigUpdate, &QTimer::timeout, this, &CNetworkVatlib::sendIncrementalAircraftConfig); - m_scheduledConfigUpdate.setSingleShot(true); - - m_processingTimer.start(c_processingIntervalMsec); - } - - void CNetworkVatlib::initializeSession() - { - Q_ASSERT_X(isDisconnected(), Q_FUNC_INFO, "attempted to reinitialize session while still connected"); - Q_ASSERT_X(sApp, Q_FUNC_INFO, "Need sApp"); - - int clientCapabilities = vatCapsAircraftInfo | vatCapsFastPos | vatCapsAtcInfo | vatCapsAircraftConfig; - if (m_loginMode == LoginStealth) - { - clientCapabilities |= vatCapsStealth; - } - - int clientId = 0; - QString clientKey; - if (!getCmdLineClientIdAndKey(clientId, clientKey)) - { - clientId = CBuildConfig::vatsimClientId(); - clientKey = CBuildConfig::vatsimPrivateKey(); - } - - VatServerType serverType; - switch (m_server.getServerType()) - { - case CServer::FSDServerVatsim: serverType = vatServerVatsim; break; - default: serverType = vatServerLegacyFsd; break; - } - - const QString hostApplication = this->getNetworkHostApplicationString().replace(':', ' '); - m_net.reset(Vat_CreateNetworkSession(serverType, sApp->swiftVersionChar(), - CBuildConfig::getVersion().majorVersion(), - CBuildConfig::getVersion().minorVersion(), - toFSD(hostApplication), clientId, toFSD(clientKey), - clientCapabilities)); - - Vat_SetStateChangeHandler(m_net.data(), onConnectionStatusChanged, this); - Vat_SetTextMessageHandler(m_net.data(), onTextMessageReceived, this); - Vat_SetRadioMessageHandler(m_net.data(), onRadioMessageReceived, this); - Vat_SetDeletePilotHandler(m_net.data(), onPilotDisconnected, this); - Vat_SetDeleteAtcHandler(m_net.data(), onControllerDisconnected, this); - Vat_SetPilotPositionHandler(m_net.data(), onPilotPositionUpdate, this); - Vat_SetInterimPilotPositionHandler(m_net.data(), onInterimPilotPositionUpdate, this); - Vat_SetAtcPositionHandler(m_net.data(), onAtcPositionUpdate, this); - Vat_SetKillHandler(m_net.data(), onKicked, this); - Vat_SetPongHandler(m_net.data(), onPong, this); - Vat_SetMetarResponseHandler(m_net.data(), onMetarReceived, this); - Vat_SetClientQueryHandler(m_net.data(), onInfoQueryRequestReceived, this); - Vat_SetClientQueryResponseHandler(m_net.data(), onInfoQueryReplyReceived, this); - Vat_SetClientCapabilitiesReplyHandler(m_net.data(), onCapabilitiesReplyReceived, this); - Vat_SetControllerAtisHandler(m_net.data(), onAtisReplyReceived, this); - Vat_SetFlightPlanHandler(m_net.data(), onFlightPlanReceived, this); - Vat_SetServerErrorHandler(m_net.data(), onErrorReceived, this); - Vat_SetAircraftInfoRequestHandler(m_net.data(), onPilotInfoRequestReceived, this); - Vat_SetAircraftInfoHandler(m_net.data(), onPilotInfoReceived, this); - Vat_SetCustomPilotPacketHandler(m_net.data(), onCustomPacketReceived, this); - Vat_SetAircraftConfigHandler(m_net.data(), onAircraftConfigReceived, this); - - fsdMessageSettingsChanged(); - } - - CNetworkVatlib::~CNetworkVatlib() - { - BLACK_VERIFY_X(isDisconnected(), Q_FUNC_INFO, "CNetworkVatlib destroyed while still connected."); - terminateConnection(); - } - - const QString &CNetworkVatlib::getLibraryInfo(bool detailed) const - { - static const QString vs(QStringLiteral("VATLIB: ") + Vat_GetVersionText()); - static const QString vd(QStringLiteral("VATLIB: ") + Vat_GetVersionText() + QStringLiteral("\n") + Vat_GetBuildInfo()); - return detailed ? vd : vs; - } - - QStringList CNetworkVatlib::getPresetValues() const - { - const QStringList v = - { - m_ownModelString, - m_ownLivery, - m_ownAircraftIcaoCode.getDesignator(), - m_ownAirlineIcaoCode.getVDesignator(), - m_ownCallsign.asString(), - m_partnerCallsign.asString() - }; - return v; - } - - void CNetworkVatlib::process() - { - if (!m_net) { return; } - sendIncrementalAircraftConfig(); - Vat_ExecuteNetworkTasks(m_net.data()); - } - - void CNetworkVatlib::sendPositionUpdate() - { - if (!m_net) { return; } - - if (isConnected()) - { - CSimulatedAircraft myAircraft(getOwnAircraft()); - if (m_loginMode == LoginAsObserver) - { - // Observer - VatAtcPosition pos; - pos.facility = vatFacilityTypeUnknown; - pos.visibleRange = 10; // NM - pos.latitude = myAircraft.latitude().value(CAngleUnit::deg()); - pos.longitude = myAircraft.longitude().value(CAngleUnit::deg()); - pos.elevation = 0; - pos.rating = vatAtcRatingObserver; - pos.frequency = 199998; - Vat_SendATCUpdate(m_net.data(), &pos); - - // statistics - this->increaseStatisticsValue(QStringLiteral("Vat_SendATCUpdate")); - } - else - { - // Normal / Stealth mode - VatPilotPosition pos; - pos.altitudePressure = myAircraft.getPressureAltitude().valueInteger(CLengthUnit::ft()); - pos.altitudeTrue = myAircraft.getAltitude().valueInteger(CLengthUnit::ft()); - pos.heading = myAircraft.getHeading().value(CAngleUnit::deg()); - pos.pitch = myAircraft.getPitch().value(CAngleUnit::deg()); - pos.bank = myAircraft.getBank().value(CAngleUnit::deg()); - pos.onGround = myAircraft.getParts().isOnGround(); - pos.latitude = myAircraft.latitude().value(CAngleUnit::deg()); - pos.longitude = myAircraft.longitude().value(CAngleUnit::deg()); - pos.groundSpeed = myAircraft.getGroundSpeed().valueInteger(CSpeedUnit::kts()); - pos.rating = vatPilotRatingUnknown; - pos.transponderCode = static_cast(myAircraft.getTransponderCode()); - pos.transponderMode = vatTransponderModeStandby; - switch (myAircraft.getTransponderMode()) - { - case CTransponder::ModeC: pos.transponderMode = vatTransponderModeCharlie; break; - case CTransponder::StateIdent: pos.transponderMode = vatTransponderModeIdent; break; - default: pos.transponderMode = vatTransponderModeStandby; break; - } - Vat_SendPilotUpdate(m_net.data(), &pos); - - // statistics - this->increaseStatisticsValue(QStringLiteral("Vat_SendPilotUpdate")); - } - } - } - - void CNetworkVatlib::sendInterimPositions() - { - if (!m_net) { return; } - if (isConnected()) - { - CSimulatedAircraft myAircraft(getOwnAircraft()); - if (m_loginMode == LoginNormal) - { - VatInterimPilotPosition pos; - pos.altitudeTrue = myAircraft.getAltitude().valueInteger(CLengthUnit::ft()); - pos.heading = myAircraft.getHeading().value(CAngleUnit::deg()); - pos.pitch = myAircraft.getPitch().value(CAngleUnit::deg()); - pos.bank = myAircraft.getBank().value(CAngleUnit::deg()); - pos.onGround = myAircraft.getParts().isOnGround(); - pos.latitude = myAircraft.latitude().value(CAngleUnit::deg()); - pos.longitude = myAircraft.longitude().value(CAngleUnit::deg()); - - for (const auto &receiver : as_const(m_interimPositionReceivers)) - { - Vat_SendInterimPilotUpdate(m_net.data(), toFSD(receiver), &pos); - - // statistics - this->increaseStatisticsValue(QStringLiteral("Vat_SendInterimPilotUpdate")); - } - } - } - } - - //! Convert vatlib status code to INetwork::ConnectionStatus - INetwork::ConnectionStatus convertConnectionStatus(VatConnectionStatus status) - { - switch (status) - { - case vatStatusConnecting: return INetwork::Connecting; - case vatStatusConnected: return INetwork::Connected; - case vatStatusDisconnected: return INetwork::Disconnected; - case vatStatusDisconnecting: return INetwork::Disconnecting; - } - qFatal("unrecognised connection status"); - return INetwork::DisconnectedError; - } - - void CNetworkVatlib::changeConnectionStatus(VatConnectionStatus status) - { - if (m_status != status) - { - qSwap(m_status, status); - if (m_status == vatStatusConnected) - { - m_server.setConnectedSinceNow(); - this->setCurrentEcosystem(m_server.getEcosystem()); - this->connectedToNewtork(); - } - else - { - m_server.markAsDisconnected(); - } - - if (this->isDisconnected()) - { - this->stopPositionTimers(); - this->clearState(); - this->setLastEcosystem(m_server.getEcosystem()); - this->setCurrentEcosystem(CEcosystem::NoSystem); - this->saveNetworkStatistics(m_server.getName()); - } - - emit this->connectionStatusChanged(convertConnectionStatus(status), convertConnectionStatus(m_status)); - } - } - - QByteArray CNetworkVatlib::toFSDnoColon(const QString &qstr) const - { - if (!qstr.contains(':')) { return toFSD(qstr); } - if (CBuildConfig::isLocalDeveloperDebugBuild()) - { - // so we can investigate - BLACK_VERIFY_X(false, Q_FUNC_INFO, "Illegal char :"); - } - QString copy(qstr); - return toFSD(copy.remove(':')); - } - - QByteArray CNetworkVatlib::toFSD(const QString &qstr) const - { - Q_ASSERT_X(m_fsdTextCodec, Q_FUNC_INFO, "Missing codec"); - return m_fsdTextCodec->fromUnicode(qstr); - } - - QByteArray CNetworkVatlib::toFSD(const CCallsign &callsign) const - { - return toFSD(callsign.getStringAsSet()); - } - - std::function CNetworkVatlib::toFSD(const QStringList &qstrList) const - { - QVector bytesVec; - bytesVec.reserve(qstrList.size()); - for (auto i = qstrList.cbegin(); i != qstrList.cend(); ++i) - { - bytesVec.push_back(toFSD(*i)); - } - - return [ cstrVec = QVector(), bytesVec = std::move(bytesVec) ]() mutable - { - Q_ASSERT_X(cstrVec.isEmpty(), Q_FUNC_INFO, "toFSD lambda called twice"); - for (auto i = bytesVec.cbegin(); i != bytesVec.cend(); ++i) - { - cstrVec.push_back(i->constData()); - } - return const_cast(cstrVec.constData()); - }; - } - - QString CNetworkVatlib::fromFSD(const char *cstr) const - { - Q_ASSERT_X(m_fsdTextCodec, Q_FUNC_INFO, "Missing codec"); - return m_fsdTextCodec->toUnicode(cstr); - } - - QString CNetworkVatlib::getNetworkHostApplicationString() const - { - return this->getSimulatorNameAndVersion(); - } - - QStringList CNetworkVatlib::fromFSD(const char **cstrArray, int size) const - { - QStringList qstrList; - qstrList.reserve(size); - for (int i = 0; i < size; ++i) - { - qstrList.push_back(fromFSD(cstrArray[i])); - } - return qstrList; - } - - bool CNetworkVatlib::isInterimPositionSendingEnabledForServer() const - { - const CFsdSetup::SendReceiveDetails d = this->getSetupForServer().getSendReceiveDetails(); - return (d & CFsdSetup::SendInterimPositions); - } - - bool CNetworkVatlib::isInterimPositionReceivingEnabledForServer() const - { - const CFsdSetup::SendReceiveDetails d = this->getSetupForServer().getSendReceiveDetails(); - return (d & CFsdSetup::ReceiveInterimPositions); - } - - const CFsdSetup &CNetworkVatlib::getSetupForServer() const - { - return m_server.getFsdSetup(); - } - - void CNetworkVatlib::startPositionTimers() - { - m_positionUpdateTimer.start(c_updatePostionIntervalMsec); - if (this->isInterimPositionSendingEnabledForServer()) { m_interimPositionUpdateTimer.start(c_updateInterimPostionIntervalMsec); } - } - - void CNetworkVatlib::stopPositionTimers() - { - m_positionUpdateTimer.stop(); - m_interimPositionUpdateTimer.stop(); - } - - QString CNetworkVatlib::convertToUnicodeEscaped(const QString &str) - { - QString escaped; - for (const auto &ch : str) - { - const ushort code = ch.unicode(); - if (code < 0x80) - { - escaped += ch; - } - else - { - escaped += "\\u"; - escaped += QString::number(code, 16).rightJustified(4, '0'); - } - } - return escaped; - } - - VatSimType CNetworkVatlib::convertToSimType(CSimulatorPluginInfo &simInfo) - { - //! \fixme Define recognized simulators somewhere */ - if (simInfo.getSimulator() == "fs9") - { - return vatSimTypeMSFS2004; - } - else if (simInfo.getSimulator() == "fsx") - { - return vatSimTypeMSFSX; - } - else if (simInfo.getSimulator() == "p3d") - { - return vatSimTypeP3Dv4; // we always set the latest, as we have only one flag - } - else if (simInfo.getSimulator() == "xplane") - { - return vatSimTypeXPLANE11; // latest, as there is only one flag - } - else - { - return vatSimTypeUnknown; - } - } - - /********************************** * * * * * * * * * * * * * * * * * * * ************************************/ - /********************************** INetwork functions ************************************/ - /********************************** * * * * * * * * * * * * * * * * * * * ************************************/ - - void CNetworkVatlib::presetServer(const CServer &server) - { - Q_ASSERT_X(isDisconnected(), Q_FUNC_INFO, "Can't change server details while still connected"); - - // If the server type changed, we need to destroy the existing vatlib session - if (m_server.getServerType() != server.getServerType()) { m_net.reset(); } - - m_server = server; - const QString codecName(server.getFsdSetup().getTextCodec()); - Q_ASSERT_X(!codecName.isEmpty(), Q_FUNC_INFO, "Missing code name"); - m_fsdTextCodec = QTextCodec::codecForName(codecName.toLocal8Bit()); - if (!m_fsdTextCodec) { m_fsdTextCodec = QTextCodec::codecForName("latin1"); } - } - - void CNetworkVatlib::presetSimulatorInfo(const CSimulatorPluginInfo &simInfo) - { - Q_ASSERT_X(isDisconnected(), Q_FUNC_INFO, "Can't change server details while still connected"); - m_simulatorInfo = simInfo; - } - - void CNetworkVatlib::presetCallsign(const CCallsign &callsign) - { - Q_ASSERT_X(isDisconnected(), Q_FUNC_INFO, "Can't change callsign while still connected"); - m_ownCallsign = callsign; - this->updateOwnCallsign(callsign); - } - - void CNetworkVatlib::presetPartnerCallsign(const CCallsign &callsign) - { - m_partnerCallsign = callsign; - } - - void CNetworkVatlib::presetIcaoCodes(const CSimulatedAircraft &ownAircraft) - { - Q_ASSERT_X(isDisconnected(), Q_FUNC_INFO, "Can't change ICAO codes while still connected"); - m_ownAircraftIcaoCode = ownAircraft.getAircraftIcaoCode(); - m_ownAirlineIcaoCode = ownAircraft.getAirlineIcaoCode(); - m_ownLivery = removeColon(ownAircraft.getModel().getSwiftLiveryString()); - m_ownModelString = removeColon(ownAircraft.getModelString()); - m_sendLiveryString = true; - m_sendMModelString = true; - - updateOwnIcaoCodes(m_ownAircraftIcaoCode, m_ownAirlineIcaoCode); - } - - void CNetworkVatlib::presetLiveryAndModelString(const QString &livery, bool sendLiveryString, const QString &modelString, bool sendModelString) - { - m_ownLivery = removeColon(livery); - m_ownModelString = removeColon(modelString); - m_sendLiveryString = sendLiveryString; - m_sendMModelString = sendModelString; - } - - void CNetworkVatlib::presetLoginMode(LoginMode mode) - { - Q_ASSERT_X(isDisconnected(), Q_FUNC_INFO, "Can't change login mode while still connected"); - m_loginMode = mode; - m_net.reset(nullptr); - } - - void CNetworkVatlib::initiateConnection() - { - Q_ASSERT_X(isDisconnected(), Q_FUNC_INFO, "Can't connect while still connected"); - if (!m_net) { initializeSession(); } - this->clearState(); - m_filterPasswordFromLogin = true; - QByteArray callsign = toFSDnoColon(m_ownCallsign.asString()); - QByteArray name; - if (m_loginMode == LoginAsObserver) - { - // Observer mode - VatAtcConnection info; - name = toFSDnoColon(m_server.getUser().getRealName()); - info.name = name.data(); - info.rating = vatAtcRatingObserver; - info.callsign = callsign.data(); - Vat_SpecifyATCLogon(m_net.data(), toFSD(m_server.getAddress()), m_server.getPort(), - toFSDnoColon(m_server.getUser().getId()), - toFSDnoColon(m_server.getUser().getPassword()), - &info); - - // statistics - this->increaseStatisticsValue(QStringLiteral("Vat_SpecifyATCLogon")); - } - else - { - // normal scenario, also used in STEALTH - VatPilotConnection info; - info.callsign = callsign.data(); - name = toFSDnoColon(m_server.getUser().getRealNameAndHomeBase()); - info.name = name.data(); - info.rating = vatPilotRatingStudent; // as documented, expected to be vatPilotRatingStudent only - info.simType = convertToSimType(m_simulatorInfo); - Vat_SpecifyPilotLogon(m_net.data(), toFSD(m_server.getAddress()), m_server.getPort(), - toFSDnoColon(m_server.getUser().getId()), - toFSDnoColon(m_server.getUser().getPassword()), - &info); - - // statistics - this->increaseStatisticsValue(QStringLiteral("Vat_SpecifyPilotLogon")); - } - - Vat_Logon(m_net.data()); - this->startPositionTimers(); - - // statistics - this->increaseStatisticsValue(QStringLiteral("Vat_Logon")); - } - - void CNetworkVatlib::terminateConnection() - { - this->stopPositionTimers(); - if (m_net && !isDisconnected()) - { - // Process all pending tasks before logging off - process(); - Vat_Logoff(m_net.data()); - - // statistics - this->increaseStatisticsValue(QStringLiteral("Vat_Logoff")); - } - this->clearState(); - } - - void CNetworkVatlib::sendTextMessages(const CTextMessageList &messages) - { - BLACK_VERIFY_X(this->isConnected(), Q_FUNC_INFO, "Sending text message, but not connected"); - if (!this->isConnected()) { return; } - if (messages.isEmpty()) { return; } - CTextMessageList privateMessages = messages.getPrivateMessages(); - privateMessages.markAsSent(); - for (const CTextMessage &message : as_const(privateMessages)) - { - if (message.getRecipientCallsign().isEmpty()) { continue; } - Vat_SendTextMessage(m_net.data(), toFSD(message.getRecipientCallsign()), toFSD(message.getMessage())); - - if (!message.isRelayedMessage()) - { - // this is the normal scenario, but relayed messages will NOT emit - emit this->textMessageSent(message); - } - - // statistics - this->increaseStatisticsValue(QStringLiteral("Vat_SendTextMessage")); - } - - CTextMessageList radioMessages = messages.getRadioMessages(); - radioMessages.markAsSent(); - QVector freqsVec; - for (const CTextMessage &message : radioMessages) - { - // I could send the same message to n frequencies in one step - // if this is really required, I need to group by message - // currently I send individual messages - freqsVec.clear(); - int freqkHz = message.getFrequency().valueInteger(CFrequencyUnit::kHz()); - if (m_server.getServerType() == CServer::FSDServerVatsim) - { - // VATSIM always drops the last 5 kHz. - freqkHz = freqkHz / 10 * 10; - } - freqsVec.push_back(freqkHz); - Vat_SendRadioMessage(m_net.data(), freqsVec.data(), static_cast(freqsVec.size()), toFSDnoColon(message.getMessage())); - emit this->textMessageSent(message); - - // statistics - this->increaseStatisticsValue(QStringLiteral("Vat_SendRadioMessage")); - } - } - - void CNetworkVatlib::sendWallopMessage(const QString &message) - { - if (message.isEmpty()) {return; } - BLACK_VERIFY_X(this->isConnected(), Q_FUNC_INFO, "Sending wallop, but not connected"); - if (!this->isConnected()) { return; } - Vat_SendWallop(m_net.data(), toFSDnoColon(simplifyTextMessage(message))); - - // statistics - this->increaseStatisticsValue(QStringLiteral("Vat_SendWallop")); - } - - void CNetworkVatlib::sendCustomPacket(const CCallsign &callsign, const QString &packetId, const QStringList &data) - { - Q_ASSERT_X(isConnected(), Q_FUNC_INFO, "Can't send to server when disconnected"); - Vat_SendCustomPilotPacket(m_net.data(), toFSD(callsign), toFSD(packetId), toFSD(data)(), data.size()); - - // statistics - this->increaseStatisticsValue(QStringLiteral("Vat_SendCustomPilotPacket")); - } - - void CNetworkVatlib::sendIpQuery() - { - Q_ASSERT_X(isConnected(), Q_FUNC_INFO, "Can't send to server when disconnected"); - Vat_SendClientQuery(m_net.data(), vatClientQueryIP, nullptr); - - // statistics - this->increaseStatisticsValue(QStringLiteral("Vat_SendClientQuery"), enumToString(vatClientQueryIP)); - } - - void CNetworkVatlib::sendFrequencyQuery(const CCallsign &callsign) - { - Q_ASSERT_X(isConnected(), Q_FUNC_INFO, "Can't send to server when disconnected"); - Vat_SendClientQuery(m_net.data(), vatClientQueryFreq, toFSD(callsign)); - - // statistics - this->increaseStatisticsValue(QStringLiteral("Vat_SendClientQuery"), enumToString(vatClientQueryFreq)); - } - - void CNetworkVatlib::sendUserInfoQuery(const CCallsign &callsign) - { - Q_ASSERT_X(isConnected(), Q_FUNC_INFO, "Can't send to server when disconnected"); - Vat_SendClientQuery(m_net.data(), vatClientQueryInfo, toFSD(callsign)); - - // statistics - this->increaseStatisticsValue(QStringLiteral("Vat_SendClientQuery"), enumToString(vatClientQueryInfo)); - } - - void CNetworkVatlib::setInterimPositionReceivers(const CCallsignSet &receivers) - { - m_interimPositionReceivers = receivers; - } - - const CCallsignSet &CNetworkVatlib::getInterimPositionReceivers() const - { - return m_interimPositionReceivers; - } - - QStringList CNetworkVatlib::vatlibArguments() - { - QStringList args; - int id = 0; - QString key; - if (!CNetworkVatlib::getCmdLineClientIdAndKey(id, key)) { return args; } - - // from cmd. line - args << "--idAndKey"; - args << sApp->getParserValue("clientIdAndKey"); // as typed in - return args; - } - - void CNetworkVatlib::sendServerQuery(const CCallsign &callsign) - { - Q_ASSERT_X(isConnected(), Q_FUNC_INFO, "Can't send to server when disconnected"); - Vat_SendClientQuery(m_net.data(), vatClientQueryServer, toFSD(callsign)); - - // statistics - this->increaseStatisticsValue(QStringLiteral("Vat_SendClientQuery"), enumToString(vatClientQueryServer)); - } - - void CNetworkVatlib::sendAtcQuery(const CCallsign &callsign) - { - Q_ASSERT_X(isConnected(), Q_FUNC_INFO, "Can't send to server when disconnected"); - Vat_SendClientQuery(m_net.data(), vatClientQueryAtc, toFSD(callsign)); - - // statistics - this->increaseStatisticsValue(QStringLiteral("Vat_SendClientQuery"), enumToString(vatClientQueryAtc)); - } - - void CNetworkVatlib::sendAtisQuery(const CCallsign &callsign) - { - Q_ASSERT_X(isConnected(), Q_FUNC_INFO, "Can't send to server when disconnected"); - if (m_server.getServerType() != CServer::FSDServerVatsim) - { - m_pendingAtisQueries.insert(callsign, {}); - } - Vat_SendClientQuery(m_net.data(), vatClientQueryAtis, toFSD(callsign)); - - // statistics - this->increaseStatisticsValue(QStringLiteral("Vat_SendClientQuery"), enumToString(vatClientQueryAtis)); - } - - void CNetworkVatlib::sendFlightPlan(const CFlightPlan &flightPlan) - { - Q_ASSERT_X(isConnected(), Q_FUNC_INFO, "Can't send to server when disconnected"); - - VatFlightPlan vatlibFP; - - // Removed with T353 although it is standard - // const QString route = QString(flightPlan.getRoute()).replace(" ", "."); - const QString route = flightPlan.getRoute(); - const QString remarks = QString(flightPlan.getRemarks()); - - //! \fixme that would be the official string, can this be used? - const QString alt = flightPlan.getCruiseAltitude().asFpVatsimAltitudeString(); - // const QString alt = flightPlan.getCruiseAltitude().asFpAltitudeString(); - - QString act = flightPlan.getCombinedPrefixIcaoSuffix(); - if (act.isEmpty()) { act = flightPlan.getAircraftIcao().getDesignator(); } // fallback - - QByteArray acTypeTemp, altAptTemp, cruiseAltTemp, depAptTemp, destAptTemp, routeTemp, remarksTemp; - vatlibFP.aircraftType = acTypeTemp = toFSDnoColon(act); - vatlibFP.alternateAirport = altAptTemp = toFSDnoColon(flightPlan.getAlternateAirportIcao().asString()); - vatlibFP.cruiseAltitude = cruiseAltTemp = toFSDnoColon(alt); - vatlibFP.departAirport = depAptTemp = toFSDnoColon(flightPlan.getOriginAirportIcao().asString()); - vatlibFP.destAirport = destAptTemp = toFSDnoColon(flightPlan.getDestinationAirportIcao().asString()); - vatlibFP.departTimeActual = flightPlan.getTakeoffTimeActual().toUTC().toString("hhmm").toInt(); - vatlibFP.departTime = flightPlan.getTakeoffTimePlanned().toUTC().toString("hhmm").toInt(); - - QList timeParts = flightPlan.getEnrouteTime().getHrsMinSecParts(); - vatlibFP.enrouteHrs = timeParts[CTime::Hours]; - vatlibFP.enrouteMins = timeParts[CTime::Minutes]; - - timeParts = flightPlan.getFuelTime().getHrsMinSecParts(); - vatlibFP.fuelHrs = timeParts[CTime::Hours]; - vatlibFP.fuelMins = timeParts[CTime::Minutes]; - vatlibFP.remarks = remarksTemp = toFSDnoColon(remarks); - vatlibFP.route = routeTemp = toFSDnoColon(route); - vatlibFP.trueCruisingSpeed = flightPlan.getCruiseTrueAirspeed().valueInteger(CSpeedUnit::kts()); - switch (flightPlan.getFlightRules()) - { - default: - case CFlightPlan::IFR: vatlibFP.flightType = vatFlightTypeIFR; break; - case CFlightPlan::VFR: vatlibFP.flightType = vatFlightTypeVFR; break; - case CFlightPlan::SVFR: vatlibFP.flightType = vatFlightTypeSVFR; break; - case CFlightPlan::DVFR: vatlibFP.flightType = vatFlightTypeDVFR; break; - } - Vat_SendFlightPlan(m_net.data(), &vatlibFP); - - // statistics - this->increaseStatisticsValue(QStringLiteral("Vat_SendFlightPlan")); - } - - void CNetworkVatlib::sendFlightPlanQuery(const CCallsign &callsign) - { - Q_ASSERT_X(isConnected(), Q_FUNC_INFO, "Can't send to server when disconnected"); - Vat_SendClientQuery(m_net.data(), vatClientQueryFP, toFSD(callsign)); - - // statistics - this->increaseStatisticsValue(QStringLiteral("Vat_SendClientQuery"), enumToString(vatClientQueryFP)); - } - - void CNetworkVatlib::sendRealNameQuery(const CCallsign &callsign) - { - Q_ASSERT_X(isConnected(), Q_FUNC_INFO, "Can't send to server when disconnected"); - Vat_SendClientQuery(m_net.data(), vatClientQueryName, toFSD(callsign)); - - // statistics - this->increaseStatisticsValue(QStringLiteral("Vat_SendClientQuery"), enumToString(vatClientQueryName)); - } - - void CNetworkVatlib::sendCapabilitiesQuery(const CCallsign &callsign) - { - Q_ASSERT_X(isConnected(), Q_FUNC_INFO, "Can't send to server when disconnected"); - Vat_SendClientQuery(m_net.data(), vatClientQueryCaps, toFSD(callsign)); - - // statistics - this->increaseStatisticsValue(QStringLiteral("Vat_SendClientQuery"), enumToString(vatClientQueryCaps)); - } - - void CNetworkVatlib::replyToFrequencyQuery(const CCallsign &callsign) // private - { - QStringList response { QString::number(getOwnAircraft().getCom1System().getFrequencyActive().value(CFrequencyUnit::MHz()), 'f', 3)}; - Vat_SendClientQueryResponse(m_net.data(), vatClientQueryFreq, toFSD(callsign), toFSD(response)(), response.size()); - - // statistics - this->increaseStatisticsValue(QStringLiteral("Vat_SendClientQueryResponse"), enumToString(vatClientQueryFreq)); - } - - void CNetworkVatlib::replyToNameQuery(const CCallsign &callsign) // private - { - QStringList response { removeColon(m_server.getUser().getRealNameAndHomeBase()), "" }; - Vat_SendClientQueryResponse(m_net.data(), vatClientQueryName, toFSD(callsign), toFSD(response)(), response.size()); - - // statistics - this->increaseStatisticsValue(QStringLiteral("Vat_SendClientQueryResponse"), enumToString(vatClientQueryName)); - } - - void CNetworkVatlib::replyToConfigQuery(const CCallsign &callsign) - { - QJsonObject config = this->getOwnAircraftParts().toJson(); - config.insert(CAircraftParts::attributeNameIsFullJson(), true); - QString data = QJsonDocument(QJsonObject { { "config", config } }).toJson(QJsonDocument::Compact); - data = convertToUnicodeEscaped(data); - Vat_SendAircraftConfig(m_net.data(), toFSD(callsign), toFSD(data)); - - // statistics - this->increaseStatisticsValue(QStringLiteral("Vat_SendAircraftConfig")); - } - - void CNetworkVatlib::sendIcaoCodesQuery(const CCallsign &callsign) - { - Q_ASSERT_X(isConnected(), Q_FUNC_INFO, "Can't send to server when disconnected"); - Vat_RequestAircraftInfo(m_net.data(), toFSD(callsign)); - - // statistics - this->increaseStatisticsValue(QStringLiteral("Vat_RequestAircraftInfo")); - } - - void CNetworkVatlib::sendAircraftInfo(const CCallsign &callsign) // private - { - const QString airlineIcao = m_server.getFsdSetup().force3LetterAirlineCodes() ? m_ownAirlineIcaoCode.getDesignator() : m_ownAirlineIcaoCode.getVDesignator(); - const QByteArray acTypeICAObytes = toFSDnoColon(m_ownAircraftIcaoCode.getDesignator()); - const QByteArray liverybytes = toFSDnoColon(m_ownLivery); - const QByteArray airlineICAObytes = toFSDnoColon(airlineIcao); - - VatAircraftInfo aircraftInfo { acTypeICAObytes, airlineICAObytes, m_sendLiveryString ? liverybytes : QByteArray() }; - Vat_SendAircraftInfo(m_net.data(), toFSD(callsign), &aircraftInfo); - - // statistics - this->increaseStatisticsValue(QStringLiteral("Vat_SendAircraftInfo")); - } - - void CNetworkVatlib::sendIncrementalAircraftConfig() - { - if (!this->isConnected()) { return; } - if (!this->getSetupForServer().sendAircraftParts()) { return; } - const CAircraftParts currentParts(this->getOwnAircraftParts()); - - // If it hasn't changed, return - if (m_sentAircraftConfig == currentParts) { return; } - - if (!m_tokenBucket.tryConsume()) - { - // If timer is not yet active, start it - if (!m_scheduledConfigUpdate.isActive()) m_scheduledConfigUpdate.start(1000); - return; - } - - // Method could have been triggered by another change in aircraft config - // so a previous update might still be scheduled. Stop it. - if (m_scheduledConfigUpdate.isActive()) m_scheduledConfigUpdate.stop(); - const QJsonObject previousConfig = m_sentAircraftConfig.toJson(); - const QJsonObject currentConfig = currentParts.toJson(); - const QJsonObject incrementalConfig = getIncrementalObject(previousConfig, currentConfig); - broadcastAircraftConfig(incrementalConfig); - m_sentAircraftConfig = currentParts; - } - - void CNetworkVatlib::sendPing(const CCallsign &callsign) - { - Q_ASSERT_X(isConnected(), Q_FUNC_INFO, "Can't send to server when disconnected"); - Vat_SendPing(m_net.data(), toFSD(callsign)); - - // statistics - this->increaseStatisticsValue(QStringLiteral("Vat_SendPing")); - } - - void CNetworkVatlib::sendMetarQuery(const CAirportIcaoCode &airportIcao) - { - Q_ASSERT_X(isConnected(), Q_FUNC_INFO, "Can't send to server when disconnected"); - Vat_RequestMetar(m_net.data(), toFSD(airportIcao.asString())); - - // statistics - this->increaseStatisticsValue(QStringLiteral("Vat_RequestMetar")); - } - - const QList &CNetworkVatlib::getCmdLineOptions() - { - static const QList e; - static const QList opts - { - QCommandLineOption({ "idAndKey", "clientIdAndKey" }, - QCoreApplication::translate("networkvatlib", "Client id and key pair separated by ':', e.g. :."), "clientIdAndKey") - }; - - // only in not officially shipped versions - return (CBuildConfig::isLocalDeveloperDebugBuild()) ? opts : e; - } - - bool CNetworkVatlib::getCmdLineClientIdAndKey(int &id, QString &key) - { - // init values - id = 0; - key = ""; - - // split parser values - if (CNetworkVatlib::getCmdLineOptions().isEmpty()) { return false; } // no such option, avoid warnings - const QString clientIdAndKey = sApp->getParserValue("clientIdAndKey").toLower(); - if (clientIdAndKey.isEmpty() || !clientIdAndKey.contains(':')) { return false; } - const QStringList stringList = clientIdAndKey.split(':'); - const QString clientIdAsString = stringList[0]; - bool ok = true; - id = clientIdAsString.toInt(&ok, 0); // base 0 means C convention - if (!ok || id == 0) { return false; } - key = stringList[1]; - return true; - } - - void CNetworkVatlib::sendCustomFsinnQuery(const CCallsign &callsign) - { - Q_ASSERT_X(isConnected(), Q_FUNC_INFO, "Can't send to server when disconnected"); - const CSimulatedAircraft myAircraft(getOwnAircraft()); - QString modelString = m_ownModelString.isEmpty() ? myAircraft.getModelString() : m_ownModelString; - if (modelString.isEmpty()) { modelString = noModelString(); } - const QStringList data { { "0" }, - myAircraft.getAirlineIcaoCodeDesignator(), - myAircraft.getAircraftIcaoCodeDesignator(), - { QString() }, { QString() }, { QString() }, { QString() }, - myAircraft.getAircraftIcaoCombinedType(), - m_sendMModelString ? removeColon(modelString) : QString() - }; - sendCustomPacket(callsign, QStringLiteral("FSIPIR"), data); - } - - void CNetworkVatlib::sendCustomFsinnReponse(const CCallsign &callsign) - { - Q_ASSERT_X(isConnected(), Q_FUNC_INFO, "Can't send to server when disconnected"); - - static const QStringList dataTemplate{ "0", "", "", "", "", "", "", "", "" }; - const CSimulatedAircraft myAircraft(getOwnAircraft()); - - QStringList data(dataTemplate); - data[1] = myAircraft.getAirlineIcaoCodeDesignator(); - data[2] = myAircraft.getAircraftIcaoCodeDesignator(); - data[7] = myAircraft.getAircraftIcaoCombinedType(); - data[8] = removeColon(myAircraft.hasModelString() ? myAircraft.getModelString() : noModelString()); - this->sendCustomPacket(callsign, "FSIPI", data); - } - - void CNetworkVatlib::broadcastAircraftConfig(const QJsonObject &config) - { - const QString dataStr = convertToUnicodeEscaped(QJsonDocument(QJsonObject { { "config", config } }).toJson(QJsonDocument::Compact)); - const QByteArray data(toFSD(dataStr)); - Vat_SendAircraftConfigBroadcast(m_net.data(), data); - - // statistics - this->increaseStatisticsValue(QStringLiteral("Vat_SendAircraftConfigBroadcast")); - } - - void CNetworkVatlib::sendAircraftConfigQuery(const CCallsign &callsign) - { - static const QString dataStr { QJsonDocument(JsonPackets::aircraftConfigRequest()).toJson(QJsonDocument::Compact) }; - static const QByteArray data(toFSD(dataStr)); - Vat_SendAircraftConfig(m_net.data(), toFSD(callsign), data); - - // statistics - this->increaseStatisticsValue(QStringLiteral("Vat_SendAircraftConfig")); - } - - /********************************** * * * * * * * * * * * * * * * * * * * ************************************/ - /********************************** shimlib callbacks ************************************/ - /********************************** * * * * * * * * * * * * * * * * * * * ************************************/ - - // Cast void* to a pointer of CNetworkVatlib - CNetworkVatlib *cbvar_cast(void *cbvar) - { - return static_cast(cbvar); - } - - void CNetworkVatlib::onConnectionStatusChanged(VatFsdClient *, VatConnectionStatus, VatConnectionStatus newStatus, void *cbvar) - { - cbvar_cast(cbvar)->changeConnectionStatus(newStatus); - } - - void CNetworkVatlib::onTextMessageReceived(VatFsdClient *, const char *from, const char *to, const char *msg, void *cbvar) - { - auto *self = cbvar_cast(cbvar); - - // statistics - self->increaseStatisticsValue(__func__); - - const CCallsign sender(self->fromFSD(from)); - const CCallsign receiver(self->fromFSD(to)); - const QString message(self->fromFSD(msg)); - - // Other FSD servers send the controller ATIS as text message. The following conditions need to be met: - // * non-VATSIM server. VATSIM has a specific ATIS message - // * Receiver callsign must be owner callsign and not any type of broadcast. - // * We have requested the ATIS of this controller before. - if (self->m_server.getServerType() != CServer::FSDServerVatsim && - self->m_ownCallsign == receiver && - self->m_pendingAtisQueries.contains(sender)) - { - self->maybeHandleAtisReply(sender, receiver, message); - return; - } - - CTextMessage tm(message, sender, receiver); - tm.setCurrentUtcTime(); - cbvar_cast(cbvar)->consolidateTextMessage(tm); - } - - void CNetworkVatlib::onRadioMessageReceived(VatFsdClient *, const char *from, unsigned int numFreq, int *freqList, const char *msg, void *cbvar) - { - auto *self = cbvar_cast(cbvar); - - // statistics - self->increaseStatisticsValue(__func__); - - const CFrequency com1 = self->getOwnAircraft().getCom1System().getFrequencyActive(); - const CFrequency com2 = self->getOwnAircraft().getCom2System().getFrequencyActive(); - QList frequencies; - for (unsigned int i = 0; i < numFreq; ++i) - { - CFrequency f(freqList[i], CFrequencyUnit::kHz()); - // VATSIM always drops the last 5 kHz. So round it to the correct channel spacing. - CComSystem::roundToChannelSpacing(f, CComSystem::ChannelSpacing25KHz); - if (f == com1 || f == com2) - { - frequencies.push_back(f); - } - } - if (frequencies.isEmpty()) { return; } - CTextMessageList messages(self->fromFSD(msg), frequencies, CCallsign(self->fromFSD(from))); - messages.setCurrentUtcTime(); - emit cbvar_cast(cbvar)->textMessagesReceived(messages); - } - - void CNetworkVatlib::onPilotDisconnected(VatFsdClient *, const char *callsign, void *cbvar) - { - auto *self = cbvar_cast(cbvar); - const CCallsign cs(self->fromFSD(callsign), CCallsign::Aircraft); - self->clearState(cs); - emit self->pilotDisconnected(cs); - } - - void CNetworkVatlib::onControllerDisconnected(VatFsdClient *, const char *callsign, void *cbvar) - { - emit cbvar_cast(cbvar)->atcDisconnected(CCallsign(cbvar_cast(cbvar)->fromFSD(callsign), CCallsign::Atc)); - } - - void CNetworkVatlib::onPilotPositionUpdate(VatFsdClient *, const char *callsignChar, const VatPilotPosition *position, void *cbvar) - { - auto *self = cbvar_cast(cbvar); - - // statistics - self->increaseStatisticsValue(__func__); - - const CCallsign callsign(callsignChar, CCallsign::Aircraft); - CAircraftSituation situation( - callsign, - CCoordinateGeodetic(position->latitude, position->longitude, position->altitudeTrue), - CHeading(position->heading, CHeading::True, CAngleUnit::deg()), - CAngle(position->pitch, CAngleUnit::deg()), - CAngle(position->bank, CAngleUnit::deg()), - CSpeed(position->groundSpeed, CSpeedUnit::kts()) - ); - - // Ref T297, default offset time - situation.setCurrentUtcTime(); - const qint64 offsetTimeMs = self->receivedPositionFixTsAndGetOffsetTime(situation.getCallsign(), situation.getMSecsSinceEpoch()); - situation.setTimeOffsetMs(offsetTimeMs); - - CTransponder::TransponderMode mode = CTransponder::StateStandby; - switch (position->transponderMode) - { - case vatTransponderModeCharlie: mode = CTransponder::ModeC; break; - case vatTransponderModeStandby: mode = CTransponder::StateStandby; break; - case vatTransponderModeIdent: mode = CTransponder::StateIdent; break; - default: break; - } - - // I did have a situation where I got wrong transponder codes (KB) - // So I now check for a valid code in order to detect such codes - CTransponder transponder; - if (CTransponder::isValidTransponderCode(position->transponderCode)) - { - transponder = CTransponder(position->transponderCode, mode); - } - else - { - if (CBuildConfig::isLocalDeveloperDebugBuild()) - { - CLogMessage(static_cast(nullptr)).debug(u"Wrong transponder code '%1' for '%2'") << position->transponderCode << callsign; - } - - // I set a default: IFR standby is a reasonable default - transponder = CTransponder(2000, CTransponder::StateStandby); - } - emit cbvar_cast(cbvar)->aircraftPositionUpdate(situation, transponder); - } - - void CNetworkVatlib::onAircraftConfigReceived(VatFsdClient *, const char *callsignChar, const char *aircraftConfig, void *cbvar) - { - auto *self = cbvar_cast(cbvar); - const CCallsign callsign(self->fromFSD(callsignChar), CCallsign::Aircraft); - - // statistics - self->increaseStatisticsValue(__func__); - - // if (!isInRange) { return; } // sort out all broadcasted we DO NOT NEED - QJsonParseError parserError; - const QByteArray json = self->fromFSD(aircraftConfig).toUtf8(); - const QJsonDocument doc = QJsonDocument::fromJson(json, &parserError); - - if (parserError.error != QJsonParseError::NoError) - { - CLogMessage(self).warning(u"Failed to parse aircraft config packet: '%1' packet: '%2'") << parserError.errorString() << QString(json); - return; // we cannot parse the packet, so we give up here - } - - const QJsonObject packet = doc.object(); - if (packet == JsonPackets::aircraftConfigRequest()) - { - // this MUST work for NOT IN RANGE aircraft as well - self->replyToConfigQuery(callsign); - return; - } - - const bool inRange = self->isAircraftInRange(callsign); - if (!inRange) { return; } // sort out all broadcasted we DO NOT NEED - if (!self->getSetupForServer().receiveAircraftParts()) { return; } - const QJsonObject config = doc.object().value("config").toObject(); - if (config.empty()) { return; } - - const qint64 offsetTimeMs = self->currentOffsetTime(callsign); - emit self->aircraftConfigPacketReceived(callsign, config, offsetTimeMs); - } - - void CNetworkVatlib::onInterimPilotPositionUpdate(VatFsdClient *, const char *sender, const VatInterimPilotPosition *position, void *cbvar) - { - CNetworkVatlib *self = cbvar_cast(cbvar); - if (!self->isInterimPositionReceivingEnabledForServer()) { return; } - - // statistics - self->increaseStatisticsValue(__func__); - - CAircraftSituation situation( - CCallsign(self->fromFSD(sender), CCallsign::Aircraft), - CCoordinateGeodetic(position->latitude, position->longitude, position->altitudeTrue), - CHeading(position->heading, CHeading::True, CAngleUnit::deg()), - CAngle(position->pitch, CAngleUnit::deg()), - CAngle(position->bank, CAngleUnit::deg()), - CSpeed::null() // there is no speed information in an interim packet - ); - - // Ref T297, default offset time - situation.setCurrentUtcTime(); - const qint64 offsetTimeMs = self->receivedPositionFixTsAndGetOffsetTime(situation.getCallsign(), situation.getMSecsSinceEpoch()); - situation.setTimeOffsetMs(offsetTimeMs); - situation.setInterimFlag(true); - - emit self->aircraftInterimPositionUpdate(situation); - } - - void CNetworkVatlib::onAtcPositionUpdate(VatFsdClient *, const char *callsign, const VatAtcPosition *pos, void *cbvar) - { - CNetworkVatlib *self = cbvar_cast(cbvar); - - // statistics - self->increaseStatisticsValue(__func__); - - const int frequencyKHz = pos->frequency; - CFrequency freq(frequencyKHz, CFrequencyUnit::kHz()); - freq.switchUnit(CFrequencyUnit::MHz()); // we would not need to bother, but this makes it easier to identify - const CLength networkRange(pos->visibleRange, CLengthUnit::NM()); - const CCallsign cs(cbvar_cast(cbvar)->fromFSD(callsign), CCallsign::Atc); - - // Filter non-ATC like OBS stations, like pilots logging in as shared cockpit co-pilots. - if (pos->facility == vatFacilityTypeUnknown && !cs.isObserverCallsign()) { return; } - - const CLength range = fixAtcRange(networkRange, cs); - const CCoordinateGeodetic position(pos->latitude, pos->longitude, 0); - - emit self->atcPositionUpdate(cs, freq, position, range); - } - - const CLength &CNetworkVatlib::fixAtcRange(const CLength &networkRange, const CCallsign &cs) - { - /** T702, https://discordapp.com/channels/539048679160676382/539846348275449887/597814208125730826 - DEL 5 NM - GND 10 NM - TWR 25 NM - DEP/APP 150 NM - CTR 300 NM - FSS fixed 1500NM, no minimum - **/ - - // ATIS often have a range of 0 nm. Correct this to a proper value. - const QString suffix = cs.getSuffix(); - if (suffix.contains(QStringLiteral("ATIS"), Qt::CaseInsensitive)) { static const CLength l_Atis(150.0, CLengthUnit::NM()); return maxOrNotNull(networkRange, l_Atis); } - if (suffix.contains(QStringLiteral("GND"), Qt::CaseInsensitive)) { static const CLength l_Gnd(10.0, CLengthUnit::NM()); return maxOrNotNull(networkRange, l_Gnd); } - if (suffix.contains(QStringLiteral("TWR"), Qt::CaseInsensitive)) { static const CLength l_Twr(25.0, CLengthUnit::NM()); return maxOrNotNull(networkRange, l_Twr); } - if (suffix.contains(QStringLiteral("DEP"), Qt::CaseInsensitive)) { static const CLength l_Dep(150.0, CLengthUnit::NM()); return maxOrNotNull(networkRange, l_Dep); } - if (suffix.contains(QStringLiteral("APP"), Qt::CaseInsensitive)) { static const CLength l_App(150.0, CLengthUnit::NM()); return maxOrNotNull(networkRange, l_App); } - if (suffix.contains(QStringLiteral("CTR"), Qt::CaseInsensitive)) { static const CLength l_Ctr(300.0, CLengthUnit::NM()); return maxOrNotNull(networkRange, l_Ctr); } - if (suffix.contains(QStringLiteral("FSS"), Qt::CaseInsensitive)) { static const CLength l_Fss(1500.0, CLengthUnit::NM()); return maxOrNotNull(networkRange, l_Fss); } - - return networkRange; - } - - const CLength &CNetworkVatlib::maxOrNotNull(const CLength &l1, const CLength &l2) - { - if (l1.isNull()) { return l2; } - if (l2.isNull()) { return l1; } - return (l2 > l1) ? l2 : l1; - } - - - void CNetworkVatlib::onKicked(VatFsdClient *, const char *reason, void *cbvar) - { - emit cbvar_cast(cbvar)->kicked(cbvar_cast(cbvar)->fromFSD(reason)); - } - - void CNetworkVatlib::onPong(VatFsdClient *, const char *sender, double elapsedTime, void *cbvar) - { - emit cbvar_cast(cbvar)->pongReceived(cbvar_cast(cbvar)->fromFSD(sender), CTime(elapsedTime, CTimeUnit::ms())); - } - - void CNetworkVatlib::onCustomPacketReceived(VatFsdClient *, const char *callsign, const char *packetId, const char **data, int dataSize, void *cbvar) - { - // statistics en detail handled in customPacketDispatcher - CNetworkVatlib *self = cbvar_cast(cbvar); - self->increaseStatisticsValue(__func__); - self->customPacketDispatcher(cbvar_cast(cbvar)->fromFSD(callsign), cbvar_cast(cbvar)->fromFSD(packetId), cbvar_cast(cbvar)->fromFSD(data, dataSize)); - } - - void CNetworkVatlib::onRawFsdMessage(VatFsdClient *, const char *message, void *cbvar) - { - cbvar_cast(cbvar)->handleRawFsdMessage(cbvar_cast(cbvar)->fromFSD(message)); - } - - void CNetworkVatlib::customPacketDispatcher(const CCallsign &callsign, const QString &packetId, const QStringList &data) - { - if (packetId.compare("FSIPI", Qt::CaseInsensitive) == 0) - { - if (data.size() < 9) - { - CLogMessage(this).warning(u"Malformed FSIPI packet"); - } - else - { - // It doesn't matter whether it was a query or response. The information - // is the same for both. - emit this->customFSInnPacketReceived(callsign, data[1], data[2], data[7], data[8]); - - // statistics - this->increaseStatisticsValue(__func__, packetId); - } - } - else if (packetId.compare("FSIPIR", Qt::CaseInsensitive) == 0) - { - if (data.size() < 9) - { - CLogMessage(this).warning(u"Malformed FSIPIR packet"); - } - else - { - this->sendCustomFsinnReponse(callsign); - // It doesn't matter whether it was a query or response. The information - // is the same for both. - emit this->customFSInnPacketReceived(callsign, data[1], data[2], data[7], data[8]); - - // statistics - this->increaseStatisticsValue(__func__, packetId); - } - } - else if (packetId.compare("FSIP2PR", Qt::CaseInsensitive) == 0) - { - // FSInn peer2peer protocol - ignore, not supported - - // statistics - this->increaseStatisticsValue(__func__, packetId); - } - else if (packetId.compare("FSIP2P", Qt::CaseInsensitive) == 0) - { - // FSInn peer2peer protocol - ignore, not supported - - // statistics - this->increaseStatisticsValue(__func__, packetId); - } - else - { - // statistics - this->increaseStatisticsValue(__func__, packetId); - - CLogMessage(this).warning(u"Unknown custom packet from %1 - id: %2") << callsign.toQString() << packetId; - } - } - - void CNetworkVatlib::handleRawFsdMessage(const QString &fsdMessage) - { - if (!m_rawFsdMessagesEnabled) { return; } - QString fsdMessageFiltered(fsdMessage); - if (m_filterPasswordFromLogin) - { - if (fsdMessageFiltered.startsWith("FSD Sent=>#AP")) - { - thread_local const QRegularExpression re("^(FSD Sent=>#AP\\w+:SERVER:\\d+:)[^:]+(:\\d+:\\d+:\\d+:.+)$"); - fsdMessageFiltered.replace(re, "\\1\\2"); - m_filterPasswordFromLogin = false; - } - } - - CRawFsdMessage rawFsdMessage(fsdMessageFiltered); - rawFsdMessage.setCurrentUtcTime(); - if (m_rawFsdMessageLogFile.isOpen()) - { - QTextStream stream(&m_rawFsdMessageLogFile); - stream << rawFsdMessage.toQString() << endl; - } - emit rawFsdMessageReceived(rawFsdMessage); - } - - void CNetworkVatlib::fsdMessageSettingsChanged() - { - if (!m_net) { return; } - if (m_rawFsdMessageLogFile.isOpen()) { m_rawFsdMessageLogFile.close(); } - const CRawFsdMessageSettings setting = m_fsdMessageSetting.get(); - - // Workaround bug in vatlib v0.9.7. Handlers cannot be updated. - m_rawFsdMessagesEnabled = setting.areRawFsdMessagesEnabled(); - /*if (!setting.areRawFsdMessagesEnabled()) - { - Vat_SetFsdMessageHandler(m_net.data(), nullptr, this); - return; - }*/ - - Vat_SetFsdMessageHandler(m_net.data(), CNetworkVatlib::onRawFsdMessage, this); - - if (setting.getFileWriteMode() == CRawFsdMessageSettings::None || setting.getFileDir().isEmpty()) { return; } - if (setting.getFileWriteMode() == CRawFsdMessageSettings::Truncate) - { - const QString filePath = CFileUtils::appendFilePaths(setting.getFileDir(), "rawfsdmessages.log"); - m_rawFsdMessageLogFile.setFileName(filePath); - m_rawFsdMessageLogFile.open(QIODevice::Text | QIODevice::WriteOnly); - } - else if (setting.getFileWriteMode() == CRawFsdMessageSettings::Append) - { - const QString filePath = CFileUtils::appendFilePaths(setting.getFileDir(), "rawfsdmessages.log"); - m_rawFsdMessageLogFile.setFileName(filePath); - m_rawFsdMessageLogFile.open(QIODevice::Text | QIODevice::WriteOnly | QIODevice::Append); - } - else if (setting.getFileWriteMode() == CRawFsdMessageSettings::Timestamped) - { - const QString filename = u"rawfsdmessages" % - QLatin1String("_") % - QDateTime::currentDateTime().toString(QStringLiteral("yyMMddhhmmss")) % - QLatin1String(".log"); - const QString filePath = CFileUtils::appendFilePaths(setting.getFileDir(), filename); - m_rawFsdMessageLogFile.setFileName(filePath); - m_rawFsdMessageLogFile.open(QIODevice::Text | QIODevice::WriteOnly); - } - } - - void CNetworkVatlib::consolidateTextMessage(const CTextMessage &textMessage) - { - if (textMessage.isSupervisorMessage()) - { - emit this->textMessagesReceived(textMessage); - } - else - { - m_textMessagesToConsolidate.addConsolidatedTextMessage(textMessage); - m_dsSendTextMessage.inputSignal(); // trigger - } - } - - void CNetworkVatlib::emitConsolidatedTextMessages() - { - emit this->textMessagesReceived(m_textMessagesToConsolidate); - m_textMessagesToConsolidate.clear(); - } - - void CNetworkVatlib::onMetarReceived(VatFsdClient *, const char *data, void *cbvar) - { - auto *self = cbvar_cast(cbvar); - emit self->metarReplyReceived(self->fromFSD(data)); - } - - void CNetworkVatlib::maybeHandleAtisReply(const CCallsign &sender, const CCallsign &receiver, const QString &message) - { - Q_ASSERT(m_pendingAtisQueries.contains(sender)); - PendingAtisQuery &pendingQuery = m_pendingAtisQueries[sender]; - pendingQuery.m_atisMessage.push_back(message); - - // Wait maximum 5 seconds for the reply and release as text message after - if (pendingQuery.m_queryTime.secsTo(QDateTime::currentDateTimeUtc()) > 5) - { - const QString atisMessage(pendingQuery.m_atisMessage.join(QChar::LineFeed)); - CTextMessage tm(atisMessage, sender, receiver); - tm.setCurrentUtcTime(); - this->consolidateTextMessage(tm); - m_pendingAtisQueries.remove(sender); - return; - } - - // 4 digits followed by z (e.g. 0200z) is always the last atis line. - // Some controllers leave the logoff time empty. Hence we accept anything - // between 0-4 digits. - thread_local const QRegularExpression reLogoff("^\\d{0,4}z$"); - if (reLogoff.match(message).hasMatch()) - { - emit atisLogoffTimeReplyReceived(sender, message); - CInformationMessage atisMessage; - atisMessage.setType(CInformationMessage::ATIS); - for (const auto &line : as_const(pendingQuery.m_atisMessage)) - { - if (!atisMessage.isEmpty()) atisMessage.appendMessage("\n"); - atisMessage.appendMessage(line); - } - emit atisReplyReceived(CCallsign(sender.toQString(), CCallsign::Atc), atisMessage); - m_pendingAtisQueries.remove(sender); - return; - } - } - - qint64 CNetworkVatlib::receivedPositionFixTsAndGetOffsetTime(const CCallsign &callsign, qint64 markerTs) - { - Q_ASSERT_X(!callsign.isEmpty(), Q_FUNC_INFO, "Need callsign"); - - if (markerTs < 0) { markerTs = QDateTime::currentMSecsSinceEpoch(); } - if (!m_lastPositionUpdate.contains(callsign)) - { - m_lastPositionUpdate.insert(callsign, markerTs); - return CFsdSetup::c_positionTimeOffsetMsec; - } - const qint64 oldTs = m_lastPositionUpdate.value(callsign); - m_lastPositionUpdate[callsign] = markerTs; - - // Ref T297, dynamic offsets - const qint64 diff = qAbs(markerTs - oldTs); - this->insertLatestOffsetTime(callsign, diff); - - int count = 0; - static const qint64 minOffsetTime = CFsdSetup::c_interimPositionTimeOffsetMsec; // no longer needed with C++17 - const qint64 avgTimeMs = this->averageOffsetTimeMs(callsign, count, 3); // latest average - qint64 offsetTime = CFsdSetup::c_positionTimeOffsetMsec; - - if (avgTimeMs < minOffsetTime && count >= 3) - { - offsetTime = CFsdSetup::c_interimPositionTimeOffsetMsec; - } - - return m_additionalOffsetTime + offsetTime; - } - - qint64 CNetworkVatlib::currentOffsetTime(const CCallsign &callsign) const - { - Q_ASSERT_X(!callsign.isEmpty(), Q_FUNC_INFO, "Need callsign"); - - if (!m_lastOffsetTimes.contains(callsign) || m_lastOffsetTimes[callsign].isEmpty()) { return CFsdSetup::c_positionTimeOffsetMsec; } - return m_lastOffsetTimes[callsign].front(); - } - - void CNetworkVatlib::clearState() - { - m_textMessagesToConsolidate.clear(); - m_pendingAtisQueries.clear(); - m_lastPositionUpdate.clear(); - m_lastOffsetTimes.clear(); - m_sentAircraftConfig = CAircraftParts::null(); - } - - void CNetworkVatlib::clearState(const CCallsign &callsign) - { - if (callsign.isEmpty()) { return; } - m_pendingAtisQueries.remove(callsign); - m_lastPositionUpdate.remove(callsign); - m_interimPositionReceivers.remove(callsign); - m_lastOffsetTimes.remove(callsign); - } - - void CNetworkVatlib::insertLatestOffsetTime(const CCallsign &callsign, qint64 offsetMs) - { - QList &offsets = m_lastOffsetTimes[callsign]; - offsets.push_front(offsetMs); - if (offsets.size() > MaxOffsetTimes) { offsets.removeLast(); } - } - - qint64 CNetworkVatlib::averageOffsetTimeMs(const CCallsign &callsign, int &count, int maxLastValues) const - { - const QList &offsets = m_lastOffsetTimes[callsign]; - if (offsets.size() < 1) { return -1; } - qint64 sum = 0; - count = 0; - for (qint64 v : offsets) - { - count++; - sum += v; - if (count > maxLastValues) { break; } - } - return qRound(static_cast(sum) / count); - } - - qint64 CNetworkVatlib::averageOffsetTimeMs(const CCallsign &callsign, int maxLastValues) const - { - int count = 0; - return this->averageOffsetTimeMs(callsign, maxLastValues, count); - } - - QString CNetworkVatlib::removeColon(const QString &candidate) - { - QString r = candidate; - return r.remove(':'); - } - - const QString &CNetworkVatlib::enumToString(VatClientQueryType type) - { - switch (type) - { - case vatClientQueryFP: { static const QString fp("vatClientQueryFP"); return fp; } - case vatClientQueryFreq: { static const QString fr("vatClientQueryFreq"); return fr; } - case vatClientQueryInfo: { static const QString in("vatClientQueryInfo"); return in; } - case vatClientQueryAtis: { static const QString at("vatClientQueryAtis"); return at; } - case vatClientQueryServer: { static const QString se("vatClientQueryServer"); return se; } - case vatClientQueryName: { static const QString na("vatClientQueryName"); return na; } - case vatClientQueryAtc: { static const QString ac("vatClientQueryAtc"); return ac; } - case vatClientQueryCaps: { static const QString ca("vatClientQueryCaps"); return ca; } - case vatClientQueryIP: { static const QString ip("vatClientQueryIP"); return ip; } - } - static const QString unknown = "????"; - return unknown; - } - - void CNetworkVatlib::onInfoQueryRequestReceived(VatFsdClient *, const char *callsignString, VatClientQueryType type, const char *, void *cbvar) - { - QPointer self(cbvar_cast(cbvar)); - - // statistics - self->increaseStatisticsValue(__func__, enumToString(type)); - - const CCallsign callsign(self->fromFSD(callsignString)); - const bool valid = !callsign.isEmpty(); - BLACK_AUDIT_X(valid, Q_FUNC_INFO, "No callsign"); - if (!valid) { return; } - - switch (type) - { - case vatClientQueryFreq: - QTimer::singleShot(0, self, [ = ]() { if (self) self->replyToFrequencyQuery(callsign); }); - break; - case vatClientQueryName: - QTimer::singleShot(0, self, [ = ]() { if (self) self->replyToNameQuery(callsign); }); - break; - default: - break; - } - } - - void CNetworkVatlib::onInfoQueryReplyReceived(VatFsdClient *, const char *callsign, VatClientQueryType type, const char *data, const char *data2, void *cbvar) - { - auto *self = cbvar_cast(cbvar); - - // statistics - self->increaseStatisticsValue(__func__, enumToString(type)); - - switch (type) - { - case vatClientQueryFreq: emit self->frequencyReplyReceived(self->fromFSD(callsign), CFrequency(self->fromFSD(data).toDouble(), CFrequencyUnit::MHz())); break; - case vatClientQueryServer: emit self->serverReplyReceived(self->fromFSD(callsign), self->fromFSD(data)); break; - case vatClientQueryAtc: emit self->atcReplyReceived(CCallsign(self->fromFSD(data2), CCallsign::Atc), *data == 'Y'); break; - case vatClientQueryName: emit self->realNameReplyReceived(self->fromFSD(callsign), self->fromFSD(data)); break; - case vatClientQueryIP: emit self->ipReplyReceived(self->fromFSD(data)); break; - default: break; - } - } - - void CNetworkVatlib::onCapabilitiesReplyReceived(VatFsdClient *, const char *callsign, int capabilityFlags, void *cbvar) - { - CClient::Capabilities caps = CClient::None; - if (capabilityFlags & vatCapsAtcInfo) { caps |= CClient::FsdAtisCanBeReceived; } - if (capabilityFlags & vatCapsFastPos) { caps |= CClient::FsdWithInterimPositions; } - if (capabilityFlags & vatCapsAircraftInfo) { caps |= CClient::FsdWithIcaoCodes; } - if (capabilityFlags & vatCapsAircraftConfig) { caps |= CClient::FsdWithAircraftConfig; } - auto *self = cbvar_cast(cbvar); - - // statistics - self->increaseStatisticsValue(__func__); - - emit self->capabilitiesReplyReceived(self->fromFSD(callsign), static_cast(caps)); - } - - void CNetworkVatlib::onAtisReplyReceived(VatFsdClient *, const char *callsign, const VatControllerAtis *atis, void *cbvar) - { - auto *self = cbvar_cast(cbvar); - - // statistics - self->increaseStatisticsValue(__func__); - - const CCallsign cs(self->fromFSD(callsign), CCallsign::Atc); - const QString voiceRoom(self->fromFSD(atis->voiceRoom)); - const QString zuluLogoff = self->fromFSD(atis->zuluLogoff); - emit self->atisVoiceRoomReplyReceived(cs, voiceRoom); - emit self->atisLogoffTimeReplyReceived(cs, zuluLogoff); - - CInformationMessage atisMessage; - atisMessage.setType(CInformationMessage::ATIS); - for (unsigned int i = 0; i < atis->textLineCount; ++i) - { - const QString fixed = cbvar_cast(cbvar)->fromFSD(atis->textLines[i]).trimmed(); - if (!fixed.isEmpty()) - { - // detect the stupid z1, z2, z3 placeholders - //! \fixme: Anything better as this stupid code here? - thread_local const QRegularExpression RegExp("[\\n\\t\\r]"); - const QString test = fixed.toLower().remove(RegExp); - if (test == "z") return; - if (test.startsWith("z") && test.length() == 2) return; // z1, z2, .. - if (test.length() == 1) return; // sometimes just z - - // append - if (!atisMessage.isEmpty()) atisMessage.appendMessage("\n"); - atisMessage.appendMessage(fixed); - } - } - - emit self->atisReplyReceived(cs, atisMessage); - } - - void CNetworkVatlib::onFlightPlanReceived(VatFsdClient *, const char *callsignChar, const VatFlightPlan *fp, void *cbvar) - { - CFlightPlan::FlightRules rules = CFlightPlan::VFR; - switch (fp->flightType) - { - case vatFlightTypeVFR: rules = CFlightPlan::VFR; break; - case vatFlightTypeIFR: rules = CFlightPlan::IFR; break; - case vatFlightTypeDVFR: rules = CFlightPlan::DVFR; break; - case vatFlightTypeSVFR: rules = CFlightPlan::SVFR; break; - default: rules = CFlightPlan::UNKNOWN; break; - } - - auto *self = cbvar_cast(cbvar); - - // statistics - self->increaseStatisticsValue(__func__); - - QString cruiseAltString = self->fromFSD(fp->cruiseAltitude).trimmed(); - if (!cruiseAltString.isEmpty() && is09OnlyString(cruiseAltString)) - { - int ca = cruiseAltString.toInt(); - // we have a 0-9 only string - // we assume values like 24000 as FL - // RefT323, also major tool such as PFPX and Simbrief do so - if (rules == CFlightPlan::IFR) - { - if (ca >= 1000) - { - cruiseAltString = u"FL" % QString::number(ca / 100); - } - else - { - cruiseAltString = u"FL" % cruiseAltString; - } - } - else // VFR - { - if (ca >= 5000) - { - cruiseAltString = u"FL" % QString::number(ca / 100); - } - else - { - cruiseAltString = cruiseAltString % u"ft"; - } - } - } - CAltitude cruiseAlt; - cruiseAlt.parseFromString(cruiseAltString, CPqString::SeparatorBestGuess); - - const QString depTimePlanned = QStringLiteral("0000").append(QString::number(fp->departTime)).right(4); - const QString depTimeActual = QStringLiteral("0000").append(QString::number(fp->departTimeActual)).right(4); - - const CCallsign callsign(self->fromFSD(callsignChar), CCallsign::Aircraft); - const CFlightPlan flightPlan( - callsign, - self->fromFSD(fp->aircraftType), - self->fromFSD(fp->departAirport), - self->fromFSD(fp->destAirport), - self->fromFSD(fp->alternateAirport), - fromStringUtc(depTimePlanned, "hhmm"), - fromStringUtc(depTimeActual, "hhmm"), - CTime(fp->enrouteHrs * 60 + fp->enrouteMins, CTimeUnit::min()), - CTime(fp->fuelHrs * 60 + fp->fuelMins, CTimeUnit::min()), - cruiseAlt, - CSpeed(fp->trueCruisingSpeed, CSpeedUnit::kts()), - rules, - self->fromFSD(fp->route), - self->fromFSD(fp->remarks) - ); - - emit self->flightPlanReplyReceived(callsign, flightPlan); - } - - void CNetworkVatlib::onErrorReceived(VatFsdClient *, VatServerError error, const char *msg, const char *data, void *cbvar) - { - auto *self = cbvar_cast(cbvar); - const QString sn = self->m_server.getName(); - switch (error) - { - case vatServerErrorCsInUs: CLogMessage(self).error(u"The requested callsign is already taken"); emit self->terminate(); break; - case vatServerErrorCallsignInvalid: CLogMessage(self).error(u"The requested callsign is not valid"); emit self->terminate(); break; - case vatServerErrorCidInvalid: CLogMessage(self).error(u"Wrong user ID or password, inactive account. Server: '%1'") << sn; emit self->terminate(); break; - case vatServerErrorRevision: CLogMessage(self).error(u"This server '%1' does not support our protocol version") << sn; emit self->terminate(); break; - case vatServerErrorLevel: CLogMessage(self).error(u"You are not authorized to use the requested pilot rating"); emit self->terminate(); break; - case vatServerErrorServFull: CLogMessage(self).error(u"The server '%1' is full") << sn; emit self->terminate(); break; - case vatServerErrorCsSuspended: CLogMessage(self).error(u"Your user account is suspended"); emit self->terminate(); break; - case vatServerErrorInvPos: CLogMessage(self).error(u"You are not authorized to use the requested rating"); emit self->terminate(); break; - case vatServerErrorUnAuth: CLogMessage(self).error(u"This software is not authorized for use on this network '%1'") << sn; emit self->terminate(); break; - - case vatServerErrorNone: CLogMessage(self).info(u"OK"); break; - case vatServerErrorSyntax: CLogMessage(self).info(u"Malformed packet: Syntax error: %1") << self->fromFSD(data); break; - case vatServerErrorSrcInvalid: CLogMessage(self).info(u"Server: source invalid %1") << self->fromFSD(data); break; - case vatServerErrorNoSuchCs: CLogMessage(self).info(u"FSD message was using an invalid callsign: %1 (%2)") << self->fromFSD(msg) << self->fromFSD(data); break; - case vatServerErrorNoFP: CLogMessage(self).info(u"Server: no flight plan"); break; - case vatServerErrorNoWeather: CLogMessage(self).info(u"Server: requested weather profile does not exist"); break; - - // we have no idea what these mean - case vatServerErrorRegistered: CLogMessage(self).info(u"vatServerErrorRegistered: ") << self->fromFSD(msg); break; - case vatServerErrorInvalidCtrl: CLogMessage(self).info(u"vatServerErrorInvalidCtrl: ") << self->fromFSD(msg); break; - case vatServerWrongType: CLogMessage(self).info(u"vatServerWrongType: ") << self->fromFSD(msg); break; - - // default: qFatal("vatlib: %s (error %d)", msg, error); emit self->terminate(); - // KB: Why the hard termination? - default: CLogMessage(self).error(u"vatlib: %1 (error %2) server: '%3'") << msg << error << self->m_server.getName(); emit self->terminate(); break; - } - } - - void CNetworkVatlib::onPilotInfoRequestReceived(VatFsdClient *, const char *callsignChar, void *cbvar) - { - QPointer self(cbvar_cast(cbvar)); - - // statistics - self->increaseStatisticsValue(__func__); - - const CCallsign callsign(self->fromFSD(callsignChar)); - QTimer::singleShot(0, self, [ = ]() { if (self) { self->sendAircraftInfo(callsign); }}); - } - - void CNetworkVatlib::onPilotInfoReceived(VatFsdClient *, const char *callsignChar, const VatAircraftInfo *aircraftInfo, void *cbvar) - { - auto *self = cbvar_cast(cbvar); - - // statistics - self->increaseStatisticsValue(__func__); - - const CCallsign callsign(self->fromFSD(callsignChar), CCallsign::Aircraft); - emit self->icaoCodesReplyReceived( - callsign, - self->fromFSD(aircraftInfo->aircraftType).trimmed().toUpper(), - self->fromFSD(aircraftInfo->airline).trimmed().toUpper(), - self->fromFSD(aircraftInfo->livery).trimmed().toUpper() - ); - } - - void CNetworkVatlib::networkLogHandler(VatSeverityLevel /** severity **/, const char *context, const char *message) - { - const QString errorMessage = QStringLiteral("vatlib ") % context % QStringLiteral(": ") % message; - CLogMessage(static_cast(nullptr)).error(errorMessage); - } - - QString CNetworkVatlib::simplifyTextMessage(const QString &msg) - { - if (msg.isEmpty()) { return {}; } - - // per discussion of T519 only simplify, do not remove accents - // return asciiOnlyString(simplifyAccents(msg.simplified().trimmed())); - return msg.simplified().trimmed(); - } - - const QJsonObject &CNetworkVatlib::JsonPackets::aircraftConfigRequest() - { - static const QJsonObject jsonObject{ { "request", "full" } }; - return jsonObject; - } - } // namespace -} // namespace - -//! \endcond diff --git a/src/blackcore/vatsim/networkvatlib.h b/src/blackcore/vatsim/networkvatlib.h deleted file mode 100644 index 88d8bf08c..000000000 --- a/src/blackcore/vatsim/networkvatlib.h +++ /dev/null @@ -1,334 +0,0 @@ -/* Copyright (C) 2013 - * swift Project Community / Contributors - * - * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level - * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, - * or distributed except according to the terms contained in the LICENSE file. - */ - -//! \file - -#ifndef BLACKCORE_VATSIM_NETWORK_VATLIB_H -#define BLACKCORE_VATSIM_NETWORK_VATLIB_H - -#include "blackcore/blackcoreexport.h" -#include "blackcore/network.h" -#include "blackcore/vatsim/vatsimsettings.h" -#include "blackmisc/simulation/simulatorplugininfo.h" -#include "blackmisc/network/server.h" -#include "blackmisc/network/fsdsetup.h" -#include "blackmisc/network/textmessagelist.h" -#include "blackmisc/aviation/aircrafticaocode.h" -#include "blackmisc/aviation/aircraftparts.h" -#include "blackmisc/aviation/airlineicaocode.h" -#include "blackmisc/aviation/airporticaocode.h" -#include "blackmisc/aviation/callsign.h" -#include "blackmisc/aviation/callsignset.h" -#include "blackmisc/tokenbucket.h" -#include "blackmisc/settingscache.h" -#include "blackmisc/digestsignal.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -class QCommandLineOption; - -namespace BlackMisc -{ - namespace Aviation { class CFlightPlan; } - namespace Simulation { class CSimulatedAircraft; } -} - -namespace BlackCore -{ - namespace Vatsim - { - //! Implementation of INetwork using the vatlib shim - class BLACKCORE_EXPORT CNetworkVatlib : public INetwork - { - Q_OBJECT - - public: - //! Log. categories - static const BlackMisc::CLogCategoryList &getLogCategories(); - - //! Constructor - CNetworkVatlib(BlackMisc::Network::IClientProvider *clientProvider, - BlackMisc::Simulation::IOwnAircraftProvider *ownAircraftProvider, - BlackMisc::Simulation::IRemoteAircraftProvider *remoteAircraftProvider, - QObject *parent); - - //! Destructor - virtual ~CNetworkVatlib() override; - - //! \copydoc INetwork::getLibraryInfo - const QString &getLibraryInfo(bool detailed) const override; - - //! \name Network functions - //! @{ - virtual bool isConnected() const override { return m_status == vatStatusConnected; } - virtual bool isPendingConnection() const override { return m_status == vatStatusConnecting; } - virtual const BlackMisc::Network::CServer &getPresetServer() const override { return m_server; } - virtual QStringList getPresetValues() const override; - virtual const BlackMisc::Aviation::CCallsign &getPresetPartnerCallsign() const override { return m_partnerCallsign; } - virtual void presetLoginMode(LoginMode mode) override; - virtual void presetServer(const BlackMisc::Network::CServer &server) override; - virtual void presetCallsign(const BlackMisc::Aviation::CCallsign &callsign) override; - virtual void presetPartnerCallsign(const BlackMisc::Aviation::CCallsign &callsign) override; - virtual void presetIcaoCodes(const BlackMisc::Simulation::CSimulatedAircraft &ownAircraft) override; - virtual void presetLiveryAndModelString(const QString &livery, bool sendLiveryString, const QString &modelString, bool sendModelString) override; - virtual void presetSimulatorInfo(const BlackMisc::Simulation::CSimulatorPluginInfo &simInfo) override; - virtual void initiateConnection() override; - virtual void terminateConnection() override; - virtual void sendPing(const BlackMisc::Aviation::CCallsign &callsign) override; - virtual void sendRealNameQuery(const BlackMisc::Aviation::CCallsign &callsign) override; - virtual void sendIpQuery() override; - virtual void sendServerQuery(const BlackMisc::Aviation::CCallsign &callsign) override; - virtual void sendCustomFsinnQuery(const BlackMisc::Aviation::CCallsign &callsign) override; - virtual void sendCustomFsinnReponse(const BlackMisc::Aviation::CCallsign &callsign) override; - virtual void broadcastAircraftConfig(const QJsonObject &config) override; - virtual void sendAircraftConfigQuery(const BlackMisc::Aviation::CCallsign &callsign) override; - //! @} - - //! \name Text message functions - //! @{ - virtual void sendTextMessages(const BlackMisc::Network::CTextMessageList &messages) override; - virtual void sendWallopMessage(const QString &message) override; - //! @} - - //! \name ATC functions - //! @{ - virtual void sendAtcQuery(const BlackMisc::Aviation::CCallsign &callsign) override; - virtual void sendAtisQuery(const BlackMisc::Aviation::CCallsign &callsign) override; - virtual void sendFlightPlan(const BlackMisc::Aviation::CFlightPlan &flightPlan) override; - virtual void sendFlightPlanQuery(const BlackMisc::Aviation::CCallsign &callsign) override; - //! @} - - //! \name Aircraft functions - //! @{ - virtual void sendCapabilitiesQuery(const BlackMisc::Aviation::CCallsign &callsign) override; - virtual void sendIcaoCodesQuery(const BlackMisc::Aviation::CCallsign &callsign) override; - virtual void sendFrequencyQuery(const BlackMisc::Aviation::CCallsign &callsign) override; - virtual void sendUserInfoQuery(const BlackMisc::Aviation::CCallsign &callsign) override; - virtual void setInterimPositionReceivers(const BlackMisc::Aviation::CCallsignSet &receivers) override; - virtual const BlackMisc::Aviation::CCallsignSet &getInterimPositionReceivers() const override; - //! @} - - //! \name Weather functions - //! @{ - virtual void sendMetarQuery(const BlackMisc::Aviation::CAirportIcaoCode &airportIcao) override; - //! @} - - //! Additional offset time @{ - virtual qint64 getAdditionalOffsetTime() const override { return m_additionalOffsetTime; } - virtual void setAdditionalOffsetTime(qint64 addOffset) override { m_additionalOffsetTime = addOffset; } - //! @} - - //! Arguments to be passed to another swift appplication - static QStringList vatlibArguments(); - - //! Command line options this library can handle - static const QList &getCmdLineOptions(); - - private: - static int constexpr c_processingIntervalMsec = 100; //!< interval for the processing timer - static int constexpr c_updatePostionIntervalMsec = 5000; //!< interval for the position update timer (send our position to network) - static int constexpr c_updateInterimPostionIntervalMsec = 1000; //!< interval for iterim position updates (send our position as interim position) - qint64 m_additionalOffsetTime = 0; //!< additional offset time - - static bool getCmdLineClientIdAndKey(int &id, QString &key); - - void replyToFrequencyQuery(const BlackMisc::Aviation::CCallsign &callsign); - void replyToNameQuery(const BlackMisc::Aviation::CCallsign &callsign); - void replyToConfigQuery(const BlackMisc::Aviation::CCallsign &callsign); - void sendAircraftInfo(const BlackMisc::Aviation::CCallsign &callsign); - void sendIncrementalAircraftConfig(); - - //! \name VATLIB callbacks - //! @{ - static void onConnectionStatusChanged(VatFsdClient *, VatConnectionStatus oldStatus, VatConnectionStatus newStatus, void *cbvar); - static void onTextMessageReceived(VatFsdClient *, const char *from, const char *to, const char *msg, void *cbvar); - static void onRadioMessageReceived(VatFsdClient *, const char *from, unsigned int freqCount, int *freqList, const char *message, void *cbvar); - static void onControllerDisconnected(VatFsdClient *, const char *callsign, void *cbvar); - static void onInterimPilotPositionUpdate(VatFsdClient *, const char *sender, const VatInterimPilotPosition *position, void *cbvar); - static void onAtcPositionUpdate(VatFsdClient *, const char *callsign, const VatAtcPosition *pos, void *cbvar); - static void onKicked(VatFsdClient *, const char *reason, void *cbvar); - static void onPong(VatFsdClient *, const char *sender, double elapsedTime, void *cbvar); - static void onMetarReceived(VatFsdClient *, const char *data, void *cbvar); - static void onInfoQueryRequestReceived(VatFsdClient *, const char *callsign, VatClientQueryType type, const char *data, void *cbvar); - static void onInfoQueryReplyReceived(VatFsdClient *, const char *callsign, VatClientQueryType type, const char *data, const char *data2, void *cbvar); - static void onCapabilitiesReplyReceived(VatFsdClient *, const char *callsign, int capabilityFlags, void *cbvar); - static void onAtisReplyReceived(VatFsdClient *, const char *callsign, const VatControllerAtis *atis, void *cbvar); - static void onFlightPlanReceived(VatFsdClient *, const char *callsign, const VatFlightPlan *fp, void *cbvar); - static void onErrorReceived(VatFsdClient *, VatServerError error, const char *msg, const char *data, void *cbvar); - static void onPilotDisconnected(VatFsdClient *, const char *callsign, void *cbvar); - static void onPilotInfoRequestReceived(VatFsdClient *, const char *callsign, void *cbvar); - static void onPilotInfoReceived(VatFsdClient *, const char *callsign, const VatAircraftInfo *aircraftInfo, void *cbvar); - static void onPilotPositionUpdate(VatFsdClient *, const char *callsign, const VatPilotPosition *position, void *cbvar); - static void onAircraftConfigReceived(VatFsdClient *, const char *callsign, const char *aircraftConfig, void *cbvar); - static void onCustomPacketReceived(VatFsdClient *, const char *callsign, const char *packetId, const char **data, int dataSize, void *cbvar); - static void onRawFsdMessage(VatFsdClient *, const char *message, void *cbvar); - //! @} - - QByteArray toFSDnoColon(const QString &qstr) const; - QByteArray toFSD(const QString &qstr) const; - QByteArray toFSD(const BlackMisc::Aviation::CCallsign &callsign) const; - std::function toFSD(const QStringList &qstrList) const; - QString fromFSD(const char *cstr) const; - QString getNetworkHostApplicationString() const; //!< simulator version and details info string - QStringList fromFSD(const char **cstrArray, int size) const; - bool isInterimPositionSendingEnabledForServer() const; - bool isInterimPositionReceivingEnabledForServer() const; - const BlackMisc::Network::CFsdSetup &getSetupForServer() const; - void startPositionTimers(); - void stopPositionTimers(); - void initializeSession(); - void changeConnectionStatus(VatConnectionStatus newStatus); - bool isDisconnected() const { return m_status != vatStatusConnecting && m_status != vatStatusConnected; } - void sendCustomPacket(const BlackMisc::Aviation::CCallsign &callsign, const QString &packetId, const QStringList &data); - - static QString convertToUnicodeEscaped(const QString &str); - static VatSimType convertToSimType(BlackMisc::Simulation::CSimulatorPluginInfo &simInfo); - static void networkLogHandler(VatSeverityLevel severity, const char *context, const char *message); - static QString simplifyTextMessage(const QString &msg); - - //! Default model string - static const QString &defaultModelString() - { - static const QString dm("Cessna Skyhawk 172SP"); - return dm; - } - - //! Send if no model string is available - static const QString &noModelString() - { - static const QString noms("swift empty string"); - return noms; - } - - struct JsonPackets - { - static const QJsonObject &aircraftConfigRequest(); - }; - - void process(); - void sendPositionUpdate(); - void sendInterimPositions(); - void customPacketDispatcher(const BlackMisc::Aviation::CCallsign &callsign, const QString &packetId, const QStringList &data); - void handleRawFsdMessage(const QString &fsdMessage); - void fsdMessageSettingsChanged(); - - signals: - void terminate(); //!< \private - - private: - //! Consolidate text messages if we receive multiple messages which belong together - //! \remark causes a slight delay - void consolidateTextMessage(const BlackMisc::Network::CTextMessage &textMessage); - - //! Send the consolidatedTextMessages - void emitConsolidatedTextMessages(); - - //! Handles ATIS replies from non-VATSIM servers. If the conditions are not met, the message is - //! released as normal text message. - void maybeHandleAtisReply(const BlackMisc::Aviation::CCallsign &sender, const BlackMisc::Aviation::CCallsign &receiver, const QString &message); - - //! Remember when last position was received - qint64 receivedPositionFixTsAndGetOffsetTime(const BlackMisc::Aviation::CCallsign &callsign, qint64 markerTs = -1); - - //! Current offset time - qint64 currentOffsetTime(const BlackMisc::Aviation::CCallsign &callsign) const; - - //! Clear state when connection is terminated - void clearState(); - - //! Clear state for callsign - void clearState(const BlackMisc::Aviation::CCallsign &callsign); - - //! Insert as first value - void insertLatestOffsetTime(const BlackMisc::Aviation::CCallsign &callsign, qint64 offsetMs); - - //! Average offset time in ms - qint64 averageOffsetTimeMs(const BlackMisc::Aviation::CCallsign &callsign, int &count, int maxLastValues = MaxOffsetTimes) const; - - //! Average offset time in ms - qint64 averageOffsetTimeMs(const BlackMisc::Aviation::CCallsign &callsign, int maxLastValues = MaxOffsetTimes) const; - - //! Remove colon - static QString removeColon(const QString &candidate); - - //! Fix ATC station range - static const BlackMisc::PhysicalQuantities::CLength &fixAtcRange(const BlackMisc::PhysicalQuantities::CLength &networkRange, const BlackMisc::Aviation::CCallsign &cs); - - //! Max or 1st non-null value - static const BlackMisc::PhysicalQuantities::CLength &maxOrNotNull(const BlackMisc::PhysicalQuantities::CLength &l1, const BlackMisc::PhysicalQuantities::CLength &l2); - - //! Enum as - static const QString &enumToString(VatClientQueryType type); - - //! Deletion policy for QScopedPointer - struct VatFsdClientDeleter - { - //! Called by QScopedPointer destructor - static void cleanup(VatFsdClient *session) { if (session) Vat_DestroyNetworkSession(session); } - }; - - QScopedPointer m_net; - LoginMode m_loginMode; - VatConnectionStatus m_status; - BlackMisc::Network::CServer m_server; - QTextCodec *m_fsdTextCodec = nullptr; - BlackMisc::Simulation::CSimulatorPluginInfo m_simulatorInfo; //!< used simulator - BlackMisc::Aviation::CCallsign m_ownCallsign; //!< "buffered callsign", as this must not change when connected - BlackMisc::Aviation::CCallsign m_partnerCallsign; //!< callsign of partner flying in shared cockpit - BlackMisc::Aviation::CAircraftIcaoCode m_ownAircraftIcaoCode; //!< "buffered icao", as this must not change when connected - BlackMisc::Aviation::CAirlineIcaoCode m_ownAirlineIcaoCode; //!< "buffered icao", as this must not change when connected - QString m_ownLivery; //!< "buffered livery", as this must not change when connected - QString m_ownModelString; //!< "buffered model string", as this must not change when connected - bool m_sendLiveryString = true; - bool m_sendMModelString = true; - BlackMisc::Aviation::CCallsignSet m_interimPositionReceivers; //!< all aircraft receiving interim positions - BlackMisc::Aviation::CAircraftParts m_sentAircraftConfig; //!< aircraft parts sent - BlackMisc::CTokenBucket m_tokenBucket; //!< used with aircraft parts messages - - BlackMisc::CDigestSignal m_dsSendTextMessage { this, &CNetworkVatlib::emitConsolidatedTextMessages, 500, 10 }; - BlackMisc::Network::CTextMessageList m_textMessagesToConsolidate; - - QTimer m_scheduledConfigUpdate; - QTimer m_processingTimer; - QTimer m_positionUpdateTimer; //!< sending positions - QTimer m_interimPositionUpdateTimer; //!< sending interim positions - - //! Pending ATIS query since - struct PendingAtisQuery - { - QDateTime m_queryTime = QDateTime::currentDateTimeUtc(); - QStringList m_atisMessage; - }; - - QHash m_pendingAtisQueries; - QHash m_lastPositionUpdate; - QHash> m_lastOffsetTimes; //!< latest offset first - - static const int MaxOffsetTimes = 6; //!< Max offset times kept - - BlackMisc::CSettingReadOnly m_fsdMessageSetting { this, &CNetworkVatlib::fsdMessageSettingsChanged }; - QFile m_rawFsdMessageLogFile; - bool m_rawFsdMessagesEnabled = false; - bool m_filterPasswordFromLogin = false; - }; - } //namespace -} //namespace - -#endif // guard diff --git a/src/blackgui/components/mappingcomponent.h b/src/blackgui/components/mappingcomponent.h index 44bbefc51..4263bb768 100644 --- a/src/blackgui/components/mappingcomponent.h +++ b/src/blackgui/components/mappingcomponent.h @@ -21,6 +21,7 @@ #include "blackmisc/propertyindex.h" #include "blackmisc/network/connectionstatus.h" #include "blackmisc/simulation/aircraftmodellist.h" +#include "blackmisc/simulation/simulatorplugininfo.h" #include "blackmisc/variant.h" #include diff --git a/src/blackmisc/audio/notificationsounds.cpp b/src/blackmisc/audio/notificationsounds.cpp index 76e481a2b..0fd908dbd 100644 --- a/src/blackmisc/audio/notificationsounds.cpp +++ b/src/blackmisc/audio/notificationsounds.cpp @@ -14,6 +14,9 @@ namespace BlackMisc { namespace Audio { + constexpr CNotificationSounds::Notification CNotificationSounds::AllNotifications; + constexpr CNotificationSounds::Notification CNotificationSounds::DefaultNotifications; + const QString &CNotificationSounds::flagToString(CNotificationSounds::NotificationFlag notification) { static const QString unknown("unknown"); diff --git a/src/blackmisc/network/client.cpp b/src/blackmisc/network/client.cpp index 9a7a9774e..3c0d7b6fe 100644 --- a/src/blackmisc/network/client.cpp +++ b/src/blackmisc/network/client.cpp @@ -53,7 +53,7 @@ namespace BlackMisc void CClient::setCapabilities(const Capabilities &capabilities) { - m_capabilities = static_cast(capabilities); + m_capabilities = capabilities; } QString CClient::getCapabilitiesAsString() const diff --git a/src/blackmisc/network/client.h b/src/blackmisc/network/client.h index b43de9302..ed9c1ccc2 100644 --- a/src/blackmisc/network/client.h +++ b/src/blackmisc/network/client.h @@ -158,7 +158,7 @@ namespace BlackMisc private: CUser m_user; - int m_capabilities = static_cast(None); + int m_capabilities; bool m_swift = false; // another swift client QString m_modelString; QString m_server; @@ -179,5 +179,6 @@ namespace BlackMisc Q_DECLARE_METATYPE(BlackMisc::Network::CClient) Q_DECLARE_METATYPE(BlackMisc::Network::CClient::Capability) +Q_DECLARE_METATYPE(BlackMisc::Network::CClient::Capabilities) #endif // guard diff --git a/src/blackmisc/network/registermetadatanetwork.cpp b/src/blackmisc/network/registermetadatanetwork.cpp index 4be220402..f9b4406d8 100644 --- a/src/blackmisc/network/registermetadatanetwork.cpp +++ b/src/blackmisc/network/registermetadatanetwork.cpp @@ -18,8 +18,10 @@ namespace BlackMisc CAuthenticatedUser::registerMetadata(); CConnectionStatus::registerMetadata(); CClient::registerMetadata(); - qDBusRegisterMetaType(); - qRegisterMetaTypeStreamOperators(); + qDBusRegisterMetaType(); + qRegisterMetaTypeStreamOperators(); + qDBusRegisterMetaType(); + qRegisterMetaTypeStreamOperators(); CClientList::registerMetadata(); CEcosystem::registerMetadata(); CEcosystemList::registerMetadata(); diff --git a/src/swiftlauncher/swiftlauncher.cpp b/src/swiftlauncher/swiftlauncher.cpp index a5d583a91..ff3f1f2be 100644 --- a/src/swiftlauncher/swiftlauncher.cpp +++ b/src/swiftlauncher/swiftlauncher.cpp @@ -13,9 +13,9 @@ #include "blackgui/guiapplication.h" #include "blackgui/stylesheetutility.h" #include "blackcore/context/contextapplicationproxy.h" -#include "blackcore/vatsim/networkvatlib.h" #include "blackcore/setupreader.h" #include "blackmisc/simulation/fscommon/fscommonutil.h" +#include "blackcore/context/contextnetwork.h" #include "blackmisc/network/networkutils.h" #include "blackmisc/dbusserver.h" #include "blackmisc/directoryutils.h" @@ -269,7 +269,18 @@ bool CSwiftLauncher::setSwiftCoreExecutable() bool CSwiftLauncher::setSwiftDataExecutable() { m_executable = CDirectoryUtils::executableFilePath(CBuildConfig::swiftDataExecutableName()); - m_executableArgs = sGui->argumentsJoined({}, CNetworkVatlib::vatlibArguments()); + + QStringList fsdArgs; + int id = 0; + QString key; + if (IContextNetwork::getCmdLineClientIdAndKey(id, key)) + { + // from cmd. line + fsdArgs << "--idAndKey"; + fsdArgs << sApp->getParserValue("clientIdAndKey"); // as typed in + } + + m_executableArgs = sGui->argumentsJoined({}, fsdArgs); return true; } diff --git a/tests/blackcore/blackcore.pro b/tests/blackcore/blackcore.pro index c40f0b660..5ffec438a 100644 --- a/tests/blackcore/blackcore.pro +++ b/tests/blackcore/blackcore.pro @@ -2,5 +2,5 @@ TEMPLATE = subdirs SUBDIRS += \ context \ + fsd \ testconnectivity \ - vatsim \ diff --git a/tests/blackcore/fsd/fsd.pro b/tests/blackcore/fsd/fsd.pro new file mode 100644 index 000000000..d6be41cf9 --- /dev/null +++ b/tests/blackcore/fsd/fsd.pro @@ -0,0 +1,5 @@ +TEMPLATE = subdirs + +SUBDIRS += \ + testfsdmessages \ + testfsdclient \ diff --git a/tests/blackcore/fsd/testfsdclient/testfsdclient.cpp b/tests/blackcore/fsd/testfsdclient/testfsdclient.cpp new file mode 100644 index 000000000..6a645e33d --- /dev/null +++ b/tests/blackcore/fsd/testfsdclient/testfsdclient.cpp @@ -0,0 +1,829 @@ +/* Copyright (C) 2018 + * swift Project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project, + * including this file, may be copied, modified, propagated, or distributed except according to the terms + * contained in the LICENSE file. + */ + +//! \cond PRIVATE_TESTS + +/*! +* \file +* \ingroup testblackfsd +*/ + +#include "blackconfig/buildconfig.h" +#include "blackcore/fsd/fsdclient.h" +#include "blackmisc/aviation/flightplan.h" +#include "blackmisc/network/clientprovider.h" +#include "blackmisc/network/rawfsdmessage.h" +#include "blackmisc/network/networkutils.h" +#include "blackmisc/simulation/ownaircraftproviderdummy.h" +#include "blackmisc/simulation/remoteaircraftproviderdummy.h" +#include "blackmisc/registermetadata.h" +#include "blackmisc/network/user.h" +#include "test.h" + +#include +#include +#include + +using namespace BlackMisc; +using namespace BlackMisc::Aviation; +using namespace BlackMisc::Geo; +using namespace BlackMisc::PhysicalQuantities; +using namespace BlackMisc::Network; +using namespace BlackMisc::Simulation; +using namespace BlackConfig; +using namespace BlackCore::Fsd; + +namespace BlackFsdTest +{ + //! Testing FSD Client + class CTestFSDClient : public QObject + { + Q_OBJECT + + public: + //! Constructor + explicit CTestFSDClient(QObject *parent = nullptr) : QObject(parent) {} + + //! Destructor + virtual ~CTestFSDClient() {} + + private slots: + void initTestCase(); + void init(); + void cleanup(); + void testConstructor(); + void testDeleteAtc(); + void testDeletePilot(); + void testTextMessage(); + void testRadioMessage(); + void testPilotDataUpdate(); + void testAtcDataUpdate(); + void testPong(); + void testClientResponseEmptyType(); + void testClientResponseRealName1(); + void testClientResponseRealName2(); + void testClientResponseRealName3(); + void testClientResponseCapabilities(); + void testPlaneInfoRequestFsinn(); + void testPlaneInformationFsinn(); + + void testSendPilotLogin(); + void testSendAtcLogin(); + void testSendDeletePilot(); + void testSendDeleteAtc(); + void testSendPilotDataUpdate1(); + void testSendPilotDataUpdate2(); + void testSendAtcDataUpdate(); + void testSendPing(); + void testSendPong(); + void testSendClientResponse1(); + void testSendClientResponse2(); + void testSendClientQuery1(); + void testSendClientQuery2(); + void testSendClientQuery3(); + void testSendTextMessage1(); + void testSendTextMessage2(); + void testSendRadioMessage1(); + void testSendRadioMessage2(); + void testSendFlightPlan(); + void testSendPlaneInfoRequest(); + void testSendPlaneInformation1(); + void testSendPlaneInformation2(); + void testSendPlaneInformation3(); + void testSendPlaneInformation4(); + void testSendAircraftConfiguration(); + void testCom1FreqQueryResponse(); + void testPlaneInfoRequestResponse(); + void testAuth(); + void testConnection(); + + private: + FSDClient *client = nullptr; + }; + + void CTestFSDClient::initTestCase() + { + BlackMisc::registerMetadata(); + } + + void CTestFSDClient::init() + { + const CServer server = CServer::swiftFsdTestServer(true); + + COwnAircraftProviderDummy::instance()->updateOwnCallsign("ABCD"); + CLivery livery; + livery.setCombinedCode("BER"); + CAirlineIcaoCode airline("BER"); + livery.setAirlineIcaoCode(airline); + CAircraftModel model("Cessna SP1 Paint2", CAircraftModel::TypeOwnSimulatorModel, CAircraftIcaoCode("B737"), livery); + + model.setSimulator(CSimulatorInfo::xplane()); + QString modelDescription("[CSL]"); + model.setDescription(modelDescription); + COwnAircraftProviderDummy::instance()->updateOwnModel(model); + // COwnAircraftProviderDummy::instance()->updateOwnIcaoCodes(CAircraftIcaoCode("B737"), CAirlineIcaoCode("BER")); + + CFrequency frequency(123.000, CFrequencyUnit::MHz()); + COwnAircraftProviderDummy::instance()->updateActiveComFrequency(frequency, CComSystem::Com1, {}); + + client = new FSDClient(CClientProviderDummy::instance(), COwnAircraftProviderDummy::instance(), CRemoteAircraftProviderDummy::instance(), this); + client->setUnitTestMode(true); + client->setCallsign("ABCD"); + client->setClientName("Test Client"); + client->setVersion(0, 8); + client->setClientCapabilities(Capabilities::AtcInfo | Capabilities::AircraftInfo | Capabilities::AircraftConfig); + client->setLoginMode(BlackMisc::Network::CLoginMode::Pilot); + client->setServer(server); + client->setPilotRating(PilotRating::Student); + client->setSimType(SimType::XPLANE10); + client->setPilotRating(PilotRating::Student); + QString key("727d1efd5cb9f8d2c28372469d922bb4"); + client->setClientIdAndKey(0xb9ba, key.toLocal8Bit()); + } + + void CTestFSDClient::cleanup() + { + delete client; + } + + void CTestFSDClient::testConstructor() + { + + } + + void CTestFSDClient::testDeleteAtc() + { + QSignalSpy spy(client, &FSDClient::deleteAtcReceived); + client->sendFsdMessage("#DAEDDM_OBS:1234567\r\n"); + + QCOMPARE(spy.count(), 1); + QList arguments = spy.takeFirst(); + + QCOMPARE(arguments.at(0).toString(), "1234567"); + } + + void CTestFSDClient::testDeletePilot() + { + QSignalSpy spy(client, &FSDClient::deletePilotReceived); + client->sendFsdMessage("#DPOEHAB:1234567\r\n"); + + QCOMPARE(spy.count(), 1); + QList arguments = spy.takeFirst(); + + QCOMPARE(arguments.at(0).toString(), "1234567"); + } + + void CTestFSDClient::testTextMessage() + { + QSignalSpy spy(client, &FSDClient::textMessagesReceived); + client->sendFsdMessage("#TMEDMM_CTR:BER721:Hey how are you doing?\r\n"); + + QCOMPARE(spy.count(), 1); + QList arguments = spy.takeFirst(); + CTextMessageList messages = arguments.at(0).value(); + QCOMPARE(messages.size(), 1); + CTextMessage message = messages.front(); + QCOMPARE(message.getMessage(), "Hey how are you doing?"); + QCOMPARE(message.getRecipientCallsign(), "BER721"); + QCOMPARE(message.getSenderCallsign(), "EDMM_CTR"); + } + + void CTestFSDClient::testRadioMessage() + { + // reference + const CFrequency frequency(124050, CFrequencyUnit::kHz()); + const QString text("BER721, Descend F140 when ready"); + + COwnAircraftProviderDummy::instance()->updateActiveComFrequency(CFrequency(124050, CFrequencyUnit::kHz()), CComSystem::Com1, CIdentifier::anonymous()); + + QSignalSpy spy(client, &FSDClient::textMessagesReceived); + client->sendFsdMessage("#TMEDMM_CTR:@24050:BER721, Descend F140 when ready\r\n"); + + QCOMPARE(spy.count(), 1); + QList arguments = spy.takeFirst(); + CTextMessageList receivedMessages = arguments.at(0).value(); + QCOMPARE(receivedMessages.size(), 1); + CTextMessage message = receivedMessages.front(); + QCOMPARE(message.getMessage(), text); + QCOMPARE(message.getFrequency(), frequency); + QCOMPARE(message.getSenderCallsign(), "EDMM_CTR"); + + QSignalSpy spy2(client, &FSDClient::textMessagesReceived); + client->sendFsdMessage("#TMEDMM_CTR:@24050&@27000:BER721, Descend F140 when ready\r\n"); + + QCOMPARE(spy2.count(), 1); + arguments = spy.takeFirst(); + receivedMessages = arguments.at(0).value(); + QCOMPARE(receivedMessages.size(), 1); + message = receivedMessages.front(); + QCOMPARE(message.getMessage(), text); + QCOMPARE(message.getFrequency(), frequency); + QCOMPARE(message.getSenderCallsign(), "EDMM_CTR"); + } + + void CTestFSDClient::testPilotDataUpdate() + { + QSignalSpy spy(client, &FSDClient::pilotDataUpdateReceived); + client->sendFsdMessage("@N:ABCD:1200:1:48.353855:11.786155:110:0:4290769188:1\r\n"); + + QCOMPARE(spy.count(), 1); + QList arguments = spy.takeFirst(); + QCOMPARE(arguments.size(), 2); + const CAircraftSituation situation = arguments.at(0).value(); + const CTransponder transponder = arguments.at(1).value(); + + QCOMPARE(situation.getCallsign().asString(), "ABCD"); + QCOMPARE(transponder.getTransponderMode(), CTransponder::ModeC); + QCOMPARE(transponder.getTransponderCode(), 1200); + QCOMPARE(situation.getPosition().latitude(), CLatitude(48.353855, CAngleUnit::deg())); + QCOMPARE(situation.getPosition().longitude(), CLongitude(11.786155, CAngleUnit::deg())); + QCOMPARE(situation.getAltitude(), CAltitude(110, CLengthUnit::ft())); + QCOMPARE(situation.getPressureAltitude(), CAltitude(111, CAltitude::MeanSeaLevel, CAltitude::PressureAltitude, CLengthUnit::ft())); + QCOMPARE(situation.getGroundSpeed(), CSpeed(0.0, CSpeedUnit::kts())); + // TODO +// QVERIFY(qAbs(arguments.at(9).toDouble()) < 1.0); +// QVERIFY(qAbs(arguments.at(10).toDouble()) < 1.0); +// QVERIFY(qAbs(arguments.at(11).toDouble() - 25) < 1.0); +// QCOMPARE(arguments.at(12).toBool(), false); + } + + void CTestFSDClient::testAtcDataUpdate() + { + QSignalSpy spy(client, &FSDClient::atcDataUpdateReceived); + client->sendFsdMessage("%ZZZZ_APP:28200:5:150:5:48.11028:16.56972:0\r\n"); + const CCallsign callsign("ZZZZ_APP"); + const CFrequency freq(128200, CFrequencyUnit::kHz()); + const CCoordinateGeodetic pos(48.11028, 16.56972, 0.0); + const CLength range(150.0, CLengthUnit::NM()); + + QCOMPARE(spy.count(), 1); + QList arguments = spy.takeFirst(); + QCOMPARE(arguments.size(), 4); + QCOMPARE(arguments.at(0).value(), callsign); + QCOMPARE(arguments.at(1).value(), freq); + QCOMPARE(arguments.at(2).value(), pos); + QCOMPARE(arguments.at(3).value(), range); + } + + void CTestFSDClient::testPong() + { + QSignalSpy spy(client, &FSDClient::pongReceived); + client->sendFsdMessage("$POSERVER:BER368:90835991\r\n"); + + QCOMPARE(spy.count(), 1); + QList arguments = spy.takeFirst(); + QCOMPARE(arguments.size(), 2); + QCOMPARE(arguments.at(0).toString(), "SERVER"); + bool ok; + arguments.at(1).toDouble(&ok); + QVERIFY(ok); + } + + void CTestFSDClient::testClientResponseEmptyType() + { + client->sendFsdMessage("$CRDLH123:BER721::Jon Doe\r\n"); + } + + void CTestFSDClient::testClientResponseRealName1() + { + QSignalSpy spy(client, &FSDClient::realNameResponseReceived); + client->sendFsdMessage("$CRDLH123:BER721:RN:Jon Doe\r\n"); + + QCOMPARE(spy.count(), 1); + QList arguments = spy.takeFirst(); + QCOMPARE(arguments.size(), 2); + QCOMPARE(arguments.at(0).toString(), "DLH123"); + QCOMPARE(arguments.at(1).toString(), "Jon Doe"); + } + + void CTestFSDClient::testClientResponseRealName2() + { + QSignalSpy spy(client, &FSDClient::realNameResponseReceived); + client->sendFsdMessage("$CRDLH123:BER721:RN:Jon Doe:\r\n"); + + QCOMPARE(spy.count(), 1); + QList arguments = spy.takeFirst(); + QCOMPARE(arguments.size(), 2); + QCOMPARE(arguments.at(0).toString(), "DLH123"); + QCOMPARE(arguments.at(1).toString(), "Jon Doe"); + } + + void CTestFSDClient::testClientResponseRealName3() + { + QSignalSpy spy(client, &FSDClient::realNameResponseReceived); + client->sendFsdMessage("$CRDLH123:BER721:RN:Jon Doe::1\r\n"); + + QCOMPARE(spy.count(), 1); + QList arguments = spy.takeFirst(); + QCOMPARE(arguments.size(), 2); + QCOMPARE(arguments.at(0).toString(), "DLH123"); + QCOMPARE(arguments.at(1).toString(), "Jon Doe"); + } + + void CTestFSDClient::testClientResponseCapabilities() + { + QSignalSpy spy(client, &FSDClient::capabilityResponseReceived); + client->sendFsdMessage("$CRAUA64MN:DECHK:CAPS:VERSION=1:ATCINFO=1:MODELDESC=1:ACCONFIG=1\r\n"); + + QCOMPARE(spy.count(), 1); + QList arguments = spy.takeFirst(); + QCOMPARE(arguments.size(), 2); + CClient::Capabilities caps = arguments.at(1).value(); + QVERIFY(caps.testFlag(CClient::FsdWithAircraftConfig)); + } + + void CTestFSDClient::testPlaneInfoRequestFsinn() + { + QSignalSpy spy(client, &FSDClient::planeInformationFsinnReceived); + client->sendFsdMessage("#SBLHA449:DLH53M:FSIPIR:1::A320:10.05523:0.49785:1320.00000:2.AB13B127.5611C1A2::A320-200 Airbus Leipzig Air CFM\r\n"); + + QCOMPARE(spy.count(), 1); + QList arguments = spy.takeFirst(); + QCOMPARE(arguments.size(), 5); + /*QCOMPARE(arguments.at(0).toString(), "DLH123"); + QCOMPARE(arguments.at(1).toString(), "Jon Doe");*/ + } + + void CTestFSDClient::testPlaneInformationFsinn() + { + QSignalSpy spy(client, &FSDClient::planeInformationFsinnReceived); + client->sendFsdMessage("#SBLHA449:AUA89SY:FSIPI:1::A320:10.05523:0.49785:1320.00000:2.AB13B127.5611C1A2::A320-200 Airbus Leipzig Air CFM\r\n"); + + QCOMPARE(spy.count(), 1); + QList arguments = spy.takeFirst(); + QCOMPARE(arguments.size(), 5); +// QCOMPARE(arguments.at(0).toString(), "DLH123"); +// QCOMPARE(arguments.at(1).toString(), "Jon Doe"); + } + + void CTestFSDClient::testSendPilotLogin() + { + QSignalSpy spy(client, &FSDClient::rawFsdMessage); + + client->sendLogin(); + + QCOMPARE(spy.count(), 1); + QList arguments = spy.takeFirst(); + QCOMPARE(arguments.size(), 1); + CRawFsdMessage fsdMessage = arguments.at(0).value(); + QCOMPARE(fsdMessage.getRawMessage(), "FSD Sent=>#APABCD:SERVER:1234567:123456:1:100:14:Test User"); + } + + void CTestFSDClient::testSendAtcLogin() + { + QSignalSpy spy(client, &FSDClient::rawFsdMessage); + client->setLoginMode(CLoginMode::Observer); + client->setAtcRating(AtcRating::Controller1); + client->sendLogin(); + + QCOMPARE(spy.count(), 1); + QList arguments = spy.takeFirst(); + QCOMPARE(arguments.size(), 1); + CRawFsdMessage fsdMessage = arguments.at(0).value(); + QCOMPARE(fsdMessage.getRawMessage(), "FSD Sent=>#AAABCD:SERVER:Test User:1234567:123456:5:100"); + } + + void CTestFSDClient::testSendDeletePilot() + { + QSignalSpy spy(client, &FSDClient::rawFsdMessage); + + client->sendDeletePilot(); + + QCOMPARE(spy.count(), 1); + QList arguments = spy.takeFirst(); + QCOMPARE(arguments.size(), 1); + CRawFsdMessage fsdMessage = arguments.at(0).value(); + QCOMPARE(fsdMessage.getRawMessage(), "FSD Sent=>#DPABCD:1234567"); + } + + void CTestFSDClient::testSendDeleteAtc() + { + QSignalSpy spy(client, &FSDClient::rawFsdMessage); + + client->sendDeleteAtc(); + + QCOMPARE(spy.count(), 1); + QList arguments = spy.takeFirst(); + QCOMPARE(arguments.size(), 1); + CRawFsdMessage fsdMessage = arguments.at(0).value(); + QCOMPARE(fsdMessage.getRawMessage(), "FSD Sent=>#DAABCD:1234567"); + } + + void CTestFSDClient::testSendPilotDataUpdate1() + { + QSignalSpy spy(client, &FSDClient::rawFsdMessage); + + const Geo::CCoordinateGeodetic ownPosition(48.353855, 11.786155); + const CHeading heading(25.0, CAngleUnit::deg()); + const CAngle pitch(0.0, CAngleUnit::deg()); + const CAngle bank(0.0, CAngleUnit::deg()); + const CSpeed gs(0.0, CSpeedUnit::kts()); + CAircraftSituation situation; + situation.setPosition(ownPosition); + situation.setAltitude(CAltitude(110, CAltitude::MeanSeaLevel, CLengthUnit::ft())); + situation.setPressureAltitude(CAltitude(111, CAltitude::MeanSeaLevel, CAltitude::PressureAltitude, CLengthUnit::ft())); + situation.setGroundSpeed(gs); + situation.setPitch(pitch); + situation.setBank(bank); + situation.setHeading(heading); + COwnAircraftProviderDummy::instance()->updateOwnSituation(situation); + COwnAircraftProviderDummy::instance()->updateCockpit({}, {}, CTransponder(1200, CTransponder::ModeC), {}); + + client->sendPilotDataUpdate(); + + QCOMPARE(spy.count(), 1); + QList arguments = spy.takeFirst(); + QCOMPARE(arguments.size(), 1); + CRawFsdMessage fsdMessage = arguments.at(0).value(); + QCOMPARE(fsdMessage.getRawMessage(), "FSD Sent=>@N:ABCD:1200:0:48.35386:11.78616:110:0:4294963484:1"); + } + + void CTestFSDClient::testSendPilotDataUpdate2() + { + QSignalSpy spy(client, &FSDClient::rawFsdMessage); + + const Geo::CCoordinateGeodetic ownPosition(48.353855, 11.786155); + const CHeading heading(25.0, CAngleUnit::deg()); + const CAngle pitch(0.0, CAngleUnit::deg()); + const CAngle bank(0.0, CAngleUnit::deg()); + const CSpeed gs(0.0, CSpeedUnit::kts()); + CAircraftSituation situation; + situation.setPosition(ownPosition); + situation.setAltitude(CAltitude(110, CAltitude::MeanSeaLevel, CLengthUnit::ft())); + situation.setPressureAltitude(CAltitude(111, CAltitude::MeanSeaLevel, CAltitude::PressureAltitude, CLengthUnit::ft())); + situation.setGroundSpeed(gs); + situation.setPitch(pitch); + situation.setBank(bank); + situation.setHeading(heading); + COwnAircraftProviderDummy::instance()->updateOwnSituation(situation); + COwnAircraftProviderDummy::instance()->updateCockpit({}, {}, CTransponder(1200, CTransponder::ModeC), {}); + + client->sendPilotDataUpdate(); + + QCOMPARE(spy.count(), 1); + QList arguments = spy.takeFirst(); + QCOMPARE(arguments.size(), 1); + CRawFsdMessage fsdMessage = arguments.at(0).value(); + QCOMPARE(fsdMessage.getRawMessage(), "FSD Sent=>@N:ABCD:1200:0:48.35386:11.78616:110:0:4294963484:1"); + } + + void CTestFSDClient::testSendAtcDataUpdate() + { + QSignalSpy spy(client, &FSDClient::rawFsdMessage); + client->sendAtcDataUpdate(48.11028, 8.56972); + + QCOMPARE(spy.count(), 1); + QList arguments = spy.takeFirst(); + QCOMPARE(arguments.size(), 1); + CRawFsdMessage fsdMessage = arguments.at(0).value(); + QCOMPARE(fsdMessage.getRawMessage(), "FSD Sent=>%ABCD:99998:0:10:1:48.11028:8.56972:0"); + } + + void CTestFSDClient::testSendPing() + { + QSignalSpy spy(client, &FSDClient::rawFsdMessage); + client->sendPing("SERVER"); + + QCOMPARE(spy.count(), 1); + QList arguments = spy.takeFirst(); + QCOMPARE(arguments.size(), 1); + CRawFsdMessage fsdMessage = arguments.at(0).value(); + QVERIFY(fsdMessage.getRawMessage().contains("FSD Sent=>$PIABCD:SERVER:")); + } + + void CTestFSDClient::testSendPong() + { + QSignalSpy spy(client, &FSDClient::rawFsdMessage); + client->sendPong("SERVER", "123456789"); + + QCOMPARE(spy.count(), 1); + QList arguments = spy.takeFirst(); + QCOMPARE(arguments.size(), 1); + CRawFsdMessage fsdMessage = arguments.at(0).value(); + QCOMPARE(fsdMessage.getRawMessage(), "FSD Sent=>$POABCD:SERVER:123456789"); + } + + void CTestFSDClient::testSendClientResponse1() + { + QSignalSpy spy(client, &FSDClient::rawFsdMessage); + client->sendClientResponse(ClientQueryType::RealName, "ZZZZ_TWR"); + + QCOMPARE(spy.count(), 1); + QList arguments = spy.takeFirst(); + QCOMPARE(arguments.size(), 1); + CRawFsdMessage fsdMessage = arguments.at(0).value(); + QCOMPARE(fsdMessage.getRawMessage(), "FSD Sent=>$CRABCD:ZZZZ_TWR:RN:Test User::1"); + } + + void CTestFSDClient::testSendClientResponse2() + { + QSignalSpy spy(client, &FSDClient::rawFsdMessage); + client->sendClientResponse(ClientQueryType::Capabilities, "ZZZZ_TWR"); + + QCOMPARE(spy.count(), 1); + QList arguments = spy.takeFirst(); + QCOMPARE(arguments.size(), 1); + CRawFsdMessage fsdMessage = arguments.at(0).value(); + QCOMPARE(fsdMessage.getRawMessage(), "FSD Sent=>$CRABCD:ZZZZ_TWR:CAPS:ATCINFO=1:MODELDESC=1:ACCONFIG=1"); + } + + void CTestFSDClient::testSendClientQuery1() + { + QSignalSpy spy(client, &FSDClient::rawFsdMessage); + client->sendClientQuery(ClientQueryType::RealName, "ZZZZ_TWR"); + + QCOMPARE(spy.count(), 1); + QList arguments = spy.takeFirst(); + QCOMPARE(arguments.size(), 1); + CRawFsdMessage fsdMessage = arguments.at(0).value(); + QCOMPARE(fsdMessage.getRawMessage(), "FSD Sent=>$CQABCD:ZZZZ_TWR:RN"); + } + + void CTestFSDClient::testSendClientQuery2() + { + QSignalSpy spy(client, &FSDClient::rawFsdMessage); + client->sendClientQueryIsValidAtc("EDDM_TWR"); + + QCOMPARE(spy.count(), 1); + QList arguments = spy.takeFirst(); + QCOMPARE(arguments.size(), 1); + CRawFsdMessage fsdMessage = arguments.at(0).value(); + QCOMPARE(fsdMessage.getRawMessage(), "FSD Sent=>$CQABCD:SERVER:ATC:EDDM_TWR"); + } + + void CTestFSDClient::testSendClientQuery3() + { + QSignalSpy spy(client, &FSDClient::rawFsdMessage); + client->sendClientQueryAircraftConfig("DLH123"); + + QCOMPARE(spy.count(), 1); + QList arguments = spy.takeFirst(); + QCOMPARE(arguments.size(), 1); + CRawFsdMessage fsdMessage = arguments.at(0).value(); + QCOMPARE(fsdMessage.getRawMessage(), "FSD Sent=>$CQABCD:DLH123:ACC:{\"request\":\"full\"}"); + } + + void CTestFSDClient::testSendTextMessage1() + { + QSignalSpy spy(client, &FSDClient::rawFsdMessage); + client->sendTextMessage("ZZZZ_TWR", "hey dude!"); + + QCOMPARE(spy.count(), 1); + QList arguments = spy.takeFirst(); + QCOMPARE(arguments.size(), 1); + CRawFsdMessage fsdMessage = arguments.at(0).value(); + QCOMPARE(fsdMessage.getRawMessage(), "FSD Sent=>#TMABCD:ZZZZ_TWR:hey dude!"); + } + + void CTestFSDClient::testSendTextMessage2() + { + QSignalSpy spy(client, &FSDClient::rawFsdMessage); + client->sendTextMessage(TextMessageGroups::AllSups, "Please help!!!"); + + QCOMPARE(spy.count(), 1); + QList arguments = spy.takeFirst(); + QCOMPARE(arguments.size(), 1); + CRawFsdMessage fsdMessage = arguments.at(0).value(); + QCOMPARE(fsdMessage.getRawMessage(), "FSD Sent=>#TMABCD:*S:Please help!!!"); + } + + void CTestFSDClient::testSendRadioMessage1() + { + QSignalSpy spy(client, &FSDClient::rawFsdMessage); + QVector frequencies { 124050 }; + client->sendRadioMessage(frequencies, "hey dude!"); + + QCOMPARE(spy.count(), 1); + QList arguments = spy.takeFirst(); + QCOMPARE(arguments.size(), 1); + CRawFsdMessage fsdMessage = arguments.at(0).value(); + QCOMPARE(fsdMessage.getRawMessage(), "FSD Sent=>#TMABCD:@24050:hey dude!"); + } + + void CTestFSDClient::testSendRadioMessage2() + { + QSignalSpy spy(client, &FSDClient::rawFsdMessage); + QVector frequencies { 124050, 135725 }; + client->sendRadioMessage(frequencies, "hey dude!"); + + QCOMPARE(spy.count(), 1); + QList arguments = spy.takeFirst(); + QCOMPARE(arguments.size(), 1); + CRawFsdMessage fsdMessage = arguments.at(0).value(); + QCOMPARE(fsdMessage.getRawMessage(), "FSD Sent=>#TMABCD:@24050&@35725:hey dude!"); + } + + void CTestFSDClient::testSendFlightPlan() + { + QSignalSpy spy(client, &FSDClient::rawFsdMessage); + QDateTime takeoffTimePlanned = QDateTime::fromString("1530", "hhmm"); + takeoffTimePlanned.setTimeSpec(Qt::UTC); + QDateTime takeoffTimeActual = QDateTime::fromString("1535", "hhmm"); + takeoffTimeActual.setTimeSpec(Qt::UTC); + CAltitude flightLevel(35000, CAltitude::FlightLevel, CLengthUnit::ft()); + CFlightPlan fp({}, "B744", "EGLL", "KORD", "NONE", + takeoffTimePlanned, takeoffTimeActual, + CTime(8.25, CTimeUnit::h()), CTime(9.5, CTimeUnit::h()), + flightLevel, CSpeed(420, CSpeedUnit::kts()), CFlightPlan::VFR, + "EGLL.KORD", "Unit Test"); + client->sendFlightPlan(fp); + + QCOMPARE(spy.count(), 1); + QList arguments = spy.takeFirst(); + QCOMPARE(arguments.size(), 1); + CRawFsdMessage fsdMessage = arguments.at(0).value(); + QCOMPARE(fsdMessage.getRawMessage(), "FSD Sent=>$FPABCD:SERVER:V:B744:420:EGLL:1530:1535:FL350:KORD:8:15:9:30:NONE:UNIT TEST:EGLL.KORD"); + } + + void CTestFSDClient::testSendPlaneInfoRequest() + { + QSignalSpy spy(client, &FSDClient::rawFsdMessage); + client->sendPlaneInfoRequest("XYZ"); + + QCOMPARE(spy.count(), 1); + QList arguments = spy.takeFirst(); + QCOMPARE(arguments.size(), 1); + CRawFsdMessage fsdMessage = arguments.at(0).value(); + QCOMPARE(fsdMessage.getRawMessage(), "FSD Sent=>#SBABCD:XYZ:PIR"); + } + + void CTestFSDClient::testSendPlaneInformation1() + { + QSignalSpy spy(client, &FSDClient::rawFsdMessage); + client->sendPlaneInformation("XYZ", "B744", "BAW"); + + QCOMPARE(spy.count(), 1); + QList arguments = spy.takeFirst(); + QCOMPARE(arguments.size(), 1); + CRawFsdMessage fsdMessage = arguments.at(0).value(); + QCOMPARE(fsdMessage.getRawMessage(), "FSD Sent=>#SBABCD:XYZ:PI:GEN:EQUIPMENT=B744:AIRLINE=BAW"); + } + + void CTestFSDClient::testSendPlaneInformation2() + { + QSignalSpy spy(client, &FSDClient::rawFsdMessage); + client->sendPlaneInformation("XYZ", "B744", "BAW", "UNION"); + + QCOMPARE(spy.count(), 1); + QList arguments = spy.takeFirst(); + QCOMPARE(arguments.size(), 1); + CRawFsdMessage fsdMessage = arguments.at(0).value(); + QCOMPARE(fsdMessage.getRawMessage(), "FSD Sent=>#SBABCD:XYZ:PI:GEN:EQUIPMENT=B744:AIRLINE=BAW:LIVERY=UNION"); + } + + void CTestFSDClient::testSendPlaneInformation3() + { + QSignalSpy spy(client, &FSDClient::rawFsdMessage); + client->sendPlaneInformation("XYZ", "B744"); + + QCOMPARE(spy.count(), 1); + QList arguments = spy.takeFirst(); + QCOMPARE(arguments.size(), 1); + CRawFsdMessage fsdMessage = arguments.at(0).value(); + QCOMPARE(fsdMessage.getRawMessage(), "FSD Sent=>#SBABCD:XYZ:PI:GEN:EQUIPMENT=B744"); + } + + void CTestFSDClient::testSendPlaneInformation4() + { + QSignalSpy spy(client, &FSDClient::rawFsdMessage); + client->sendPlaneInformation("XYZ", "", "", "UNION"); + + QCOMPARE(spy.count(), 1); + QList arguments = spy.takeFirst(); + QCOMPARE(arguments.size(), 1); + CRawFsdMessage fsdMessage = arguments.at(0).value(); + QCOMPARE(fsdMessage.getRawMessage(), "FSD Sent=>#SBABCD:XYZ:PI:GEN:LIVERY=UNION"); + } + + void CTestFSDClient::testSendAircraftConfiguration() + { + QSignalSpy spy(client, &FSDClient::rawFsdMessage); + client->sendAircraftConfiguration("XYZ", "{\"request\":\"full\"}"); + + QCOMPARE(spy.count(), 1); + QList arguments = spy.takeFirst(); + QCOMPARE(arguments.size(), 1); + CRawFsdMessage fsdMessage = arguments.at(0).value(); + QCOMPARE(fsdMessage.getRawMessage(), "FSD Sent=>$CQABCD:XYZ:ACC:{\"request\":\"full\"}"); + } + + void CTestFSDClient::testCom1FreqQueryResponse() + { + QSignalSpy spy(client, &FSDClient::rawFsdMessage); + client->sendFsdMessage("$CQEDMM_CTR:ABCD:C?\r\n"); + + QCOMPARE(spy.count(), 2); + QList arguments = spy.takeAt(1); + QCOMPARE(arguments.size(), 1); + CRawFsdMessage fsdMessage = arguments.at(0).value(); + QCOMPARE(fsdMessage.getRawMessage(), "FSD Sent=>$CRABCD:EDMM_CTR:C?:123.000"); + } + + void CTestFSDClient::testPlaneInfoRequestResponse() + { + QSignalSpy spy(client, &FSDClient::rawFsdMessage); + client->sendFsdMessage("#SBEDMM_CTR:ABCD:PIR\r\n"); + + QCOMPARE(spy.count(), 2); + QList arguments = spy.takeAt(1); + QCOMPARE(arguments.size(), 1); + CRawFsdMessage fsdMessage = arguments.at(0).value(); + QCOMPARE(fsdMessage.getRawMessage(), "FSD Sent=>#SBABCD:EDMM_CTR:PI:GEN:EQUIPMENT=B737:AIRLINE=BER"); + } + + void CTestFSDClient::testAuth() + { + quint16 m_clientId = 0x82b0; + QString m_privateKey("52d9343020e9c7d0c6b04b0cca20ad3b"); + QString initialChallenge("a054064f45cb6d6a6f1345"); + + vatsim_auth *auth = vatsim_auth_create(m_clientId, qPrintable(m_privateKey)); + vatsim_auth_set_initial_challenge(auth, qPrintable(initialChallenge)); + + QString challenge("0b8244efa2bd0f6da0"); + char buffer[33]; + vatsim_auth_generate_response(auth, qPrintable(challenge), buffer); + QString response(buffer); + QCOMPARE(response, "df00748db5ec02ea416ab79b441a88f7"); + + challenge = "6d1beed4fa142b9b5567c0"; + vatsim_auth_generate_response(auth, qPrintable(challenge), buffer); + response = QString(buffer); + QCOMPARE(response, "5d7e48df0ff0f52b268d4e23d32483c2"); + + vatsim_auth_generate_challenge(auth, buffer); + QVERIFY(QString(buffer).length() > 0); + + char sysuid[50]; + vatsim_get_system_unique_id(sysuid); + qDebug() << sysuid; + } + + bool pingServer(const CServer &server) + { + QString m; + const CUrl url(server.getAddress(), server.getPort()); + if (!CNetworkUtils::canConnect(url, m, 2500)) + { + qWarning() << "Skipping unit test as" << url.getFullUrl() << "cannot be connected"; + return false; + } + return true; + } + + void CTestFSDClient::testConnection() + { + const CServer fsdServer = CServer::swiftFsdTestServer(true); + if (!pingServer(fsdServer)) { QSKIP("Server not reachable."); } + + QSignalSpy spy(client, &FSDClient::connectionStatusChanged); + client->setUnitTestMode(false); + client->connectToServer(); + + if (spy.isEmpty()) { QVERIFY(spy.wait()); } + QList arguments = spy.takeAt(0); + QCOMPARE(arguments.size(), 2); + QCOMPARE(CConnectionStatus::Disconnected, arguments.at(0).value().getConnectionStatus()); + QCOMPARE(CConnectionStatus::Connecting, arguments.at(1).value().getConnectionStatus()); + + if (spy.isEmpty()) { QVERIFY(spy.wait()); } + arguments = arguments = spy.takeAt(0); + QCOMPARE(arguments.size(), 2); + QCOMPARE(CConnectionStatus::Connecting, arguments.at(0).value().getConnectionStatus()); + QCOMPARE(CConnectionStatus::Connected, arguments.at(1).value().getConnectionStatus()); + + QSignalSpy pongSpy(client, &FSDClient::pongReceived); + connect(client, &FSDClient::pongReceived, [](const QString &sender, double elapsedTimeM) + { + qDebug() << "Received pong from" << sender << "in" << elapsedTimeM << "ms"; + }); + client->sendPing("SERVER"); + QVERIFY(pongSpy.wait()); + + client->disconnectFromServer(); + if (spy.isEmpty()) { QVERIFY(spy.wait()); } + arguments = spy.takeAt(0); + QCOMPARE(arguments.size(), 2); + QCOMPARE(CConnectionStatus::Connected, arguments.at(0).value().getConnectionStatus()); + QCOMPARE(CConnectionStatus::Disconnecting, arguments.at(1).value().getConnectionStatus()); + + if (spy.isEmpty()) { QVERIFY(spy.wait()); } + arguments = spy.takeAt(0); + QCOMPARE(arguments.size(), 2); + QCOMPARE(CConnectionStatus::Disconnecting, arguments.at(0).value().getConnectionStatus()); + QCOMPARE(CConnectionStatus::Disconnected, arguments.at(1).value().getConnectionStatus()); + } +} + +//! main +BLACKTEST_MAIN(BlackFsdTest::CTestFSDClient); + +#include "testfsdclient.moc" + +//! \endcond diff --git a/tests/blackcore/vatsim/testnetwork/testnetwork.pro b/tests/blackcore/fsd/testfsdclient/testfsdclient.pro similarity index 73% rename from tests/blackcore/vatsim/testnetwork/testnetwork.pro rename to tests/blackcore/fsd/testfsdclient/testfsdclient.pro index efa5dd824..bdf09e93b 100644 --- a/tests/blackcore/vatsim/testnetwork/testnetwork.pro +++ b/tests/blackcore/fsd/testfsdclient/testfsdclient.pro @@ -1,8 +1,8 @@ load(common_pre) -QT += core dbus network testlib +QT += core network dbus testlib -TARGET = testnetwork +TARGET = testfsdclient CONFIG -= app_bundle CONFIG += blackconfig CONFIG += blackmisc @@ -21,12 +21,9 @@ INCLUDEPATH += \ $$SourceRoot/src \ $$SourceRoot/tests \ -HEADERS += \ - expect.h +SOURCES += testfsdclient.cpp -SOURCES += \ - testnetwork.cpp \ - expect.cpp +LIBS *= -lvatsimauth DESTDIR = $$DestRoot/bin diff --git a/tests/blackcore/fsd/testfsdmessages/testfsdmessages.cpp b/tests/blackcore/fsd/testfsdmessages/testfsdmessages.cpp new file mode 100644 index 000000000..6f35624c2 --- /dev/null +++ b/tests/blackcore/fsd/testfsdmessages/testfsdmessages.cpp @@ -0,0 +1,611 @@ +/* Copyright (C) 2018 + * swift Project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project, + * including this file, may be copied, modified, propagated, or distributed except according to the terms + * contained in the LICENSE file. + */ + +//! \cond PRIVATE_TESTS + +/*! +* \file +* \ingroup testblackfsd +*/ + +#include "blackconfig/buildconfig.h" +#include "blackcore/fsd/addatc.h" +#include "blackcore/fsd/addpilot.h" +#include "blackcore/fsd/atcdataupdate.h" +#include "blackcore/fsd/authchallenge.h" +#include "blackcore/fsd/authresponse.h" +#include "blackcore/fsd/clientidentification.h" +#include "blackcore/fsd/deleteatc.h" +#include "blackcore/fsd/deletepilot.h" +#include "blackcore/fsd/pbh.h" +#include "blackcore/fsd/pilotdataupdate.h" +#include "blackcore/fsd/ping.h" +#include "blackcore/fsd/pong.h" +#include "blackcore/fsd/killrequest.h" +#include "blackcore/fsd/textmessage.h" +#include "blackcore/fsd/clientquery.h" +#include "blackcore/fsd/clientresponse.h" +#include "blackcore/fsd/flightplan.h" +#include "blackcore/fsd/fsdidentification.h" +#include "blackcore/fsd/serializer.h" +#include "blackcore/fsd/servererror.h" +#include "blackcore/fsd/interimpilotdataupdate.h" +#include "blackcore/fsd/planeinforequest.h" +#include "blackcore/fsd/planeinformation.h" +#include "blackcore/fsd/planeinforequestfsinn.h" +#include "blackcore/fsd/planeinformationfsinn.h" +#include "blackcore/fsd/enums.h" +#include "test.h" + +#include +#include + +using namespace BlackMisc::Aviation; +using namespace BlackMisc::Network; +using namespace BlackConfig; +using namespace BlackCore::Fsd; + +namespace BlackMiscTest +{ + //! Testing FSD Messages + class CTestFsdMessages : public QObject + { + Q_OBJECT + + public: + //! Constructor + explicit CTestFsdMessages(QObject *parent = nullptr) : QObject(parent) {} + + //! Destructor + virtual ~CTestFsdMessages() {} + + private slots: + void testAddAtc(); + void testAddPilot(); + void testAtcDataUpdate(); + void testAuthChallenge(); + void testAuthResponse(); + void testClientIdentification(); + void testClientQuery(); + void testClientResponse(); + void testDeleteAtc(); + void testDeletePilot(); + void testFlightPlan(); + void testFSDIdentification(); + void testInterimPilotDataUpdate(); + void testKillRequest(); + void testPBH(); + void testPilotDataUpdate(); + void testPing(); + void testPlaneInfoRequest(); + void testPlaneInformation(); + void testPlaneInfoRequestFsinn(); + void testPlaneInformationFsinn(); + void testPong(); + void testServerError(); + void testTextMessage(); + }; + + void CTestFsdMessages::testAddAtc() + { + const AddAtc message("ABCD", "Jon Doe", "1234567", "1234567", AtcRating::Student3, 100); + + QCOMPARE(message.sender(), "ABCD"); + QCOMPARE(message.receiver(), "SERVER"); + QCOMPARE(message.cid(), "1234567"); + QCOMPARE(message.password(), "1234567"); + QCOMPARE(message.rating(), AtcRating::Student3); + QCOMPARE(message.protocolRevision(), 100); + QCOMPARE(message.realName(), "Jon Doe"); + + QString stringRef("ABCD:SERVER:Jon Doe:1234567:1234567:4:100"); + QString str = message.toTokens().join(":"); + QCOMPARE(str, stringRef); + + QStringList tokens = QString("ABCD:SERVER:Jon Doe:1234567:1234567:4:100").split(':'); + const AddAtc messageFromTokens = AddAtc::fromTokens(tokens); + QCOMPARE(messageFromTokens, message); + } + + void CTestFsdMessages::testAddPilot() + { + const AddPilot message("ABCD", "1234567", "1234567", PilotRating::Student, 100, SimType::MSFS95, "Jon Doe"); + QCOMPARE(message.sender(), QString("ABCD")); + QCOMPARE(message.receiver(), QString("SERVER")); + QCOMPARE(message.cid(), QString("1234567")); + QCOMPARE(message.password(), QString("1234567")); + QCOMPARE(message.rating(), PilotRating::Student); + QCOMPARE(message.protocolVersion(), 100); + QCOMPARE(message.realName(), QString("Jon Doe")); + QCOMPARE(message.simType(), SimType::MSFS95); + + QString stringRef("ABCD:SERVER:1234567:1234567:1:100:1:Jon Doe"); + QString str = message.toTokens().join(":"); + QCOMPARE(str, stringRef); + + QStringList tokens = QString("ABCD:SERVER:1234567:1234567:1:100:1:Jon Doe").split(':'); + const AddPilot messageFromTokens = AddPilot::fromTokens(tokens); + QCOMPARE(messageFromTokens, message); + } + + void CTestFsdMessages::testAtcDataUpdate() + { + const AtcDataUpdate message("ABCD", 128200, CFacilityType::APP, 145, AtcRating::Controller1, 48.11028, 8.56972, 100); + QCOMPARE(message.sender(), QString("ABCD")); + QCOMPARE(message.receiver(), QString("")); + QCOMPARE(message.m_frequencykHz, 128200); + QCOMPARE(message.m_facility, CFacilityType::APP); + QCOMPARE(message.m_visibleRange, 145); + QCOMPARE(message.m_rating, AtcRating::Controller1); + QCOMPARE(message.m_latitude, 48.11028); + QCOMPARE(message.m_longitude, 8.56972); + QCOMPARE(message.m_elevation, 100); + + QString stringRef("ABCD:28200:5:145:5:48.11028:8.56972:100"); + QString str = message.toTokens().join(":"); + QCOMPARE(str, stringRef); + + const AtcDataUpdate reference("ABCD", 128200, CFacilityType::APP, 145, AtcRating::Controller1, 48.11028, 8.56972, 100); + + QStringList tokens = QString("ABCD:28200:5:145:5:48.11028:8.56972:100").split(':'); + const AtcDataUpdate messageFromTokens = AtcDataUpdate::fromTokens(tokens); + QCOMPARE(reference, messageFromTokens); + } + + void CTestFsdMessages::testAuthChallenge() + { + const AuthChallenge message("ABCD", "SERVER", "7a57f2dd9d360d347b"); + QCOMPARE(message.sender(), QString("ABCD")); + QCOMPARE(message.receiver(), QString("SERVER")); + QCOMPARE(QString("7a57f2dd9d360d347b"), message.m_challengeKey); + + QString stringRef("ABCD:SERVER:7a57f2dd9d360d347b"); + QString str = message.toTokens().join(":"); + QCOMPARE(str, stringRef); + + QStringList tokens = QString("ABCD:SERVER:7a57f2dd9d360d347b").split(':'); + const AuthChallenge messageFromTokens = AuthChallenge::fromTokens(tokens); + QCOMPARE(messageFromTokens, message); + } + + void CTestFsdMessages::testAuthResponse() + { + const AuthResponse message("ABCD", "SERVER", "7a57f2dd9d360d347b"); + QCOMPARE(message.sender(), QString("ABCD")); + QCOMPARE(message.receiver(), QString("SERVER")); + QCOMPARE(QString("7a57f2dd9d360d347b"), message.m_response); + + QString stringRef("ABCD:SERVER:7a57f2dd9d360d347b"); + QString str = message.toTokens().join(":"); + QCOMPARE(str, stringRef); + + const AuthResponse reference("ABCD", "SERVER", "7a57f2dd9d360d347b"); + + QStringList tokens = QString("ABCD:SERVER:7a57f2dd9d360d347b").split(':'); + const AuthResponse messageFromTokens = AuthResponse::fromTokens(tokens); + QCOMPARE(messageFromTokens, message); + + } + + void CTestFsdMessages::testClientIdentification() + { + const ClientIdentification message("ABCD", 0xe410, "Client", 1, 5, "1234567", "1108540872", "29bbc8b1398eb38e0139"); + QCOMPARE(message.sender(), QString("ABCD")); + QCOMPARE(message.receiver(), QString("SERVER")); + QCOMPARE(0xe410, message.m_clientId); + QCOMPARE("Client", message.m_clientName); + QCOMPARE(1, message.m_clientVersionMajor); + QCOMPARE(5, message.m_clientVersionMinor); + QCOMPARE(QString("1234567"), message.m_userCid); + QCOMPARE(QString("1108540872"), message.m_sysUid); + QCOMPARE(QString("29bbc8b1398eb38e0139"), message.m_initialChallenge); + + QString stringRef("ABCD:SERVER:e410:Client:1:5:1234567:1108540872:29bbc8b1398eb38e0139"); + QString str = message.toTokens().join(":"); + QCOMPARE(str, stringRef); + + const ClientIdentification reference("ABCD", 0xe410, "Client", 1, 5, "1234567", "1108540872", "29bbc8b1398eb38e0139"); + + QStringList tokens = QString("ABCD:SERVER:e410:Client:1:5:1234567:1108540872:29bbc8b1398eb38e0139").split(':'); + const ClientIdentification messageFromTokens = ClientIdentification::fromTokens(tokens); + QCOMPARE(messageFromTokens, message); + } + + void CTestFsdMessages::testClientQuery() + { +// const ClientQuery message("ABCD", "@94835", Client::WhoIsTracking); +// QCOMPARE(message.sender(), QString("ABCD")); +// QCOMPARE(QString("@94835"), message.receiver()); +// QCOMPARE(ClientQueryType::WhoIsTracking, message.m_queryType); +// QCOMPARE(QStringList(), message.m_payload); + +// const ClientQuery pdu2("ABCD", "@94835", ClientQueryType::WhoIsTracking, {"LHA449"}); +// QCOMPARE(QString("ABCD"), pdu2.sender()); +// QCOMPARE(QString("@94835"), pdu2.receiver()); +// QCOMPARE(ClientQueryType::WhoIsTracking, pdu2.m_queryType); +// QCOMPARE(QStringList {"LHA449"}, pdu2.m_payload); + +// const ClientQuery message("ABCD", "@94835", ClientQueryType::WhoIsTracking, {"LHA449"}); +// QString stringRef("ABCD:@94835:WH:LHA449"); +// QString str = message.toTokens().join(":"); +// QCOMPARE(str, stringRef); + +// const ClientQuery reference("ABCD", "@94835", ClientQueryType::WhoIsTracking, {"LHA449"}); + +// QStringList tokens = QString("ABCD:@94835:WH:LHA449").split(':'); +// const ClientQuery messageFromTokens = ClientQuery::fromTokens(tokens); +// QCOMPARE(messageFromTokens, message); + } + + void CTestFsdMessages::testClientResponse() + { + const ClientResponse message("ABCD", "SERVER", ClientQueryType::Capabilities, {"MODELDESC=1","ATCINFO=1"}); + QCOMPARE(message.sender(), QString("ABCD")); + QCOMPARE(message.receiver(), QString("SERVER")); + QCOMPARE(ClientQueryType::Capabilities, message.m_queryType); + QStringList reference {"MODELDESC=1","ATCINFO=1"}; + QCOMPARE(reference, message.m_responseData); + + QString stringRef("ABCD:SERVER:CAPS:MODELDESC=1:ATCINFO=1"); + auto str = message.toTokens().join(":"); + QCOMPARE(str, stringRef); + + QStringList tokens = QString("ABCD:SERVER:CAPS:MODELDESC=1:ATCINFO=1").split(':'); + auto messageFromTokens = ClientResponse::fromTokens(tokens); + QCOMPARE(messageFromTokens, message); + } + + void CTestFsdMessages::testDeleteAtc() + { + const DeleteAtc message("ABCD", "1234567"); + + QCOMPARE(message.sender(), QString("ABCD")); + QCOMPARE(QString(""), message.receiver()); + QCOMPARE(QString("1234567"), message.m_cid); + + QString stringRef("ABCD:1234567"); + QString str = message.toTokens().join(":"); + QCOMPARE(str, stringRef); + + QStringList tokens = QString("ABCD:1234567").split(':'); + auto messageFromTokens = DeleteAtc::fromTokens(tokens); + QCOMPARE(messageFromTokens, message); + } + + void CTestFsdMessages::testDeletePilot() + { + const DeletePilot message("ABCD", "1234567"); + + QCOMPARE(message.sender(), QString("ABCD")); + QCOMPARE(QString(""), message.receiver()); + QCOMPARE(QString("1234567"), message.m_cid); + + QString stringRef("ABCD:1234567"); + QString str = message.toTokens().join(":"); + QCOMPARE(str, stringRef); + + QStringList tokens = QString("ABCD:1234567").split(':'); + auto messageFromTokens = DeletePilot::fromTokens(tokens); + QCOMPARE(messageFromTokens, message); + + } + + void CTestFsdMessages::testFlightPlan() + { + const FlightPlan message("ABCD", "SERVER", FlightType::VFR, "B744", 420, "EGLL", 1530, 1535, "FL350", "KORD", 8, 15, + 9, 30, "NONE", "Unit Test", "EGLL.KORD"); + + QCOMPARE(message.sender(), QString("ABCD")); + QCOMPARE(message.receiver(), QString("SERVER")); + QCOMPARE(FlightType::VFR, message.m_flightType); + QCOMPARE(QString("B744"), message.m_aircraftIcaoType); + QCOMPARE(420, message.m_trueCruisingSpeed); + QCOMPARE(QString("EGLL"), message.m_depAirport); + QCOMPARE(1530, message.m_estimatedDepTime); + QCOMPARE(1535, message.m_actualDepTime); + QCOMPARE(QString("FL350"), message.m_cruiseAlt); + QCOMPARE(QString("KORD"), message.m_destAirport); + QCOMPARE(8, message.m_hoursEnroute); + QCOMPARE(15, message.m_minutesEnroute); + QCOMPARE(9, message.m_fuelAvailHours); + QCOMPARE(30, message.m_fuelAvailMinutes); + QCOMPARE(QString("NONE"), message.m_altAirport); + QCOMPARE(QString("Unit Test"), message.m_remarks); + QCOMPARE(QString("EGLL.KORD"), message.m_route); + + QString stringRef("ABCD:SERVER:V:B744:420:EGLL:1530:1535:FL350:KORD:8:15:9:30:NONE:Unit Test:EGLL.KORD"); + QString str = message.toTokens().join(":"); + QCOMPARE(str, stringRef); + + QStringList tokens = QString("ABCD:SERVER:V:B744:420:EGLL:1530:1535:FL350:KORD:8:15:9:30:NONE:Unit Test:EGLL.KORD").split(':'); + auto messageFromTokens = FlightPlan::fromTokens(tokens); + QCOMPARE(messageFromTokens, message); + } + + void CTestFsdMessages::testFSDIdentification() + { + + } + + void CTestFsdMessages::testInterimPilotDataUpdate() + { + const InterimPilotDataUpdate message("ABCD", "XYZ", 43.12578, -72.15841, 12008, 400, -2, 3, 280, true); + + QCOMPARE(message.sender(), QString("ABCD")); + QCOMPARE(QString("XYZ"), message.receiver()); + QCOMPARE(43.12578, message.m_latitude); + QCOMPARE(-72.15841, message.m_longitude); + QCOMPARE(12008, message.m_altitudeTrue); + QCOMPARE(400, message.m_groundSpeed); + QCOMPARE(-2, message.m_pitch); + QCOMPARE(3, message.m_bank); + QCOMPARE(280, message.m_heading); + QCOMPARE(true, message.m_onGround); + + QString stringRef("ABCD:XYZ:VI:43.12578:-72.15841:12008:400:25132146"); + QString str = message.toTokens().join(":"); + QCOMPARE(str, stringRef); + + QStringList tokens = QString("ABCD:XYZ:VI:43.12578:-72.15841:12008:400:25132146").split(':'); + auto messageFromTokens = InterimPilotDataUpdate::fromTokens(tokens); + QCOMPARE(QString("ABCD"), messageFromTokens.sender()); + QCOMPARE(QString("XYZ"), messageFromTokens.receiver()); + QCOMPARE(43.12578, messageFromTokens.m_latitude); + QCOMPARE(-72.15841, messageFromTokens.m_longitude); + QCOMPARE(12008, messageFromTokens.m_altitudeTrue); + QCOMPARE(400, messageFromTokens.m_groundSpeed); + QVERIFY(message.m_pitch - messageFromTokens.m_pitch < 1.0); + QVERIFY(message.m_bank - messageFromTokens.m_bank < 1.0); + QVERIFY(message.m_heading - messageFromTokens.m_heading < 1.0); + QCOMPARE(messageFromTokens.m_onGround, true); + } + + void CTestFsdMessages::testKillRequest() + { + const KillRequest message("SUP", "ABCD", "I don't like you!"); + + QCOMPARE(QString("SUP"), message.sender()); + QCOMPARE(QString("ABCD"), message.receiver()); + QCOMPARE(QString("I don't like you!"), message.m_reason); + + QString stringRef("SUP:ABCD:I don't like you!"); + QString str = message.toTokens().join(":"); + QCOMPARE(str, stringRef); + + QStringList tokens = QString("SUP:ABCD:I don't like you!").split(':'); + auto messageFromTokens = KillRequest::fromTokens(tokens); + QCOMPARE(messageFromTokens, message); + } + + void CTestFsdMessages::testPBH() + { +// for (int pitch = -90; pitch < 90; pitch++) +// { +// for (int bank = -180; bank < 180; bank++) +// { +// for (int heading = 0; heading < 360; heading++) +// { +// std::uint32_t pbh = 0; +// packPBH(pitch, bank, heading, true, pbh); + +// double pitch2 = 0; +// double bank2 = 0; +// double heading2 = 0; +// bool onGround2 = false; +// unpackPBH(pbh, pitch2, bank2, heading2, onGround2); +// QVERIFY(pitch2 >= -90); +// QVERIFY(pitch2 < 90); +// QVERIFY(bank2 >= -180); +// QVERIFY(bank2 < 180); +// QVERIFY(heading2 >= 0); +// QVERIFY(heading2 < 360); +// QCOMPARE(pitch, pitch2); +// QCOMPARE(bank, bank2); +// QVERIFY(qAbs(heading - heading2) < 1); +// QCOMPARE(true, onGround2); +// } +// } +// } + + } + + void CTestFsdMessages::testPilotDataUpdate() + { + const PilotDataUpdate message(CTransponder::ModeC, "ABCD", 7000, PilotRating::Student, 43.12578, -72.15841, 12000, 12008, + 125, -2, 3, 280, true); + + QCOMPARE(message.sender(), QString("ABCD")); + QCOMPARE(QString(""), message.receiver()); + QCOMPARE(CTransponder::ModeC, message.m_transponderMode); + QCOMPARE(7000, message.m_transponderCode); + QCOMPARE(PilotRating::Student, message.m_rating); + QCOMPARE(43.12578, message.m_latitude); + QCOMPARE(-72.15841, message.m_longitude); + QCOMPARE(12000, message.m_altitudeTrue); + QCOMPARE(12008, message.m_altitudePressure); + QCOMPARE(125, message.m_groundSpeed); + QCOMPARE(-2, message.m_pitch); + QCOMPARE(3, message.m_bank); + QCOMPARE(280, message.m_heading); + QCOMPARE(true, message.m_onGround); + + QString stringRef("N:ABCD:7000:1:43.12578:-72.15841:12000:125:25132146:8"); + QString str = message.toTokens().join(":"); + QCOMPARE(str, stringRef); + + QStringList tokens = QString("N:ABCD:7000:1:43.12578:-72.15841:12000:125:25132146:8").split(':'); + auto messageFromTokens = PilotDataUpdate::fromTokens(tokens); + QCOMPARE(QString("ABCD"), messageFromTokens.sender()); + QCOMPARE(QString(""), messageFromTokens.receiver()); + QCOMPARE(CTransponder::ModeC, messageFromTokens.m_transponderMode); + QCOMPARE(7000, messageFromTokens.m_transponderCode); + QCOMPARE(PilotRating::Student, messageFromTokens.m_rating); + QCOMPARE(43.12578, messageFromTokens.m_latitude); + QCOMPARE(-72.15841, messageFromTokens.m_longitude); + QCOMPARE(12000, messageFromTokens.m_altitudeTrue); + QCOMPARE(12008, messageFromTokens.m_altitudePressure); + QCOMPARE(125, messageFromTokens.m_groundSpeed); + QVERIFY(message.m_pitch - messageFromTokens.m_pitch < 1.0); + QVERIFY(message.m_bank - messageFromTokens.m_bank < 1.0); + QVERIFY(message.m_heading - messageFromTokens.m_heading < 1.0); + QCOMPARE(messageFromTokens.m_onGround, true); + } + + void CTestFsdMessages::testPing() + { + const Ping message("ABCD", "SERVER", "85275222"); + QCOMPARE(message.sender(), QString("ABCD")); + QCOMPARE(message.receiver(), QString("SERVER")); + QCOMPARE("85275222", message.m_timestamp); + + QString stringRef("ABCD:SERVER:85275222"); + QString str = message.toTokens().join(":"); + QCOMPARE(str, stringRef); + + QStringList tokens = QString("ABCD:SERVER:85275222").split(':'); + auto messageFromTokens = Ping::fromTokens(tokens); + QCOMPARE(messageFromTokens, message); + } + + void CTestFsdMessages::testPlaneInfoRequest() + { + const PlaneInfoRequest message("ABCD", "XYZ"); + QCOMPARE(message.sender(), QString("ABCD")); + QCOMPARE(QString("XYZ"), message.receiver()); + + QString stringRef("ABCD:XYZ:PIR"); + QString str = message.toTokens().join(":"); + QCOMPARE(str, stringRef); + + QStringList tokens = QString("ABCD:XYZ:PIR").split(':'); + auto messageFromTokens = PlaneInfoRequest::fromTokens(tokens); + QCOMPARE(messageFromTokens, message); + } + + void CTestFsdMessages::testPlaneInformation() + { + const PlaneInformation message("ABCD", "XYZ", "B744", "BAW", "UNION"); + QCOMPARE(message.sender(), QString("ABCD")); + QCOMPARE(QString("XYZ"), message.receiver()); + QCOMPARE(QString("B744"), message.m_aircraft); + QCOMPARE(QString("BAW"), message.m_airline); + QCOMPARE(QString("UNION"), message.m_livery); + + const PlaneInformation message1("ABCD", "XYZ", "B744", "", ""); + QString stringRef1("ABCD:XYZ:PI:GEN:EQUIPMENT=B744"); + QString str1 = message1.toTokens().join(":"); + QCOMPARE(str1, stringRef1); + + const PlaneInformation message2("ABCD", "XYZ", "B744", "BAW", ""); + QString stringRef2("ABCD:XYZ:PI:GEN:EQUIPMENT=B744:AIRLINE=BAW"); + QString str2 = message2.toTokens().join(":"); + QCOMPARE(str2, stringRef2); + + const PlaneInformation message3("ABCD", "XYZ", "B744", "BAW", "UNION"); + QString stringRef3("ABCD:XYZ:PI:GEN:EQUIPMENT=B744:AIRLINE=BAW:LIVERY=UNION"); + QString str3 = message3.toTokens().join(":"); + QCOMPARE(str3, stringRef3); + + const PlaneInformation reference1("ABCD", "XYZ", "B744", "", ""); + + QStringList tokens1 = QString("ABCD:XYZ:PI:GEN:EQUIPMENT=B744").split(':'); + auto messageFromTokens1 = PlaneInformation::fromTokens(tokens1); + QCOMPARE(reference1, messageFromTokens1); + + const PlaneInformation reference2("ABCD", "XYZ", "B744", "BAW", ""); + + QStringList tokens2 = QString("ABCD:XYZ:PI:GEN:EQUIPMENT=B744:AIRLINE=BAW").split(':'); + auto messageFromTokens2 = PlaneInformation::fromTokens(tokens2); + QCOMPARE(reference2, messageFromTokens2); + + const PlaneInformation reference3("ABCD", "XYZ", "B744", "BAW", "UNION"); + + QStringList tokens3 = QString("ABCD:XYZ:PI:GEN:EQUIPMENT=B744:AIRLINE=BAW:LIVERY=UNION").split(':'); + auto messageFromTokens3 = PlaneInformation::fromTokens(tokens3); + QCOMPARE(reference3, messageFromTokens3); + } + + void CTestFsdMessages::testPlaneInfoRequestFsinn() + { + const PlaneInfoRequestFsinn message("ABCD", "XYZ", "DLH", "A320", "L2J", "FLIGHTFACTOR A320 LUFTHANSA D-AIPC"); + QCOMPARE(message.sender(), QString("ABCD")); + QCOMPARE(QString("XYZ"), message.receiver()); + QCOMPARE(QString("DLH"), message.m_airlineIcao); + QCOMPARE(QString("A320"), message.m_aircraftIcao); + QCOMPARE(QString("L2J"), message.m_aircraftIcaoCombinedType); + QCOMPARE(QString("FLIGHTFACTOR A320 LUFTHANSA D-AIPC"), message.m_sendMModelString); + + QString stringRef("ABCD:XYZ:FSIPIR:0:DLH:A320:::::L2J:FLIGHTFACTOR A320 LUFTHANSA D-AIPC"); + QString str = message.toTokens().join(":"); + QCOMPARE(str, stringRef); + } + + void CTestFsdMessages::testPlaneInformationFsinn() + { + const PlaneInformationFsinn message("ABCD", "XYZ", "DLH", "A320", "L2J", "FLIGHTFACTOR A320 LUFTHANSA D-AIPC"); + QCOMPARE(message.sender(), QString("ABCD")); + QCOMPARE(QString("XYZ"), message.receiver()); + QCOMPARE(QString("DLH"), message.m_airlineIcao); + QCOMPARE(QString("A320"), message.m_aircraftIcao); + QCOMPARE(QString("L2J"), message.m_aircraftIcaoCombinedType); + QCOMPARE(QString("FLIGHTFACTOR A320 LUFTHANSA D-AIPC"), message.m_sendMModelString); + + QString stringRef("ABCD:XYZ:FSIPI:0:DLH:A320:::::L2J:FLIGHTFACTOR A320 LUFTHANSA D-AIPC"); + QString str = message.toTokens().join(":"); + QCOMPARE(str, stringRef); + } + + void CTestFsdMessages::testPong() + { + const Pong message("ABCD", "SERVER", "85275222"); + QCOMPARE(message.sender(), QString("ABCD")); + QCOMPARE(message.receiver(), QString("SERVER")); + QCOMPARE("85275222", message.m_timestamp); + + QString stringRef("ABCD:SERVER:85275222"); + QString str = message.toTokens().join(":"); + QCOMPARE(str, stringRef); + + const Pong reference("ABCD", "SERVER", "85275222"); + + QStringList tokens = QString("ABCD:SERVER:85275222").split(':'); + auto messageFromTokens = Pong::fromTokens(tokens); + QCOMPARE(messageFromTokens, message); + } + + void CTestFsdMessages::testServerError() + { + const ServerError message("SERVER", "ABCD", ServerErrorCode::NoWeatherProfile, "EGLL", "No such weather profile"); + QCOMPARE(QString("SERVER"), message.sender()); + QCOMPARE(QString("ABCD"), message.receiver()); + QCOMPARE(ServerErrorCode::NoWeatherProfile, message.m_errorNumber); + QCOMPARE("EGLL", message.m_causingParameter); + QCOMPARE("No such weather profile", message.m_description); + + QString stringRef("SERVER:ABCD:9:EGLL:No such weather profile"); + QString str = message.toTokens().join(":"); + QCOMPARE(str, stringRef); + + QStringList tokens = QString("SERVER:ABCD:009:EGLL:No such weather profile").split(':'); + auto messageFromTokens = ServerError::fromTokens(tokens); + QCOMPARE(messageFromTokens, message); + } + + void CTestFsdMessages::testTextMessage() + { + + } +} + +//! main +BLACKTEST_APPLESS_MAIN(BlackMiscTest::CTestFsdMessages); + +#include "testfsdmessages.moc" + +//! \endcond diff --git a/tests/blackcore/fsd/testfsdmessages/testfsdmessages.pro b/tests/blackcore/fsd/testfsdmessages/testfsdmessages.pro new file mode 100644 index 000000000..479346d64 --- /dev/null +++ b/tests/blackcore/fsd/testfsdmessages/testfsdmessages.pro @@ -0,0 +1,28 @@ +load(common_pre) + +QT += core dbus testlib + +TARGET = testfsdmessages +CONFIG -= app_bundle +CONFIG += blackconfig +CONFIG += blackmisc +CONFIG += blackcore +CONFIG += testcase +CONFIG += no_testcase_installs + +TEMPLATE = app + +DEPENDPATH += \ + . \ + $$SourceRoot/src \ + $$SourceRoot/tests \ + +INCLUDEPATH += \ + $$SourceRoot/src \ + $$SourceRoot/tests \ + +SOURCES += testfsdmessages.cpp + +DESTDIR = $$DestRoot/bin + +load(common_post) diff --git a/tests/blackcore/vatsim/testnetwork/expect.cpp b/tests/blackcore/vatsim/testnetwork/expect.cpp deleted file mode 100644 index f426f2478..000000000 --- a/tests/blackcore/vatsim/testnetwork/expect.cpp +++ /dev/null @@ -1,123 +0,0 @@ -/* Copyright (C) 2013 - * swift Project Community / Contributors - * - * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level - * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, - * or distributed except according to the terms contained in the LICENSE file. - */ - -//! \cond PRIVATE_TESTS - -/*! - * \file - * \ingroup testblackcore - */ - -#include "expect.h" -#include "blackmisc/range.h" - -#include -#include -#include -#include - -namespace BlackCoreTest -{ - -void ExpectUnit::wait(int timeout) -{ - m_parent->wait(m_srcloc, timeout, *this); -} - -/* Connect slot for the first expectation, then send all queued signals. - */ -void ExpectUnit::init() const -{ - m_nextExpect = m_expects.begin(); - next(); - for (auto i = m_sends.begin(); i != m_sends.end(); ++i) - { - (*i)(); - } -} - -/* Connect slot for the next expectation, or call the onDone() callback if that was the last one. - * Each expectation slot calls this function, so each successful expectation connects the next one. - */ -void ExpectUnit::next() const -{ - m_guard.cleanup(); - - if (m_nextExpect == m_expects.end()) - { - if (m_onDone) - { - m_onDone(this); - } - } - else - { - (*m_nextExpect)(); - ++m_nextExpect; - } -} - -/* For each unit queued by an overloaded wait(), setup its onDone() callback and call its init() method. - * The onDone() callback removes the unit from the queue. When all units have been removed from the queue, - * then the expectation has succeeded. If the timer times out before that happens, then the expectation - * has failed. Failures cause subsequent calls to wait() to do nothing. - */ -void Expect::wait(const SourceLocation& srcloc, int timeout) -{ - auto unitsCopy = m_units; - m_units.clear(); - - if (m_failed) - { - return; - } - - for (auto i : unitsCopy) - { - i->onDone([&](const ExpectUnit* u){ unitsCopy.remove(u); }); - } - // toList is an easy way to make a temporary copy, needed because init might invalidate iterators - for (auto i : unitsCopy.toList()) // clazy:exclude=container-anti-pattern,range-loop - { - i->init(); - } - - QTimer timer; - timer.setSingleShot(true); - QObject::connect(&timer, &QTimer::timeout, [=, &unitsCopy]{ - reportTimeout(srcloc, unitsCopy); - for (auto i : BlackMisc::as_const(unitsCopy)) - { - i->onDone(nullptr); //paranoia - } - unitsCopy.clear(); - m_failed = true; - }); - timer.start(timeout * 1000); - - while (! unitsCopy.isEmpty()) - { - QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents, 10); - } -} - -void Expect::reportTimeout(const SourceLocation& srcloc, const QSet& units) -{ - QString msg = "*** Timed Out ***"; - QString prefix = "\nwhile waiting for "; - for (auto i = units.begin(); i != units.end(); ++i) - { - msg += prefix + (*i)->m_waitingFor; - prefix = "\nand "; - } - QTest::qFail(qPrintable(msg), qPrintable(srcloc.file), srcloc.line); -} - -//! \endcond - -} //namespace diff --git a/tests/blackcore/vatsim/testnetwork/expect.h b/tests/blackcore/vatsim/testnetwork/expect.h deleted file mode 100644 index e4d916079..000000000 --- a/tests/blackcore/vatsim/testnetwork/expect.h +++ /dev/null @@ -1,299 +0,0 @@ -/* Copyright (C) 2013 - * swift Project Community / Contributors - * - * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level - * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, - * or distributed except according to the terms contained in the LICENSE file. - */ - -#ifndef BLACKCORETEST_EXPECT_H -#define BLACKCORETEST_EXPECT_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -//! \cond PRIVATE_TESTS - -/*! - * \file - * \ingroup testblackcore - */ - -namespace BlackCoreTest -{ - class Expect; - - /*! - * Class representing a position in a source code file, for error reporting. Used in combination with the SOURCE_LOCATION macro. - */ - class SourceLocation - { - public: - SourceLocation(const QString &f, int ln) : file(f), line(ln) {} //!< Constructor - const QString file; //!< Source code filename - const int line; //!< Line number - }; - -//! Evaluates to an instance of SourceLocation representing the position where the macro is used. -#define SOURCE_LOCATION (BlackCoreTest::SourceLocation(__FILE__, __LINE__)) - - /*! - * RAII class for Qt signal/slot connections. All connections are closed when object is destroyed. - */ - class ConnectGuard - { - public: - //! Constructor. - ConnectGuard() {} - - //! Destructor. - ~ConnectGuard() { cleanup(); } - - //! Add a connection to the object.. - ConnectGuard &operator+= (const QMetaObject::Connection &conn) { m_conns += conn; return *this; } - - //! Disconnect and remove all stored connections. - void cleanup() { for (auto i = m_conns.cbegin(); i != m_conns.cend(); ++i) QObject::disconnect(*i); m_conns.clear(); } - - //! Copying is only allowed when there are no connections stored. - //! @{ - ConnectGuard(const ConnectGuard &other) { Q_ASSERT(other.m_conns.isEmpty()); Q_UNUSED(other); } - ConnectGuard &operator= (const ConnectGuard &other) { Q_ASSERT(other.m_conns.isEmpty()); Q_UNUSED(other); return *this; } - //! @} - - private: - QVector m_conns; - }; - - /*! - * One unit of expectation, as returned by Expect::unit(). - * - * Contains a sequence of signals to send, and a sequence of signals that are expected to be received in reply. - */ - class ExpectUnit - { - public: - /*! - * Adds a signal to the list of signals to send, with optional arguments. - * \param slot A pointer-to-member-function of the subject class. - * \return this object, so methods can be chained. - */ - template ExpectUnit &send(F slot) { m_sends.push_back(std::bind(slot, subject())); return *this; } - - /*! - * \copydoc send(F) - * \param arg1 - */ - template ExpectUnit &send(F slot, T1 arg1) { m_sends.push_back(std::bind(slot, subject(), arg1)); return *this; } - - /*! - * \copydoc send(F,T1) - * \param arg2 - */ - template ExpectUnit &send(F slot, T1 arg1, T2 arg2) { m_sends.push_back(std::bind(slot, subject(), arg1, arg2)); return *this; } - - /*! - * \copydoc send(F,T1,T2) - * \param arg3 - */ - template ExpectUnit &send(F slot, T1 arg1, T2 arg2, T3 arg3) { m_sends.push_back(std::bind(slot, subject(), arg1, arg2, arg3)); return *this; } - - /*! - * \copydoc send(F,T1,T2,T3) - * \param arg4 - */ - template ExpectUnit &send(F slot, T1 arg1, T2 arg2, T3 arg3, T4 arg4) { m_sends.push_back(std::bind(slot, subject(), arg1, arg2, arg3, arg4)); return *this; } - - /*! - * \copydoc send(F,T1,T2,T3,T4) - * \param arg5 - */ - template ExpectUnit &send(F slot, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) { m_sends.push_back(std::bind(slot, subject(), arg1, arg2, arg3, arg4, arg5)); return *this; } - - /*! - * Adds a signal to the list of signals which are expects to be received. - * \param signal A pointer-to-member-function of the subject class. - * \param slot A callable object which will be connected to the signal, e.g. to validate the signal arguments. - * \return this object, so methods can be chained. - */ - template ExpectUnit &expect(F1 signal, F2 slot) - { - auto subj = subject(); - auto next = [ = ] { this->next(); }; - m_expects.push_back([ = ] - { - m_waitingFor = subj->metaObject()->className(); - m_waitingFor += "::"; - m_waitingFor += QMetaMethod::fromSignal(signal).name(); - m_guard += QObject::connect(subj, signal, slot); - m_guard += QObject::connect(subj, signal, next); - }); - return *this; - } - - /*! - * Adds a signal to the list of signals which are expected to be received. - * \param signal A pointer-to-member-function of the subject class. - * \return this object, so methods can be chained. - */ - template ExpectUnit &expect(F signal) { return expect(signal, [] {}); } - - /*! - * Sends all the queued signals, then waits for each of the expected signals in sequence. - * If the full sequence of expected signals is not received, calls QTest::qFail(). - * \param timeout Time to wait in seconds. Qt event queue is processed when waiting. - */ - void wait(int timeout); - - private: - friend class Expect; - - ExpectUnit(Expect *parent, QObject *subject, const SourceLocation &srcloc) : m_parent(parent), m_subject(subject), m_srcloc(srcloc) {} - - void init() const; - void next() const; - - QPointer m_parent; - QPointer m_subject; - SourceLocation m_srcloc; - QString m_waitingFor; - QVector> m_sends; - QVector> m_expects; - mutable QVector>::const_iterator m_nextExpect; - mutable ConnectGuard m_guard; - - mutable std::function m_onDone; - void onDone(const std::function &callback) const { m_onDone = callback; } - - // Helper traits class. Given T is a pointer-to-member-of-U, ClassOf::type is U. - template struct ClassOf; - - template struct ClassOf { using type = U; }; - - // Given T is a pointer-to-member-of-U, subject() returns Expect's subject casted to U*. - template typename ClassOf::type *subject() const { return qobject_cast::type *>(m_subject.data()); } - }; - - /*! - * Class for writing unit tests in which signals are sent to some object, and other signals are expected to be received in reply. - * Loosely modelled on the C library libexpect. - * - * Commonly used in combination with the macros EXPECT_UNIT or EXPECT_WAIT. - */ - class Expect : public QObject - { - Q_OBJECT - - public: - /*! - * Constructor. - * \param testSubject The object to be tested. - */ - explicit Expect(QObject *testSubject) : m_subject(testSubject), m_failed(false) {} - - /*! - * Add a single expectation that will remain for the lifetime of the object. - * \param signal A pointer-to-member-function of the subject class. - * \param slot A callable object which will be connected to the signal, e.g. to validate the signal arguments. - */ - template void always(F1 signal, F2 slot) - { - m_guard += QObject::connect(m_subject, signal, slot); - } - - /*! - * Returns a new unit of expectation for the subject. Commonly called via the EXPECT_UNIT macro. - * \param srcloc Represents the caller's location in the source code, for error reporting. - */ - ExpectUnit unit(const SourceLocation &srcloc = SOURCE_LOCATION) { return ExpectUnit(this, m_subject, srcloc); } - - /*! - * Allows two or more units of expectation to be waited on simultaneously. Commonly valled via the EXPECT_WAIT macro. - * \param srcloc Represents the caller's location in the source code, for error reporting. - * \param timeout Time to wait in seconds. Qt event queue is processed when waiting. - * \param u1 - */ - void wait(const SourceLocation &srcloc, int timeout, const ExpectUnit &u1) { m_units.insert(&u1); wait(srcloc, timeout); } - - /*! - * \copydoc wait(const SourceLocation&,int,const ExpectUnit&) - * \param u2 - */ - void wait(const SourceLocation &srcloc, int timeout, const ExpectUnit &u1, const ExpectUnit &u2) { m_units.insert(&u1); m_units.insert(&u2); wait(srcloc, timeout); } - - /*! - * \copydoc wait(const SourceLocation&,int,const ExpectUnit&,const ExpectUnit&) - * \param u3 - */ - void wait(const SourceLocation &srcloc, int timeout, const ExpectUnit &u1, const ExpectUnit &u2, const ExpectUnit &u3) { m_units.insert(&u1); m_units.insert(&u2); m_units.insert(&u3); wait(srcloc, timeout); } - - /*! - * \copydoc wait(const SourceLocation&,int,const ExpectUnit&,const ExpectUnit&,const ExpectUnit&) - * \param u4 - */ - void wait(const SourceLocation &srcloc, int timeout, const ExpectUnit &u1, const ExpectUnit &u2, const ExpectUnit &u3, const ExpectUnit &u4) { m_units.insert(&u1); m_units.insert(&u2); m_units.insert(&u3); m_units.insert(&u4); wait(srcloc, timeout); } - - /*! - * \copydoc wait(const SourceLocation&,int,const ExpectUnit&,const ExpectUnit&,const ExpectUnit&,const ExpectUnit&) - * \param u5 - */ - void wait(const SourceLocation &srcloc, int timeout, const ExpectUnit &u1, const ExpectUnit &u2, const ExpectUnit &u3, const ExpectUnit &u4, const ExpectUnit &u5) { m_units.insert(&u1); m_units.insert(&u2); m_units.insert(&u3); m_units.insert(&u4); m_units.insert(&u5); wait(srcloc, timeout); } - - private: - friend class ExpectUnit; - - void wait(const SourceLocation &srcloc, int timeout); - void reportTimeout(const SourceLocation &srcloc, const QSet &units); - - QPointer m_subject; - QSet m_units; - ConnectGuard m_guard; - bool m_failed; - }; - - /*! - * Wrapper for Expect::unit() which fills in the source location parameter. - * Returns a new unit of expectation for the subject. - * \param EXP Instance of Expect on which to call unit(). - */ -#define EXPECT_UNIT(EXP) ((EXP).unit(SOURCE_LOCATION)) - - /*! - * Wrapper for Expect::wait() which fills in the source location parameter. - * Allows two or more units of expectation to be waited on simultaneously. - * \param EXP Instance of Expect on which to call wait(). - * \param TIME Time to wait in seconds. Qt event queue is processed when waiting. - * \param U1, U2 - */ -#define EXPECT_WAIT_2(EXP, TIME, U1, U2) ((EXP).wait(SOURCE_LOCATION, (TIME), (U1), (U2))) - - /*! - * \copydoc EXPECT_WAIT_2(EXP,TIME,U1,U2) - * \param U3 - */ -#define EXPECT_WAIT_3(EXP, TIME, U1, U2, U3) ((EXP).wait(SOURCE_LOCATION, (TIME), (U1), (U2), (U3))) - - /*! - * \copydoc EXPECT_WAIT_3(EXP,TIME,U1,U2,U3) - * \param U4 - */ -#define EXPECT_WAIT_4(EXP, TIME, U1, U2, U3, U4) ((EXP).wait(SOURCE_LOCATION, (TIME), (U1), (U2), (U3), (U4))) - - /*! - * \copydoc EXPECT_WAIT_4(EXP,TIME,U1,U2,U3,U4) - * \param U5 - */ -#define EXPECT_WAIT_5(EXP, TIME, U1, U2, U3, U4, U5) ((EXP).wait(SOURCE_LOCATION, (TIME), (U1), (U2), (U3), (U4), (U5))) - -} // ns - -//! \endcond - -#endif // guard diff --git a/tests/blackcore/vatsim/testnetwork/testnetwork.cpp b/tests/blackcore/vatsim/testnetwork/testnetwork.cpp deleted file mode 100644 index 6654e1ea5..000000000 --- a/tests/blackcore/vatsim/testnetwork/testnetwork.cpp +++ /dev/null @@ -1,175 +0,0 @@ -/* Copyright (C) 2015 - * swift project Community / Contributors - * - * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level - * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, - * or distributed except according to the terms contained in the LICENSE file. - */ - -//! \cond PRIVATE_TESTS -//! \file -//! \ingroup testblackcore - -#include "expect.h" -#include "blackcore/application.h" -#include "blackcore/network.h" -#include "blackcore/vatsim/networkvatlib.h" -#include "blackmisc/aviation/aircrafticaocode.h" -#include "blackmisc/aviation/airlineicaocode.h" -#include "blackmisc/aviation/callsign.h" -#include "blackmisc/network/clientprovider.h" -#include "blackmisc/network/networkutils.h" -#include "blackmisc/network/server.h" -#include "blackmisc/network/url.h" -#include "blackmisc/network/user.h" -#include "blackmisc/pq/time.h" -#include "blackmisc/simulation/simulatedaircraft.h" -#include "blackmisc/simulation/ownaircraftproviderdummy.h" -#include "blackmisc/simulation/remoteaircraftproviderdummy.h" -#include "blackmisc/stringutils.h" -#include "test.h" - -#include -#include -#include -#include - -using namespace BlackCore; -using namespace BlackCore::Vatsim; -using namespace BlackMisc; -using namespace BlackMisc::Aviation; -using namespace BlackMisc::Simulation; -using namespace BlackMisc::Network; -using namespace BlackMisc::Geo; -using namespace BlackMisc::PhysicalQuantities; - -namespace BlackCoreTest -{ - //! INetwork implementation classes tests - class CTestNetwork : public QObject - { - Q_OBJECT - - private slots: - void initTestCase(); - - //! Test the vatlib - void networkVatlibTest() { networkTest(m_networkVatlib); } - - void cleanupTestCase(); - - private: - //! Common part used by all tests. - void networkTest(BlackCore::INetwork *net); - - CNetworkVatlib *m_networkVatlib = nullptr; //!< vatlib instance - - //! Test if server is available - static bool pingServer(const BlackMisc::Network::CServer &server); - }; - - void CTestNetwork::initTestCase() - { - m_networkVatlib = new CNetworkVatlib( - CClientProviderDummy::instance(), - COwnAircraftProviderDummy::instance(), - CRemoteAircraftProviderDummy::instance(), - this - ); - } - - void CTestNetwork::networkTest(BlackCore::INetwork *net) - { - const CServer fsdServer = CServer::swiftFsdTestServer(true); - if (!this->pingServer(fsdServer)) { QSKIP("Server not reachable."); } - - QString string = net->connectionStatusToString(INetwork::Connected); - QVERIFY(string == "Connected"); - - Expect e(net); - CSimulatedAircraft aircraft; - aircraft.setIcaoCodes(CAircraftIcaoCode("C172", "L1P"), CAirlineIcaoCode("YYY")); - - EXPECT_UNIT(e) - .send(&INetwork::presetServer, fsdServer) - .send(&INetwork::presetCallsign, "SWIFT") - .send(&INetwork::presetIcaoCodes, aircraft) - .send(&INetwork::initiateConnection) - .expect(&INetwork::connectionStatusChanged, [](INetwork::ConnectionStatus, INetwork::ConnectionStatus newStatus) - { - QVERIFY(newStatus == INetwork::Connecting); - qDebug() << "CONNECTING"; - }) - .expect(&INetwork::connectionStatusChanged, [](INetwork::ConnectionStatus, INetwork::ConnectionStatus newStatus) - { - // we skip the test at the beginning if the server cannot be reached - // otherwise the whole build on Jenkins may fail - QVERIFY(newStatus == INetwork::Connected); - qDebug() << "CONNECTED"; - }) - .wait(10); - - EXPECT_UNIT(e) - .send(&INetwork::sendPing, "server") - .expect(&INetwork::pongReceived, [](const CCallsign & callsign, const PhysicalQuantities::CTime & elapsedTime) - { - qDebug() << "PONG" << callsign << elapsedTime; - }) - .wait(10); - - EXPECT_UNIT(e) - .send(&INetwork::terminateConnection) - .expect(&INetwork::connectionStatusChanged, [](INetwork::ConnectionStatus, INetwork::ConnectionStatus newStatus) - { - QVERIFY(newStatus == INetwork::Disconnecting); - qDebug() << "DISCONNECTING"; - }) - .expect(&INetwork::connectionStatusChanged, [](INetwork::ConnectionStatus, INetwork::ConnectionStatus newStatus) - { - QVERIFY(newStatus == INetwork::Disconnected); - qDebug() << "DISCONNECTED"; - }) - .wait(10); - - QThread::msleep(250); // make sure the last debug messages are written - } - - bool CTestNetwork::pingServer(const CServer &server) - { - QString m; - const CUrl url(server.getAddress(), server.getPort()); - if (!CNetworkUtils::canConnect(url, m, 2500)) - { - qWarning() << "Skipping unit test as" << url.getFullUrl() << "cannot be connected"; - return false; - } - return true; - } - - void CTestNetwork::cleanupTestCase() - { - delete m_networkVatlib; - } -} - -//! main -int main(int argc, char *argv[]) -{ - QCoreApplication app(argc, argv); - BLACKTEST_INIT(BlackCoreTest::CTestNetwork) - CApplication a(CApplicationInfo::UnitTest); - a.addVatlibOptions(); - const bool setup = a.parseAndSynchronizeSetup(); - if (!setup) { qWarning() << "No setup loaded"; } - int r = EXIT_FAILURE; - if (a.start()) - { - r = QTest::qExec(&to, args); - } - a.gracefulShutdown(); - return r; -} - -#include "testnetwork.moc" - -//! \endcond diff --git a/tests/blackcore/vatsim/vatsim.pro b/tests/blackcore/vatsim/vatsim.pro deleted file mode 100644 index f33df779e..000000000 --- a/tests/blackcore/vatsim/vatsim.pro +++ /dev/null @@ -1,3 +0,0 @@ -TEMPLATE = subdirs -SUBDIRS += \ - testnetwork \