5#include <QtCore/QPointer>
6#include <QtNetwork/QSslError>
7#include <QtNetwork/QSslSocket>
8#include <QtNetwork/QTcpSocket>
22 , _errorBodyTask(_scheduler, this)
28 if (_socket || _ended) {
32 if (!request.
error.isEmpty()) {
35 _request = std::move(request.
bytes);
36 const auto port =
static_cast<quint16
>(
config.port);
37 _endpoint = QStringLiteral(
"%1:%2").arg(
config.host).arg(port);
38 _pinnedCertificate =
config.pinnedCertificate;
39 _allowSelfSignedCerts =
config.allowSelfSignedCerts;
40 qCDebug(NTRIPHttpSessionLog) <<
"connectToHost" <<
config.host <<
":" <<
config.port <<
"TLS" <<
config.useTls;
42 auto* socket =
new QTcpSocket(
this);
44 socket->connectToHost(
config.host, port);
47 auto* socket =
new QSslSocket(
this);
51 socket->connectToHostEncrypted(
config.host, port,
config.host);
55void NTRIPHttpSession::_attach(QTcpSocket* socket)
57 socket->setParent(
this);
61 connect(socket, &QTcpSocket::errorOccurred,
this, [
this](QAbstractSocket::SocketError code) {
63 if (_ended || !_socket || code == QAbstractSocket::RemoteHostClosedError) {
66 const QString message = _socket->errorString();
67 qCWarning(NTRIPHttpSessionLog) <<
"Socket error code:" << code <<
"msg:" << message;
69 const bool tls = code == QAbstractSocket::SslHandshakeFailedError || code == QAbstractSocket::SslInternalError;
72 connect(socket, &QTcpSocket::readyRead,
this, &NTRIPHttpSession::_read);
73 connect(socket, &QTcpSocket::disconnected,
this, &NTRIPHttpSession::_read);
75 auto* sslSocket = qobject_cast<QSslSocket*>(socket);
77 connect(socket, &QTcpSocket::connected,
this, &NTRIPHttpSession::_establish);
80 connect(sslSocket, &QSslSocket::encrypted,
this, &NTRIPHttpSession::_establish);
81 connect(sslSocket, &QSslSocket::sslErrors,
this,
82 [
this, sslSocket](
const QList<QSslError>& errors) { _verifyCertificate(sslSocket, errors); });
85void NTRIPHttpSession::_verifyCertificate(QSslSocket* socket,
const QList<QSslError>& errors)
91 for (
const QSslError&
error : errors) {
92 qCWarning(NTRIPHttpSessionLog) <<
"TLS error:" <<
error.errorString();
95 const QSslCertificate certificate = socket->peerCertificate();
97 _fail(NTRIPError::SslError,
messages.join(QStringLiteral(
"; ")));
100 if (!_allowSelfSignedCerts) {
101 qCWarning(NTRIPHttpSessionLog)
102 <<
"Rejecting self-signed certificate (enable 'Accept self-signed certificates' to allow)";
103 _fail(NTRIPError::SslError, tr(
"Self-signed certificate rejected. Enable 'Accept self-signed "
104 "certificates' in NTRIP settings to allow."));
108 if (pin == _pinnedCertificate) {
109 qCDebug(NTRIPHttpSessionLog) <<
"Accepting the pinned self-signed certificate";
110 }
else if (_pinnedCertificate.startsWith(_endpoint + QLatin1Char(
'|'))) {
111 qCWarning(NTRIPHttpSessionLog) <<
"Rejecting self-signed certificate that differs from the pinned one";
112 _fail(NTRIPError::SslError,
113 tr(
"The caster's certificate changed since it was first trusted. If the change is expected, turn "
114 "'Accept self-signed certificates' off and on again to trust the new certificate."));
117 qCWarning(NTRIPHttpSessionLog) <<
"Accepting self-signed certificate (user opted in)";
121 socket->ignoreSslErrors(errors);
124void NTRIPHttpSession::_establish()
126 if (_ended || !_socket) {
129 qCDebug(NTRIPHttpSessionLog) <<
"Socket connected"
130 <<
"local" << _socket->localAddress().toString() <<
":" << _socket->localPort()
131 <<
"-> peer" << _socket->peerAddress().toString() <<
":" << _socket->peerPort();
133 _socket->setSocketOption(QAbstractSocket::KeepAliveOption, 1);
134 _socket->setSocketOption(QAbstractSocket::LowDelayOption, 1);
135 if (
const QString pin = std::exchange(_newPin, {}); !pin.isEmpty()) {
136 const QPointer<NTRIPHttpSession> self(
this);
138 if (!self || _ended || !_socket) {
142 if (!
write(_request)) {
143 _fail(NTRIPError::SocketError, tr(
"Socket did not accept the complete NTRIP write"));
146 qCDebug(NTRIPHttpSessionLog) <<
"HTTP request sent to" << _endpoint;
151 const auto socket = _socket;
152 if (_ended || !socket) {
155 return socket->write(bytes) == bytes.size();
160 return !_ended && _socket && _socket->state() == QAbstractSocket::ConnectedState;
163void NTRIPHttpSession::_read()
165 const auto socket = _socket;
166 if (_ended || _reading || !socket) {
169 const QPointer<NTRIPHttpSession> self(
this);
172 while (current && socket->bytesAvailable() > 0) {
173 const qint64 receivedAtMs = _scheduler->
nowMs();
175 if (bytes.isEmpty()) {
178 _receive(bytes, receivedAtMs);
179 current = self && !_ended;
185 if (current && socket->state() == QAbstractSocket::UnconnectedState) {
190void NTRIPHttpSession::_receive(QByteArrayView bytes, qint64 receivedAtMs,
const QDateTime& utcNow)
193 _publish(_decoder.
feed(bytes, utcNow), receivedAtMs);
200 const QPointer<NTRIPHttpSession> self(
this);
203 if (!self || _ended) {
207 if (!result.
body.isEmpty()) {
209 if (!self || _ended) {
224void NTRIPHttpSession::_finishResponse()
227 if (!_ended && !_reading) {
232void NTRIPHttpSession::_fail(
NTRIPError code,
const QString& message)
246void NTRIPHttpSession::_end()
255 if (
const auto socket = _socket) {
262 if (QObject* owner = parent()) {
#define QGC_LOGGING_CATEGORY(name, categoryStr)
std::array< MessageId, 4 > messages
bool awaitingErrorBody() const
Result feed(QByteArrayView bytes, const QDateTime &utcNow)
static constexpr qint64 READ_CHUNK_BYTES
void certificatePinned(const QString &pin)
A newly trusted self-signed certificate, as NTRIPConnectionConfig::pinnedCertificate; precedes the re...
static constexpr qint64 READ_BUFFER_BYTES
QString open(const NTRIPConnectionConfig &config)
static constexpr std::chrono::milliseconds ERROR_BODY_TIMEOUT
bool write(const QByteArray &bytes)
False unless the socket accepted every byte; the owner decides how to report it.
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 abort()
Ends the connection without further signals.
void failed(const NTRIPFailure &failure)
A socket, TLS or HTTP failure, including an error status with the caster's explanation.
bool schedule(std::chrono::microseconds delay, RuntimeScheduler::Callback callback)
QString certificatePin(const QString &endpoint, const QSslCertificate &certificate)
Binds the certificate's SHA-256 digest to one caster endpoint ("host:port").
bool isSelfSignedOnly(const QList< QSslError > &errors)
Callers must also require opt-in and ignore only the supplied error objects.
QSslConfiguration createSslConfig()
The default TLS configuration restricted to TLS 1.2 or later.
QByteArray body
Retains valid prefixes when subsequent framing fails.
std::optional< NTRIPFailure > failure
static NTRIPHttpRequest build(const NTRIPConnectionConfig &config, NTRIPHttpPurpose purpose=NTRIPHttpPurpose::Corrections)