[AFV] Signal/handling for authentication failures

* authentication issues in AFV were silently handled
* and there was no re-try, which could mean an initial glitch COULD cause AFV not working properly
* NOW handling in AFV client and context
* also reset connection data so for a new session no old authentication token or check time is used
* check for "invalid" QDateTime(s)
This commit is contained in:
Klaus Basan
2020-04-27 19:04:01 +02:00
committed by Mat Sutcliffe
parent 9e6716e515
commit bba07ef4c4
13 changed files with 146 additions and 34 deletions

View File

@@ -62,7 +62,7 @@ namespace BlackCore
m_output(new COutput(this)),
m_voiceServerTimer(new QTimer(this))
{
this->setObjectName("AFV client: " + apiServer );
this->setObjectName("AFV client: " + apiServer);
m_connection->setReceiveAudio(false);
connect(m_input, &CInput::opusDataAvailable, this, &CAfvClient::opusDataAvailable);
@@ -144,7 +144,7 @@ namespace BlackCore
}
const bool integrated = sApp->getIContextSimulator()->getSimulatorSettings().isComIntegrated();
const bool changed = integrated != m_integratedComUnit;
const bool changed = integrated != m_integratedComUnit;
m_integratedComUnit = integrated;
if (changed)
@@ -167,9 +167,21 @@ namespace BlackCore
this->connectWithContexts();
this->setCallsign(callsign);
QPointer<CAfvClient> myself(this);
if (!this->isConnected() && m_retryConnectAttempt == 0)
{
// check if connect simply did NOT receive an answer
QTimer::singleShot(20 * 1000, this, [ = ]
{
if (!myself) { return; }
if (m_retryConnectAttempt > 0) { return; } // already handled
// this will reconnect ONLY if not already connected
this->retryConnectTo(cid, password, callsign, client, QStringLiteral("No connection afer 20secs"));
});
}
// thread safe connect
{
QPointer<CAfvClient> myself(this);
QMutexLocker lock(&m_mutexConnection);
// async connection
@@ -194,10 +206,12 @@ namespace BlackCore
QMutexLocker lock(&m_mutex);
if (m_voiceServerTimer) { m_voiceServerTimer->start(PositionUpdatesMs); }
}
m_retryConnectAttempt = 0;
emit this->connectionStatusChanged(Connected);
}
else
{
myself->retryConnectTo(cid, password, callsign, client, QStringLiteral("AFV authentication failed for '%1' callsign '%2'").arg(cid, callsign));
emit this->connectionStatusChanged(Disconnected);
}
}
@@ -1024,12 +1038,12 @@ namespace BlackCore
void CAfvClient::autoLogoffWithoutFsdNetwork()
{
if (!hasContexts()) { return; }
if (!this->isConnected()) { m_connectMismatches = 0; return; }
if (!hasContexts()) { return; }
if (!this->isConnected()) { m_fsdConnectMismatches = 0; return; }
// AFV is connected
if (sApp->getIContextNetwork()->isConnected()) { m_connectMismatches = 0; return; }
if (++m_connectMismatches < 2) { return; } // avoid a single issue causing logoff
if (sApp->getIContextNetwork()->isConnected()) { m_fsdConnectMismatches = 0; return; }
if (++m_fsdConnectMismatches < 2) { return; } // avoid a single issue causing logoff
CLogMessage(this).warning(u"Auto logoff AFV client because FSD no longer connected");
this->disconnectFrom();
@@ -1161,6 +1175,32 @@ namespace BlackCore
emit this->receivingCallsignsChanged(args);
}
void CAfvClient::retryConnectTo(const QString &cid, const QString &password, const QString &callsign, const QString &client, const QString &reason)
{
if (this->isConnected()) { return; }
m_retryConnectAttempt++;
const int retrySecs = qMin(3 * 60, m_retryConnectAttempt * 30);
const CStatusMessage msg = CStatusMessage(this).validationError(reason + ". Retry in %1secs. Attempt %2.") << retrySecs << m_retryConnectAttempt;
this->reconnectTo(cid, password, callsign, client, retrySecs * 1000, msg);
}
void CAfvClient::reconnectTo(const QString &cid, const QString &password, const QString &callsign, const QString &client, int delayMs, const CStatusMessage &msg)
{
if (msg.isFailure())
{
emit this->afvConnectionFailure(msg);
}
QPointer<CAfvClient> myself(this);
QTimer::singleShot(delayMs, this, [ = ]
{
if (!myself) { return; }
if (myself->isConnected()) { return; }
this->connectTo(cid, password, callsign, client);
});
}
QVector<StationDto> CAfvClient::getAliasedStations() const
{
QMutexLocker lock(&m_mutex);

View File

@@ -293,6 +293,9 @@ namespace BlackCore
//! Connection status has been changed
void connectionStatusChanged(ConnectionStatus status);
//! Authentication has failed with AFV server
void afvConnectionFailure(const BlackMisc::CStatusMessage &msg);
//! Client updated from own aicraft data
void updatedFromOwnAircraftCockpit();
@@ -332,6 +335,12 @@ namespace BlackCore
void onUpdateTransceiversFromContext(const BlackMisc::Simulation::CSimulatedAircraft &aircraft, const BlackMisc::CIdentifier &originator);
void onReceivingCallsignsChanged(const BlackCore::Afv::Audio::TransceiverReceivingCallsignsChangedArgs &args);
//! Re-try connection to server
void retryConnectTo(const QString &cid, const QString &password, const QString &callsign, const QString &client, const QString &reason);
//! Connect again in given ms
void reconnectTo(const QString &cid, const QString &password, const QString &callsign, const QString &client, int delayMs, const BlackMisc::CStatusMessage &msg);
//! All aliased stations
//! \threadsafe
//! @{
@@ -375,7 +384,8 @@ namespace BlackCore
QSet<quint16> m_enabledTransceivers;
static const QVector<quint16> &allTransceiverIds() { static const QVector<quint16> transceiverIds{0, 1}; return transceiverIds; }
std::atomic_int m_connectMismatches { 0 };
std::atomic_int m_fsdConnectMismatches { 0 }; //!< FSD no longer connected?
std::atomic_int m_retryConnectAttempt { 0 }; //!< Try to connect the n-th time
std::atomic_bool m_isStarted { false };
std::atomic_bool m_loopbackOn { false };
std::atomic_bool m_enableAliased { true };

View File

@@ -107,9 +107,9 @@ namespace BlackCore
const qint64 validFromSecs = doc.object().value("nbf").toInt(-1);
if (validFromSecs < 0) { break; }
const qint64 localSecsSinceEpoch = QDateTime::currentSecsSinceEpoch();
serverToUserOffsetSecs = validFromSecs - localSecsSinceEpoch;
serverToUserOffsetSecs = validFromSecs - localSecsSinceEpoch;
const qint64 serverExpirySecs = doc.object().value("exp").toInt();
const qint64 expiryLocalUtc = serverExpirySecs - serverToUserOffsetSecs;
const qint64 expiryLocalUtc = serverExpirySecs - serverToUserOffsetSecs;
lifeTimeSecs = expiryLocalUtc - localSecsSinceEpoch;
}
while (false);
@@ -117,7 +117,7 @@ namespace BlackCore
if (lifeTimeSecs > 0)
{
m_serverToUserOffsetMs = serverToUserOffsetSecs * 1000;
m_expiryLocalUtc = QDateTime::currentDateTimeUtc().addSecs(lifeTimeSecs);
m_expiryLocalUtc = QDateTime::currentDateTimeUtc().addSecs(lifeTimeSecs);
m_isAuthenticated = true;
}
@@ -254,7 +254,7 @@ namespace BlackCore
// posted in QAM thread, reply is nullptr if called from another thread
sApp->postToNetwork(request, CApplication::NoLogRequestId, json.toJson(),
{
this, [ = ](QNetworkReply *nwReply)
this, [ = ](QNetworkReply * nwReply)
{
// called in "this" thread
const QScopedPointer<QNetworkReply, QScopedPointerDeleteLater> reply(nwReply);
@@ -297,7 +297,7 @@ namespace BlackCore
void CApiServerConnection::checkExpiry()
{
if (QDateTime::currentDateTimeUtc() > m_expiryLocalUtc.addSecs(-5 * 60))
if (!m_expiryLocalUtc.isValid() || QDateTime::currentDateTimeUtc() > m_expiryLocalUtc.addSecs(-5 * 60))
{
QPointer<CApiServerConnection> myself(this);
this->connectTo(m_username, m_password, m_client, m_networkVersion,

View File

@@ -31,10 +31,10 @@ namespace BlackCore
CLogMessage(this).debug(u"ClientConnection instantiated");
// connect(&m_apiServerConnection, &ApiServerConnection::authenticationFinished, this, &ClientConnection::apiConnectionFinished);
// connect(&m_apiServerConnection, &ApiServerConnection::addCallsignFinished, this, &ClientConnection::addCallsignFinished);
// connect(&m_apiServerConnection, &ApiServerConnection::addCallsignFinished, this, &ClientConnection::addCallsignFinished);
// connect(&m_apiServerConnection, &ApiServerConnection::removeCallsignFinished, this, &ClientConnection::removeCallsignFinished);
connect(m_voiceServerTimer, &QTimer::timeout, this, &CClientConnection::voiceServerHeartbeat);
connect(m_voiceServerTimer, &QTimer::timeout, this, &CClientConnection::voiceServerHeartbeat); // sends heartbeat to server
connect(m_udpSocket, &QUdpSocket::readyRead, this, &CClientConnection::readPendingDatagrams);
connect(m_udpSocket, qOverload<QAbstractSocket::SocketError>(&QUdpSocket::error), this, &CClientConnection::handleSocketError);
}
@@ -57,7 +57,7 @@ namespace BlackCore
this, [ = ](bool authenticated)
{
// callback when connection has been established
if (!myself) { return; }
if (!myself) { return; }
if (authenticated)
{
@@ -70,6 +70,10 @@ namespace BlackCore
CLogMessage(this).info(u"Connected: '%1' to voice server, socket open: %2") << callsign << boolToYesNo(m_udpSocket->isOpen());
}
else
{
m_connection.reset();
}
// Make sure crypto channels etc. are created
m_connection.setConnected(authenticated);
@@ -92,15 +96,15 @@ namespace BlackCore
// TODO emit disconnected(reason)
CLogMessage(this).debug(u"Disconnected client: %1") << reason;
if (! m_connection.getCallsign().isEmpty())
if (!m_connection.getCallsign().isEmpty())
{
m_apiServerConnection->removeCallsign(m_connection.getCallsign());
}
// TODO connectionCheckCancelTokenSource.Cancel(); //Stops connection check loop
// TODO connectionCheckCancelTokenSource.Cancel(); // Stops connection check loop
disconnectFromVoiceServer();
m_apiServerConnection->forceDisconnect();
m_connection.setTokens({});
m_connection.reset();
CLogMessage(this).debug(u"Disconnection complete");
}

View File

@@ -32,7 +32,14 @@ namespace BlackCore
bool CClientConnectionData::isVoiceServerAlive() const
{
return m_lastVoiceServerHeartbeatAckUtc.secsTo(QDateTime::currentDateTimeUtc()) > ServerTimeoutSecs;
return m_lastVoiceServerHeartbeatAckUtc.isValid() &&
m_lastVoiceServerHeartbeatAckUtc.secsTo(QDateTime::currentDateTimeUtc()) > ServerTimeoutSecs;
}
bool CClientConnectionData::isDataServerAlive() const
{
return m_lastDataServerHeartbeatAckUtc.isValid() &&
m_lastDataServerHeartbeatAckUtc.secsTo(QDateTime::currentDateTimeUtc()) > ServerTimeoutSecs;
}
void CClientConnectionData::createCryptoChannels()
@@ -52,13 +59,24 @@ namespace BlackCore
void CClientConnectionData::setTsHeartbeatToNow()
{
m_lastVoiceServerHeartbeatAckUtc = QDateTime::currentDateTimeUtc();
const QDateTime now = QDateTime::currentDateTimeUtc();
m_lastVoiceServerHeartbeatAckUtc = now;
m_lastDataServerHeartbeatAckUtc = now;
}
void CClientConnectionData::reset()
{
m_userName.clear();
m_callsign.clear();
m_authenticatedDateTimeUtc = QDateTime();
m_lastVoiceServerHeartbeatAckUtc = QDateTime();
this->setTokens({});
}
bool CClientConnectionData::voiceServerAlive() const
{
return timeSinceAuthenticationSecs() < ServerTimeoutSecs ||
m_lastVoiceServerHeartbeatAckUtc.secsTo(QDateTime::currentDateTimeUtc()) < ServerTimeoutSecs;
return (m_authenticatedDateTimeUtc.isValid() && timeSinceAuthenticationSecs() < ServerTimeoutSecs) ||
(m_lastVoiceServerHeartbeatAckUtc.isValid() && m_lastVoiceServerHeartbeatAckUtc.secsTo(QDateTime::currentDateTimeUtc()) < ServerTimeoutSecs);
}
} // ns
} // ns

View File

@@ -78,11 +78,14 @@ namespace BlackCore
void setTsHeartbeatToNow();
//! @}
//! Reset all login related data
void reset();
/* TODO
public long VoiceServerBytesSent { get; set; }
public long VoiceServerBytesSent { get; set; }
public long VoiceServerBytesReceived { get; set; }
public long DataServerBytesSent { get; set; }
public long DataServerBytesReceived { get; set; }
public long DataServerBytesSent { get; set; }
public long DataServerBytesReceived { get; set; }
*/
QScopedPointer<Crypto::CCryptoDtoChannel> m_voiceCryptoChannel; //!< used crypto channel
@@ -101,6 +104,7 @@ namespace BlackCore
QDateTime m_authenticatedDateTimeUtc;
QDateTime m_lastVoiceServerHeartbeatAckUtc;
QDateTime m_lastDataServerHeartbeatAckUtc;
bool m_receiveAudio = true; //!< audio?
bool m_connected = false; //!< connected?

View File

@@ -208,6 +208,7 @@ namespace BlackCore
connect(m_voiceClient, &CAfvClient::stoppedAudio, this, &CContextAudioBase::stoppedAudio, Qt::QueuedConnection);
connect(m_voiceClient, &CAfvClient::ptt, this, &CContextAudioBase::ptt, Qt::QueuedConnection);
connect(m_voiceClient, &CAfvClient::connectionStatusChanged, this, &CContextAudioBase::onAfvConnectionStatusChanged, Qt::QueuedConnection);
connect(m_voiceClient, &CAfvClient::afvConnectionFailure, this, &CContextAudioBase::onAfvConnectionFailure, Qt::QueuedConnection);
}
void CContextAudioBase::terminateVoiceClient()
@@ -640,6 +641,12 @@ namespace BlackCore
}
}
void CContextAudioBase::onAfvConnectionFailure(const CStatusMessage &msg)
{
if (!m_voiceClient) { return; }
emit this->voiceClientFailure(msg);
}
bool CContextAudioBase::isRunningWithLocalCore()
{
return sApp && sApp->isLocalContext();

View File

@@ -83,8 +83,10 @@ namespace BlackCore
//! Factory method
static IContextAudio *create(CCoreFacade *runtime, CCoreFacadeConfig::ContextMode mode, BlackMisc::CDBusServer *server, QDBusConnection &connection);
// ------------- only use DBus signals here -------------
signals:
// only use DBus signals here
//! Authentication failed, ....
void voiceClientFailure(const BlackMisc::CStatusMessage &msg);
public slots:
// ------------- DBus ---------------
@@ -285,7 +287,9 @@ namespace BlackCore
//! PTT in voice client received
void ptt(bool active, BlackMisc::Audio::PTTCOM pttcom, const BlackMisc::CIdentifier &identifier);
/** Workaround those must be invisible for DBus
/*
* Workaround those must be invisible for DBus
*
//! VU levels @{
void inputVolumePeakVU (double value);
@@ -298,7 +302,7 @@ namespace BlackCore
//! Client updated from own aicraft data
void updatedFromOwnAircraftCockpit();
** Workaround **/
* end workaround */
// ------------ local signals -------
@@ -338,6 +342,9 @@ namespace BlackCore
//! AFV client connection status changed
void onAfvConnectionStatusChanged(int status);
//! AFV client authentication failed
void onAfvConnectionFailure(const BlackMisc::CStatusMessage &msg);
CActionBind m_actionPtt { BlackMisc::Input::pttHotkeyAction(), BlackMisc::Input::pttHotkeyIcon(), this, &CContextAudioBase::setVoiceTransmissionComActive };
CActionBind m_actionPttCom1 { BlackMisc::Input::pttCom1HotkeyAction(), BlackMisc::Input::pttHotkeyIcon(), this, &CContextAudioBase::setVoiceTransmissionCom1 };
CActionBind m_actionPttCom2 { BlackMisc::Input::pttCom2HotkeyAction(), BlackMisc::Input::pttHotkeyIcon(), this, &CContextAudioBase::setVoiceTransmissionCom2 };

View File

@@ -83,8 +83,12 @@ namespace BlackCore
void CContextAudioProxy::relaySignals(const QString &serviceName, QDBusConnection &connection)
{
/**
bool s = connection.connect(serviceName, IContextAudio::ObjectPath(), IContextAudio::InterfaceName(),
"voiceClientFailure", this, SIGNAL(voiceClientFailure(BlackMisc::CStatusMessage)));
Q_ASSERT(s);
/**
s = connection.connect(serviceName, IContextAudio::ObjectPath(), IContextAudio::InterfaceName(),
"changedAudioVolume", this, SIGNAL(changedAudioVolume(int)));
Q_ASSERT(s);
s = connection.connect(serviceName, IContextAudio::ObjectPath(), IContextAudio::InterfaceName(),

View File

@@ -161,11 +161,11 @@ namespace BlackGui
afv->setRxTx(true, true, true, false);
QPointer<CAudioDeviceVolumeSetupComponent> myself(this);
c = connect(afv, &CAfvClient::connectionStatusChanged, this, [ = ]
c = connect(afv, &CAfvClient::connectionStatusChanged, this, [ = ](CAfvClient::ConnectionStatus status)
{
if (!myself || !sGui || sGui->isShuttingDown()) { return; }
myself->setTransmitReceiveInUiFromVoiceClient();
Q_UNUSED(status)
}, ct);
Q_ASSERT(c);
m_afvConnections.append(c);

View File

@@ -444,6 +444,14 @@ void SwiftGuiStd::onRequestedConsoleMessage(const QString &logMsg, bool clear)
log->appendPlainTextToConsole(logMsg);
}
void SwiftGuiStd::onAudioClientFailure(const CStatusMessage &msg)
{
if (msg.isEmpty()) { return; }
if (!sGui || sGui->isShuttingDown()) { return; }
ui->fr_CentralFrameInside->showOverlayHTMLMessage(msg);
}
void SwiftGuiStd::focusInMainEntryField()
{
ui->comp_MainKeypadArea->focusInEntryField();

View File

@@ -281,6 +281,9 @@ private:
//! UI Console message has been recevied
void onRequestedConsoleMessage(const QString &logMsg, bool clear);
//! Reported issue with the client
void onAudioClientFailure(const BlackMisc::CStatusMessage &msg);
//! Focus in main entry window
void focusInMainEntryField();

View File

@@ -30,6 +30,7 @@
#include "blackcore/webdataservices.h"
#include "blackcore/context/contextnetwork.h"
#include "blackcore/context/contextsimulator.h"
#include "blackcore/context/contextaudio.h"
#include "blacksound/audioutilities.h"
#include "blackmisc/network/networkutils.h"
#include "blackmisc/loghandler.h"
@@ -65,14 +66,14 @@ void SwiftGuiStd::init()
// POST(!) GUI init
Q_ASSERT_X(sGui, Q_FUNC_INFO, "Missing sGui");
Q_ASSERT_X(sGui->getWebDataServices(), Q_FUNC_INFO, "Missing web services");
Q_ASSERT_X(sGui->supportsContexts(), Q_FUNC_INFO, "Missing contexts");
Q_ASSERT_X(sGui->supportsContexts(), Q_FUNC_INFO, "Missing contexts");
if (m_init) { return; }
ui->dw_InfoBarStatus->initialFloating();
this->setVisible(false); // hide all, so no flashing windows during init
m_mwaStatusBar = &m_statusBar;
m_mwaStatusBar = &m_statusBar;
m_mwaOverlayFrame = ui->fr_CentralFrameInside;
m_mwaLogComponent = ui->comp_MainInfoArea->getLogComponent();
sGui->initMainApplicationWidget(this);
@@ -140,6 +141,12 @@ void SwiftGuiStd::init()
Q_ASSERT(s);
s = connect(&m_timerContextWatchdog, &QTimer::timeout, this, &SwiftGuiStd::handleTimerBasedUpdates);
Q_ASSERT(s);
if (sGui->getIContextAudio())
{
s = connect(sGui->getIContextAudio(), &IContextAudio::voiceClientFailure, this, &SwiftGuiStd::onAudioClientFailure, Qt::QueuedConnection);
Q_ASSERT(s);
}
Q_UNUSED(s)
// check if DB data have been loaded