QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
NTRIPSourceTableController.cc
Go to the documentation of this file.
2
3#include <chrono>
4#include <utility>
5
6#include <QtCore/QPointer>
7
8#include "MonotonicClock.h"
9#include "NTRIPHttpSession.h"
10#include "NTRIPSourceTable.h"
11#include "QGCLoggingCategory.h"
12#include "RuntimeScheduler.h"
13#include "ScheduledTask.h"
14
15QGC_LOGGING_CATEGORY(NTRIPSourceTableControllerLog, "GPS.NTRIPSourceTableController")
16
18{
19 FetchAttempt(RuntimeScheduler* scheduler, QObject* context)
20 : timeout(scheduler, context)
21 {}
22
23 QPointer<NTRIPHttpSession> session;
25 QByteArray body;
26};
27
29 : QObject(parent)
30 , _model(new NTRIPSourceTableModel(this))
31 , _sortedModel(new NTRIPSourceTableSortModel(_model, this))
32 , _scheduler(RuntimeScheduler::orDefault(scheduler, this))
33{}
34
36{
37 // Detach the session before child cleanup so its callbacks cannot reach a destroyed controller.
38 _abortFetch();
39}
40
42{
43 return _sortedModel;
44}
45
46void NTRIPSourceTableController::fetch(const NTRIPConnectionConfig& config, const QGeoCoordinate& sortCoord)
47{
48 auto casterConfig = config;
49 casterConfig.mountpoint.clear();
50 const bool sameCaster = casterConfig == _lastFetchConfig;
51 const QString error = config.validationError();
52
53 if (error.isEmpty() && _attempt && sameCaster) {
54 _sortCoord = sortCoord;
55 return;
56 }
57
58 _abortFetch();
59 if (!error.isEmpty()) {
60 _onFetchError(error);
61 return;
62 }
63
64 if (_model->count() > 0 && _cacheStoredAtUs && sameCaster) {
65 const auto nowUs = _scheduler->nowUs();
66 if (MonotonicClock::fresh(*_cacheStoredAtUs, nowUs, CACHE_TTL)) {
67 qCDebug(NTRIPSourceTableControllerLog) << "Source table cache hit, age:"
68 << std::chrono::duration_cast<std::chrono::milliseconds>(
69 *MonotonicClock::age(*_cacheStoredAtUs, nowUs));
70 _sortCoord = sortCoord;
71 _model->updateDistances(_sortCoord);
72 _setFetchState(FetchStatus::Success, QString());
73 return;
74 }
75 }
76
77 _cacheStoredAtUs.reset();
78 _sortCoord = sortCoord;
79 const QString previousWarning = securityWarning();
80 _lastFetchConfig = casterConfig;
81
82 if (casterConfig.sendsCredentialsInClear()) {
83 qCWarning(NTRIPSourceTableControllerLog) << "Sending source-table credentials without TLS";
84 }
85 _startFetch();
86 if (securityWarning() != previousWarning) {
88 }
89 // The session reports failures to open asynchronously or, without a TLS backend, already from _startFetch().
90 if (_attempt) {
91 _setFetchState(FetchStatus::InProgress, QString());
92 }
93}
94
95void NTRIPSourceTableController::_startFetch()
96{
97 _attempt = std::make_unique<FetchAttempt>(_scheduler, this);
98 auto* session = new NTRIPHttpSession(this, _scheduler, NTRIPHttpPurpose::SourceTable);
99 _attempt->session = session;
100 // _abortFetch() retires the session, which disconnects it, so these run only for the live attempt.
102 connect(session, &NTRIPHttpSession::responseStarted, this, &NTRIPSourceTableController::_armFetchTimeout);
103 connect(session, &NTRIPHttpSession::bodyReceived, this, &NTRIPSourceTableController::_readReply);
104 connect(session, &NTRIPHttpSession::failed, this,
105 [this](const NTRIPFailure& failure) { _finishFetch(failure.detail); });
106 connect(session, &NTRIPHttpSession::finished, this,
107 [this]() { _finishFetch(tr("Response does not contain a valid source table")); });
108 _armFetchTimeout();
109 if (const QString openError = session->open(_lastFetchConfig); !openError.isEmpty()) {
110 _finishFetch(openError);
111 }
112}
113
114void NTRIPSourceTableController::_armFetchTimeout()
115{
116 // An inactivity timeout: a large table on a slow link keeps the fetch alive while bytes arrive.
117 _attempt->timeout.schedule(FETCH_TIMEOUT, [this]() {
118 // A refusal the caster is still explaining ends on its own deadline.
119 if (!_attempt->session || !_attempt->session->awaitingErrorBody()) {
120 _finishFetch(tr("Source table request timed out"));
121 }
122 });
123}
124
125void NTRIPSourceTableController::_readReply(const QByteArray& bytes)
126{
127 _armFetchTimeout();
128 if (_attempt->body.size() + bytes.size() >= MAX_SOURCE_TABLE_BYTES) {
129 _finishFetch(tr("Source table too large (exceeds %1 MB)").arg(MAX_SOURCE_TABLE_BYTES / (1024 * 1024)));
130 return;
131 }
132 const qsizetype checkedSize = _attempt->body.size();
133 _attempt->body += bytes;
134 // A complete table ends the fetch before any framing error in the bytes that follow it.
135 if (ntripSourceTableComplete(_attempt->body, checkedSize)) {
136 _finishFetch();
137 }
138}
139
140void NTRIPSourceTableController::_finishFetch(const QString& error)
141{
142 if (!_attempt) {
143 return;
144 }
145 const QByteArray body = _attempt->body;
146 _abortFetch();
147 if (error.isEmpty()) {
148 _onSourceTableReceived(QString::fromUtf8(body));
149 } else {
150 _onFetchError(error);
151 }
152}
153
154void NTRIPSourceTableController::_onSourceTableReceived(const QString& table)
155{
156 _model->parseSourceTable(table, _sortCoord);
157 _cacheStoredAtUs = _scheduler->nowUs();
158 _setFetchState(FetchStatus::Success, QString());
159}
160
161void NTRIPSourceTableController::_onFetchError(const QString& error)
162{
163 _cacheStoredAtUs.reset();
164 _model->clear();
165 _setFetchState(FetchStatus::Error, error);
166}
167
168void NTRIPSourceTableController::_setFetchState(FetchStatus status, const QString& error)
169{
170 const bool statusChanged = std::exchange(_fetchStatus, status) != status;
171 const bool errorChanged = std::exchange(_fetchError, error) != error;
172 if (errorChanged) {
173 emit fetchErrorChanged();
174 }
175 if (statusChanged) {
176 emit fetchStatusChanged();
177 }
178}
179
180void NTRIPSourceTableController::_abortFetch()
181{
182 if (!_attempt) {
183 return;
184 }
185 // retire() disconnects the session before aborting it, so the abort cannot call back into this controller.
186 if (_attempt->session) {
187 _attempt->session->retire();
188 }
189 _attempt.reset();
190}
191
193{
194 _abortFetch();
195 if (_fetchStatus == FetchStatus::InProgress) {
196 _setFetchState(FetchStatus::Idle, QString());
197 }
198}
Config config
bool ntripSourceTableComplete(QByteArrayView body, qsizetype checkedSize)
Error error
#define QGC_LOGGING_CATEGORY(name, categoryStr)
void certificatePinned(const QString &pin)
A newly trusted self-signed certificate, as NTRIPConnectionConfig::pinnedCertificate; precedes the re...
void finished()
The response ended where its HTTP framing allows.
void bodyReceived(const QByteArray &body, qint64 receivedAtMs)
Body bytes, with the scheduler time at which the bytes carrying them arrived.
void responseStarted()
The caster accepted the request; its body follows.
void failed(const NTRIPFailure &failure)
A socket, TLS or HTTP failure, including an error status with the caster's explanation.
void fetch(const NTRIPConnectionConfig &config, const QGeoCoordinate &sortCoord={})
static constexpr std::chrono::milliseconds CACHE_TTL
void certificatePinned(const QString &pin)
A self-signed caster certificate was trusted on first use; the owner persists the pin.
static constexpr qint64 MAX_SOURCE_TABLE_BYTES
NTRIPSourceTableController(QObject *parent=nullptr, RuntimeScheduler *scheduler=nullptr)
QAbstractItemModel * mountpointModel() const
static constexpr std::chrono::milliseconds FETCH_TIMEOUT
QString securityWarning() const
Set while the latest fetch sends caster credentials without TLS.
void updateDistances(const QGeoCoordinate &from)
Recompute distances; one dataChanged for DistanceKmRole spans the rows whose distance changed.
void parseSourceTable(const QString &raw, const QGeoCoordinate &from={})
Publish the parsed rows and their distances in a single reset.
virtual quint64 nowUs() const =0
constexpr std::optional< std::chrono::duration< Rep, Period > > age(std::chrono::duration< Rep, Period > timestamp, std::chrono::duration< Rep, Period > now)
Age of timestamp at now, both in the same unit; empty for a missing or future timestamp.
constexpr bool fresh(uint64_t timestampUs, uint64_t nowUs, std::chrono::microseconds lifetime)
Whether a receipt is younger than lifetime.
QString validationError() const
QString detail
Definition NTRIPError.h:40
FetchAttempt(RuntimeScheduler *scheduler, QObject *context)