Improve NTRIP client

This commit is contained in:
Carles Fernandez
2026-08-15 09:09:10 +02:00
parent 244cf0bae1
commit 00ccbf4fbd
9 changed files with 602 additions and 97 deletions
@@ -89,6 +89,8 @@ public:
Ntrip_Credential_Guard(const Ntrip_Credential_Guard&) = delete;
Ntrip_Credential_Guard& operator=(const Ntrip_Credential_Guard&) = delete;
Ntrip_Credential_Guard(Ntrip_Credential_Guard&&) = delete;
Ntrip_Credential_Guard& operator=(Ntrip_Credential_Guard&&) = delete;
private:
Pvt_Conf* d_configuration;
@@ -128,6 +128,50 @@ namespace wht = boost;
namespace wht = std;
#endif
namespace
{
/* console tag for the RTKLIB solution status; empty for single-point and
no-solution so that the default output is unchanged */
std::string solution_status_tag(int solution_status)
{
switch (solution_status)
{
case SOLQ_FIX:
return " [RTK FIXED]";
case SOLQ_FLOAT:
return " [RTK FLOAT]";
case SOLQ_SBAS:
return " [SBAS]";
case SOLQ_DGPS:
return " [DGNSS]";
case SOLQ_PPP:
return " [PPP]";
default:
return "";
}
}
const std::string& solution_status_color(int solution_status)
{
switch (solution_status)
{
case SOLQ_FIX:
return TEXT_BOLD_GREEN;
case SOLQ_FLOAT:
case SOLQ_DGPS:
return TEXT_BOLD_YELLOW;
case SOLQ_SBAS:
return TEXT_BOLD_CYAN;
case SOLQ_PPP:
return TEXT_BOLD_MAGENTA;
default:
return TEXT_RESET;
}
}
} // namespace
rtklib_pvt_gs_sptr rtklib_make_pvt_gs(uint32_t nchannels,
const Pvt_Conf& conf_,
const rtk_t& rtk,
@@ -202,6 +246,7 @@ rtklib_pvt_gs::rtklib_pvt_gs(uint32_t nchannels,
d_pvt_errors_counter(0),
d_last_ntrip_client_state(-1),
d_last_fixed_base_status(-1),
d_last_solution_status(SOLQ_NONE),
d_dump(conf_.dump),
d_dump_mat(conf_.dump_mat && conf_.dump),
d_rinex_output_enabled(conf_.rinex_output_enabled),
@@ -2433,6 +2478,84 @@ void rtklib_pvt_gs::report_fixed_base_status()
}
int rtklib_pvt_gs::displayed_solution_status() const
{
const int status = d_user_pvt_solver->pvt_sol.stat;
/* RTKLIB stamps SOLQ_PPP whenever the PPP filter runs, even on broadcast
orbits and clocks; only claim PPP when precise products are loaded */
if (status == SOLQ_PPP && !d_user_pvt_solver->has_precise_ephemeris())
{
return SOLQ_SINGLE;
}
return status;
}
void rtklib_pvt_gs::report_solution_status()
{
const int status = displayed_solution_status();
if (status == d_last_solution_status)
{
return;
}
const int previous_status = d_last_solution_status;
d_last_solution_status = status;
/* announce only transitions involving a differential, SBAS or PPP state;
plain single-point operation keeps the classic console output */
if (solution_status_tag(previous_status).empty() && solution_status_tag(status).empty())
{
return;
}
std::ostringstream report;
switch (status)
{
case SOLQ_FIX:
report << "RTK ambiguities fixed (AR ratio " << std::fixed << std::setprecision(1)
<< d_user_pvt_solver->pvt_sol.ratio << ", threshold "
<< d_user_pvt_solver->pvt_sol.thres << ")";
break;
case SOLQ_FLOAT:
if (previous_status == SOLQ_FIX)
{
report << "RTK fix lost: float solution (AR ratio " << std::fixed
<< std::setprecision(1) << d_user_pvt_solver->pvt_sol.ratio << ")";
}
else
{
report << "RTK float solution";
}
break;
case SOLQ_DGPS:
report << "RTK degraded to code differential (DGNSS)";
break;
case SOLQ_SBAS:
report << "SBAS-corrected solution";
break;
case SOLQ_PPP:
report << "PPP solution";
break;
default:
/* fell back to single-point (or lost the solution) from a labeled state */
if (previous_status == SOLQ_SBAS)
{
report << "SBAS corrections lost: single-point solution";
}
else if (previous_status == SOLQ_PPP)
{
report << "PPP lost: single-point solution";
}
else
{
report << "RTK lost: single-point solution";
}
break;
}
const std::string& color = solution_status_tag(status).empty() ? TEXT_BOLD_YELLOW : solution_status_color(status);
std::cout << color << report.str() << TEXT_RESET << std::endl;
LOG(INFO) << "Solution status change: " << report.str();
}
int rtklib_pvt_gs::work(int noutput_items, gr_vector_const_void_star& input_items,
gr_vector_void_star& output_items __attribute__((unused)))
{
@@ -2845,6 +2968,7 @@ int rtklib_pvt_gs::work(int noutput_items, gr_vector_const_void_star& input_item
if (flag_pvt_valid == true)
{
report_solution_status();
// experimental VTL tests
// send tracking command
// const std::shared_ptr<TrackingCmd> trk_cmd_test = std::make_shared<TrackingCmd>(TrackingCmd());
@@ -2955,7 +3079,8 @@ int rtklib_pvt_gs::work(int noutput_items, gr_vector_const_void_star& input_item
ss << " is Lat = " << d_user_pvt_solver->get_latitude()
<< " [deg], Long = " << d_user_pvt_solver->get_longitude()
<< " [deg], Height = " << d_user_pvt_solver->get_height()
<< " [m], with GDOP = " << d_user_pvt_solver->get_gdop();
<< " [m], with GDOP = " << d_user_pvt_solver->get_gdop()
<< solution_status_tag(displayed_solution_status());
std::cout << ss.str() << std::endl;
d_end = std::chrono::system_clock::now();
std::chrono::duration<double> elapsed_seconds = d_end - d_start;
@@ -3054,13 +3179,16 @@ int rtklib_pvt_gs::work(int noutput_items, gr_vector_const_void_star& input_item
std::cout.setf(std::ios::fixed, std::ios::floatfield);
auto* facet = new boost::posix_time::time_facet("%Y-%b-%d %H:%M:%S.%f %z");
std::cout.imbue(std::locale(std::cout.getloc(), facet));
const int solq = displayed_solution_status();
const std::string solq_tag = solution_status_tag(solq);
const std::string solq_txt = solq_tag.empty() ? "" : solution_status_color(solq) + solq_tag;
std::cout
<< TEXT_BOLD_GREEN
<< "Position at " << time_solution << UTC_solution_str
<< " using " << d_user_pvt_solver->get_num_valid_observations() << " observations is Lat = "
<< std::fixed << std::setprecision(6) << d_user_pvt_solver->get_latitude()
<< " [deg], Long = " << d_user_pvt_solver->get_longitude() << " [deg], Height = "
<< std::fixed << std::setprecision(2) << d_user_pvt_solver->get_height() << std::setprecision(ss) << " [m]" << TEXT_RESET << std::endl;
<< std::fixed << std::setprecision(2) << d_user_pvt_solver->get_height() << std::setprecision(ss) << " [m]" << solq_txt << TEXT_RESET << std::endl;
DLOG(INFO) << "RX clock offset: " << d_user_pvt_solver->get_time_offset_s() << "[s]";
std::cout
@@ -166,6 +166,10 @@ private:
void report_fixed_base_status();
void report_solution_status();
int displayed_solution_status() const;
std::map<int, Gnss_Synchro> interpolate_observables(const std::map<int, Gnss_Synchro>& observables_map_t0,
const std::map<int, Gnss_Synchro>& observables_map_t1,
double rx_time_s);
@@ -285,6 +289,7 @@ private:
uint32_t d_pvt_errors_counter;
int d_last_ntrip_client_state;
int d_last_fixed_base_status;
int d_last_solution_status;
bool d_dump;
const bool d_dump_mat;
+23 -7
View File
@@ -171,6 +171,8 @@ public:
Stream_Owner(const Stream_Owner&) = delete;
Stream_Owner& operator=(const Stream_Owner&) = delete;
Stream_Owner(Stream_Owner&&) = delete;
Stream_Owner& operator=(Stream_Owner&&) = delete;
bool open(const std::string& path, const std::string& hostname,
const std::string& username, const std::string& password)
@@ -206,9 +208,8 @@ public:
auto* ntrip = static_cast<ntrip_t*>(d_stream.port);
std::memset(ntrip->user, 0, sizeof(ntrip->user));
std::memset(ntrip->passwd, 0, sizeof(ntrip->passwd));
// RTKLIB initializes the socket descriptor to zero and may
// leave a closed descriptor behind after a failed connection.
// Prevent strclose() from closing stdin or a reused descriptor.
// Keep unopened and disconnected transports explicitly invalid
// before handing ownership back to RTKLIB's stream cleanup.
if (ntrip->tcp != nullptr && ntrip->tcp->svr.state < 1)
{
ntrip->tcp->svr.sock = -1;
@@ -232,11 +233,16 @@ private:
class Rtcm_Owner
{
public:
// The analyzer loses the unique_ptr member's ownership while following
// init_rtcm(), whose failure paths also release all partial allocations.
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
Rtcm_Owner()
: d_rtcm(new rtcm_t()),
d_initialized(init_rtcm(d_rtcm.get()) != 0)
: d_rtcm(new rtcm_t{})
{
std::memset(d_rtcm.get(), 0, sizeof(rtcm_t));
// init_rtcm() populates pointers owned by the value-initialized
// control structure, so it must run after d_rtcm is constructed.
// NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer)
d_initialized = init_rtcm(d_rtcm.get()) != 0;
}
~Rtcm_Owner()
@@ -249,6 +255,8 @@ public:
Rtcm_Owner(const Rtcm_Owner&) = delete;
Rtcm_Owner& operator=(const Rtcm_Owner&) = delete;
Rtcm_Owner(Rtcm_Owner&&) = delete;
Rtcm_Owner& operator=(Rtcm_Owner&&) = delete;
bool initialized() const
{
@@ -356,6 +364,9 @@ void resolve_ipv4_hostname(const std::shared_ptr<Host_Resolution_State>& state,
} // namespace
// The layout follows mutex-protected state groups. Saving a few padding bytes
// in this single-instance class is not worth separating each lock from its data.
// NOLINTNEXTLINE(clang-analyzer-optin.performance.Padding)
class Ntrip_Rtcm_Client::Impl
{
public:
@@ -373,6 +384,11 @@ public:
secure_clear(&d_config.password);
}
Impl(const Impl&) = delete;
Impl& operator=(const Impl&) = delete;
Impl(Impl&&) = delete;
Impl& operator=(Impl&&) = delete;
bool start()
{
std::lock_guard<std::mutex> lifecycle_lock(d_lifecycle_mutex);
@@ -689,7 +705,7 @@ private:
// shutdown wait. Keep the exceptional joinable
// handle alive; its task owns no client state
// and still reports through the shared result.
(void)resolver.release();
(void)resolver.release(); // NOLINT(bugprone-unused-return-value)
}
}
catch (...)
+7
View File
@@ -140,6 +140,13 @@ public:
Rtklib_Fixed_Base_Status get_fixed_base_status() const;
double get_fixed_base_age_s() const;
std::size_t get_fixed_base_common_satellites() const;
//! true only if precise ephemeris and clock products are loaded; used to
//! distinguish real PPP from PPP-mode processing with broadcast products
inline bool has_precise_ephemeris() const
{
return d_nav_data.ne > 0 && d_nav_data.nc > 0;
}
void store_has_data(const Galileo_HAS_data& new_has_data);
void clear_has_corrections();
void update_has_corrections(const std::map<int, Gnss_Synchro>& obs_map);
+22 -19
View File
@@ -1203,25 +1203,28 @@ typedef struct
typedef struct
{ /* ntrip control type */
int state; /* state (0:close,1:wait,2:connect) */
int type; /* type (0:server,1:client) */
int version; /* NTRIP client version (1 or 2) */
int tls_enabled; /* TLS transport enabled */
int chunked; /* HTTP chunked transfer encoding */
int chunk_state; /* chunk parser state (0:size,1:data,2:crlf) */
unsigned int chunk_remaining; /* bytes left in current HTTP chunk */
int nb; /* response buffer size */
char url[256]; /* url for proxy */
char host[256]; /* caster hostname (not proxy hostname) */
char port[16]; /* caster port */
char mntpnt[256]; /* mountpoint */
char user[256]; /* user */
char passwd[256]; /* password */
char str[NTRIP_MAXSTR]; /* mountpoint string for server */
unsigned char buff[NTRIP_MAXRSP]; /* response buffer */
tcpcli_t *tcp; /* tcp client */
void *tls_ctx; /* opaque Rtklib_Tls_Client pointer */
{ /* ntrip control type */
int state; /* state (0:close,1:wait,2:connect) */
int type; /* type (0:server,1:client) */
int version; /* NTRIP client version (1 or 2) */
int tls_enabled; /* TLS transport enabled */
int chunked; /* HTTP chunked transfer encoding */
int chunk_state; /* chunk parser state (0:size,1:data,2:crlf) */
unsigned int chunk_remaining; /* bytes left in current HTTP chunk */
int nb; /* response buffer size */
int request_length; /* buffered NTRIP request size */
int request_offset; /* bytes of buffered request already sent */
char url[256]; /* url for proxy */
char host[256]; /* caster hostname (not proxy hostname) */
char port[16]; /* caster port */
char mntpnt[256]; /* mountpoint */
char user[256]; /* user */
char passwd[256]; /* password */
char str[NTRIP_MAXSTR]; /* mountpoint string for server */
unsigned char buff[NTRIP_MAXRSP]; /* response buffer */
unsigned char request_buff[NTRIP_MAXRSP]; /* persistent non-blocking request buffer */
tcpcli_t *tcp; /* tcp client */
void *tls_ctx; /* opaque Rtklib_Tls_Client pointer */
} ntrip_t;
+406 -69
View File
@@ -42,6 +42,7 @@
#include <cerrno>
#include <cinttypes>
#include <climits>
#include <cstddef>
#include <cstdlib>
#include <cstring>
#include <deque>
@@ -983,6 +984,7 @@ int gentcp(tcp_t *tcp, int type, char *msg)
}
if (!setsock(tcp->sock, msg))
{
tcp->sock = -1;
tcp->state = -1;
return 0;
}
@@ -1002,6 +1004,7 @@ int gentcp(tcp_t *tcp, int type, char *msg)
std::snprintf(msg, MAXSTRMSG, "bind error (%d) : %d", errsock(), tcp->port);
tracet(1, "gentcp: bind error port=%d err=%d\n", tcp->port, errsock());
closesocket(tcp->sock);
tcp->sock = -1;
tcp->state = -1;
return 0;
}
@@ -1015,6 +1018,7 @@ int gentcp(tcp_t *tcp, int type, char *msg)
std::snprintf(msg, MAXSTRMSG, "address error (%s)", tcp->saddr);
tracet(1, "gentcp: gethostbyname error addr=%s err=%d\n", tcp->saddr, errsock());
closesocket(tcp->sock);
tcp->sock = -1;
tcp->state = 0;
tcp->tcon = ticonnect;
tcp->tdis = tickget();
@@ -1036,7 +1040,11 @@ int gentcp(tcp_t *tcp, int type, char *msg)
void discontcp(tcp_t *tcp, int tcon)
{
tracet(3, "discontcp: sock=%d tcon=%d\n", tcp->sock, tcon);
closesocket(tcp->sock);
if (tcp->sock >= 0)
{
closesocket(tcp->sock);
tcp->sock = -1;
}
tcp->state = 0;
tcp->tcon = tcon;
tcp->tdis = tickget();
@@ -1322,6 +1330,7 @@ int consock(tcpcli_t *tcpcli, char *msg)
std::snprintf(msg, MAXSTRMSG, "connect error (%d)", err);
tracet(1, "consock: connect error sock=%d err=%d\n", tcpcli->svr.sock, err);
closesocket(tcpcli->svr.sock);
tcpcli->svr.sock = -1;
tcpcli->svr.state = 0;
return 0;
}
@@ -1352,6 +1361,7 @@ tcpcli_t *opentcpcli(const char *path, char *msg)
return nullptr;
}
*tcpcli = tcpcli0;
tcpcli->svr.sock = -1;
decodetcppath(path, tcpcli->svr.saddr, port, nullptr, nullptr, nullptr, nullptr);
if (sscanf(port, "%d", &tcpcli->svr.port) < 1)
{
@@ -1371,7 +1381,10 @@ tcpcli_t *opentcpcli(const char *path, char *msg)
void closetcpcli(tcpcli_t *tcpcli)
{
tracet(3, "closetcpcli: sock=%d\n", tcpcli->svr.sock);
closesocket(tcpcli->svr.sock);
if (tcpcli->svr.sock >= 0)
{
closesocket(tcpcli->svr.sock);
}
free(tcpcli);
}
@@ -1524,6 +1537,19 @@ Rtklib_Tls_Client *ntrip_tls(ntrip_t *ntrip)
return static_cast<Rtklib_Tls_Client *>(ntrip->tls_ctx);
}
void ntrip_clear_request(ntrip_t *ntrip)
{
/* Requests can contain caster credentials. Use volatile stores so clearing
* the persistent retry buffer is not optimized away. */
volatile unsigned char *request = ntrip->request_buff;
for (std::size_t i = 0; i < sizeof(ntrip->request_buff); ++i)
{
request[i] = 0;
}
ntrip->request_length = 0;
ntrip->request_offset = 0;
}
void ntrip_reset_tls(ntrip_t *ntrip)
{
if (ntrip->tls_ctx)
@@ -1534,6 +1560,7 @@ void ntrip_reset_tls(ntrip_t *ntrip)
void ntrip_disconnect(ntrip_t *ntrip, int tcon)
{
ntrip_clear_request(ntrip);
ntrip_reset_tls(ntrip);
discontcp(&ntrip->tcp->svr, tcon);
}
@@ -1598,6 +1625,71 @@ int ntrip_write_transport(ntrip_t *ntrip, const unsigned char *buff, int n, char
return ns;
}
bool ntrip_prepare_request(ntrip_t *ntrip, const std::string &request, char *msg)
{
if (request.empty() || request.size() > sizeof(ntrip->request_buff))
{
std::snprintf(msg, MAXSTRMSG, "NTRIP request too large");
ntrip->state = -1;
ntrip_disconnect(ntrip, -1);
return false;
}
std::memcpy(ntrip->request_buff, request.data(), request.size());
ntrip->request_length = static_cast<int>(request.size());
ntrip->request_offset = 0;
return true;
}
int ntrip_flush_request(ntrip_t *ntrip, char *msg)
{
if (ntrip->request_length <= 0 ||
ntrip->request_offset < 0 ||
ntrip->request_offset > ntrip->request_length)
{
std::snprintf(msg, MAXSTRMSG, "invalid NTRIP request state");
ntrip->state = -1;
ntrip_disconnect(ntrip, -1);
return 0;
}
const int remaining = ntrip->request_length - ntrip->request_offset;
const int ns = ntrip_write_transport(ntrip,
ntrip->request_buff + ntrip->request_offset, remaining, msg);
/* A fatal transport error resets the request in ntrip_disconnect(). Plain
* TCP errors also leave the socket disconnected. In both cases, rebuild
* and resend the complete request on the next connection. */
if (ntrip->tcp->svr.state != 2)
{
ntrip_clear_request(ntrip);
return 0;
}
if (ns <= 0)
{
/* The non-blocking transport would block. Keeping both the buffer
* address and the remaining length unchanged is required when an
* OpenSSL SSL_write() call returns WANT_READ or WANT_WRITE. */
return 0;
}
if (ns > remaining)
{
std::snprintf(msg, MAXSTRMSG, "invalid NTRIP write result");
ntrip->state = -1;
ntrip_disconnect(ntrip, -1);
return 0;
}
ntrip->request_offset += ns;
if (ntrip->request_offset < ntrip->request_length)
{
return 0;
}
ntrip_clear_request(ntrip);
return 1;
}
void ntrip_consume_buffer(ntrip_t *ntrip, int n)
{
if (n <= 0)
@@ -1677,8 +1769,9 @@ int read_ntrip_chunked(ntrip_t *ntrip, unsigned char *buff, int n, char *msg)
std::memcpy(size_text, ntrip->buff, eol);
size_text[eol] = '\0';
char *end = nullptr;
const unsigned long chunk_size = std::strtoul(size_text, &end, 16);
if (end == size_text || chunk_size > UINT_MAX)
errno = 0;
const uint64_t chunk_size = std::strtoul(size_text, &end, 16);
if (end == size_text || errno == ERANGE || chunk_size > UINT_MAX)
{
std::snprintf(msg, MAXSTRMSG, "invalid HTTP chunk");
ntrip_disconnect(ntrip, ntrip->tcp->tirecon);
@@ -1706,15 +1799,16 @@ int read_ntrip_chunked(ntrip_t *ntrip, unsigned char *buff, int n, char *msg)
return copied;
}
const int available = std::min(ntrip->nb,
static_cast<int>(ntrip->chunk_remaining));
const int take = std::min(available, n - copied);
const unsigned int available = std::min(
static_cast<unsigned int>(ntrip->nb), ntrip->chunk_remaining);
const unsigned int take = std::min(
available, static_cast<unsigned int>(n - copied));
if (take > 0)
{
std::memcpy(buff + copied, ntrip->buff, take);
ntrip_consume_buffer(ntrip, take);
ntrip->chunk_remaining -= static_cast<unsigned int>(take);
copied += take;
ntrip_consume_buffer(ntrip, static_cast<int>(take));
ntrip->chunk_remaining -= take;
copied += static_cast<int>(take);
}
if (ntrip->chunk_remaining == 0)
{
@@ -1749,6 +1843,223 @@ int read_ntrip_chunked(ntrip_t *ntrip, unsigned char *buff, int n, char *msg)
return copied;
}
void trim_http_ows(std::string *value)
{
std::string::size_type first = 0;
while (first < value->size() &&
((*value)[first] == ' ' || (*value)[first] == '\t'))
{
++first;
}
std::string::size_type last = value->size();
while (last > first &&
((*value)[last - 1] == ' ' || (*value)[last - 1] == '\t'))
{
--last;
}
*value = value->substr(first, last - first);
}
void lowercase_ascii(std::string *value)
{
std::transform(value->begin(), value->end(), value->begin(), [](unsigned char c) {
return static_cast<char>(c >= 'A' && c <= 'Z' ? c + ('a' - 'A') : c);
});
}
bool is_http_token_character(unsigned char c)
{
const bool ascii_alphanumeric =
(c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') ||
(c >= 'a' && c <= 'z');
return ascii_alphanumeric || c == '!' || c == '#' || c == '$' ||
c == '%' || c == '&' || c == '\'' || c == '*' || c == '+' ||
c == '-' || c == '.' || c == '^' || c == '_' || c == '`' ||
c == '|' || c == '~';
}
bool is_ascii_digit(char c)
{
return c >= '0' && c <= '9';
}
bool parse_http_status_line(const char *begin, const char *end, int *status)
{
const char prefix[] = "HTTP/";
if (end - begin < static_cast<std::ptrdiff_t>(sizeof(prefix) - 1) ||
std::memcmp(begin, prefix, sizeof(prefix) - 1) != 0)
{
return false;
}
const char *p = begin + sizeof(prefix) - 1;
const char *number_begin = p;
while (p < end && is_ascii_digit(*p))
{
++p;
}
if (p == number_begin || p == end || *p++ != '.')
{
return false;
}
number_begin = p;
while (p < end && is_ascii_digit(*p))
{
++p;
}
if (p == number_begin || p == end || *p++ != ' ' || end - p < 3)
{
return false;
}
if (!is_ascii_digit(p[0]) || !is_ascii_digit(p[1]) ||
!is_ascii_digit(p[2]))
{
return false;
}
*status = (p[0] - '0') * 100 + (p[1] - '0') * 10 + (p[2] - '0');
p += 3;
if (p < end && *p++ != ' ')
{
return false;
}
while (p < end)
{
const auto c = static_cast<unsigned char>(*p++);
if ((c < 0x20U && c != '\t') || c == 0x7FU)
{
return false;
}
}
return true;
}
bool parse_transfer_encoding(const std::string &value, bool *chunked)
{
std::string::size_type begin = 0;
do
{
const std::string::size_type end = value.find(',', begin);
std::string coding = value.substr(begin,
end == std::string::npos ? std::string::npos : end - begin);
trim_http_ows(&coding);
lowercase_ascii(&coding);
/* GNSS-SDR only decodes the chunked transfer coding. Requiring an
* exact token avoids treating values such as "notchunked" as
* chunked, and rejecting other codings avoids feeding encoded data
* to the RTCM decoder. RFC 7230 also forbids applying chunked more
* than once. */
if (coding != "chunked" || *chunked)
{
return false;
}
*chunked = true;
if (end == std::string::npos)
{
break;
}
begin = end + 1;
}
while (begin <= value.size());
return true;
}
bool parse_ntrip_v2_headers(const char *begin, const char *headers_end,
bool *content_type_present, std::string *content_type, bool *chunked,
std::string *error)
{
const char *cursor = begin;
while (cursor < headers_end)
{
const char *field_end = cursor;
while (field_end + 1 < headers_end &&
!(field_end[0] == '\r' && field_end[1] == '\n'))
{
++field_end;
}
if (field_end + 1 >= headers_end)
{
*error = "malformed NTRIP v2 response headers";
return false;
}
const char *colon = std::find(cursor, field_end, ':');
if (colon == field_end || colon == cursor)
{
*error = "malformed NTRIP v2 response header";
return false;
}
for (const char *p = cursor; p < colon; ++p)
{
if (!is_http_token_character(static_cast<unsigned char>(*p)))
{
*error = "malformed NTRIP v2 response header name";
return false;
}
}
for (const char *p = colon + 1; p < field_end; ++p)
{
const auto c = static_cast<unsigned char>(*p);
if ((c < 0x20U && c != '\t') || c == 0x7FU)
{
*error = "malformed NTRIP v2 response header value";
return false;
}
}
std::string name(cursor, colon);
std::string value(colon + 1, field_end);
lowercase_ascii(&name);
trim_http_ows(&value);
if (name == "content-type")
{
if (*content_type_present)
{
*error = "duplicate NTRIP Content-Type header";
return false;
}
*content_type_present = true;
const std::string::size_type parameter = value.find(';');
*content_type = value.substr(0, parameter);
trim_http_ows(content_type);
lowercase_ascii(content_type);
if (content_type->empty())
{
*error = "empty NTRIP Content-Type header";
return false;
}
}
else if (name == "transfer-encoding")
{
if (!parse_transfer_encoding(value, chunked))
{
*error = "unsupported NTRIP Transfer-Encoding";
return false;
}
}
cursor = field_end + 2;
}
return cursor == headers_end;
}
int reject_ntrip_v2_response(ntrip_t *ntrip, char *msg,
const std::string &reason)
{
std::snprintf(msg, MAXSTRMSG, "%s", reason.c_str());
tracet(1, "rspntrip_c: %s nb=%d\n", msg, ntrip->nb);
ntrip->nb = 0;
ntrip->buff[0] = '\0';
ntrip->chunked = 0;
ntrip->chunk_state = 0;
ntrip->chunk_remaining = 0;
ntrip->state = 0;
ntrip_disconnect(ntrip, ntrip->tcp->tirecon);
return 0;
}
bool parse_ntrip_client_options(const char *path, std::string &clean_path,
int &version, int &tls_enabled, char *msg)
{
@@ -1809,26 +2120,29 @@ bool parse_ntrip_client_options(const char *path, std::string &clean_path,
/* send ntrip server request -------------------------------------------------*/
int reqntrip_s(ntrip_t *ntrip, char *msg)
{
char buff[256 + NTRIP_MAXSTR];
char *p = buff;
char *s;
s = p;
tracet(3, "reqntrip_s: state=%d\n", ntrip->state);
p += std::snprintf(p, 256 + NTRIP_MAXSTR, "SOURCE %s %s\r\n", ntrip->passwd, ntrip->mntpnt);
p += std::snprintf(p, NTRIP_MAXSTR - (p - s), "Source-Agent: NTRIP %s\r\n", NTRIP_AGENT);
p += std::snprintf(p, NTRIP_MAXSTR - (p - s), "STR: %s\r\n", ntrip->str);
p += std::snprintf(p, sizeof("\r\n") + 1, "\r\n");
if (ntrip->request_length == 0)
{
std::ostringstream request;
request << "SOURCE " << ntrip->passwd << " " << ntrip->mntpnt << "\r\n";
request << "Source-Agent: NTRIP " << NTRIP_AGENT << "\r\n";
request << "STR: " << ntrip->str << "\r\n\r\n";
if (!ntrip_prepare_request(ntrip, request.str(), msg))
{
return 0;
}
}
if (writetcpcli(ntrip->tcp, reinterpret_cast<unsigned char *>(buff), p - buff, msg) != p - buff)
const int request_length = ntrip->request_length;
if (!ntrip_flush_request(ntrip, msg))
{
return 0;
}
tracet(2, "reqntrip_s: send request state=%d ns=%" PRIdPTR "\n", ntrip->state, p - buff);
tracet(2, "reqntrip_s: send request state=%d ns=%d\n", ntrip->state, request_length);
/* The SOURCE request contains the caster password. */
tracet(5, "reqntrip_s: request headers redacted n=%" PRIdPTR "\n", p - buff);
tracet(5, "reqntrip_s: request headers redacted n=%d\n", request_length);
ntrip->state = 1;
return 1;
}
@@ -1843,42 +2157,48 @@ int reqntrip_c(ntrip_t *ntrip, char *msg)
tracet(3, "reqntrip_c: state=%d version=%d tls=%d\n", ntrip->state,
ntrip->version, ntrip->tls_enabled);
std::string target = *ntrip->url ? std::string(ntrip->url) : std::string();
target += "/";
target += ntrip->mntpnt;
std::ostringstream request;
request << "GET " << target << (ntrip->version == NTRIP_VERSION_2 ? " HTTP/1.1\r\n" : " HTTP/1.0\r\n");
if (ntrip->version == NTRIP_VERSION_2)
if (ntrip->request_length == 0)
{
request << "Host: " << ntrip->host << ":" << ntrip->port << "\r\n";
request << "Ntrip-Version: Ntrip/2.0\r\n";
}
request << "User-Agent: NTRIP " << NTRIP_AGENT << "\r\n";
request << "Accept: */*\r\n";
request << "Connection: close\r\n";
std::string target = *ntrip->url ? std::string(ntrip->url) : std::string();
target += "/";
target += ntrip->mntpnt;
if (*ntrip->user)
{
std::snprintf(user, sizeof(user), "%s:%s", ntrip->user, ntrip->passwd);
encbase64(authorization, reinterpret_cast<unsigned char *>(user), strlen(user));
request << "Authorization: Basic " << authorization << "\r\n";
}
request << "\r\n";
std::ostringstream request;
request << "GET " << target << (ntrip->version == NTRIP_VERSION_2 ? " HTTP/1.1\r\n" : " HTTP/1.0\r\n");
const std::string request_text = request.str();
if (ntrip_write_transport(ntrip,
reinterpret_cast<const unsigned char *>(request_text.data()),
static_cast<int>(request_text.size()), msg) != static_cast<int>(request_text.size()))
if (ntrip->version == NTRIP_VERSION_2)
{
request << "Host: " << ntrip->host << ":" << ntrip->port << "\r\n";
request << "Ntrip-Version: Ntrip/2.0\r\n";
}
request << "User-Agent: NTRIP " << NTRIP_AGENT << "\r\n";
request << "Accept: */*\r\n";
request << "Connection: close\r\n";
if (*ntrip->user)
{
std::snprintf(user, sizeof(user), "%s:%s", ntrip->user, ntrip->passwd);
encbase64(authorization, reinterpret_cast<unsigned char *>(user), strlen(user));
request << "Authorization: Basic " << authorization << "\r\n";
}
request << "\r\n";
if (!ntrip_prepare_request(ntrip, request.str(), msg))
{
return 0;
}
}
const int request_length = ntrip->request_length;
if (!ntrip_flush_request(ntrip, msg))
{
return 0;
}
/* The request can contain a Basic Authorization header, so only sizes are
* logged here. */
tracet(2, "reqntrip_c: request sent state=%d version=%d ns=%zu\n",
ntrip->state, ntrip->version, request_text.size());
tracet(2, "reqntrip_c: request sent state=%d version=%d ns=%d\n",
ntrip->state, ntrip->version, request_length);
ntrip->state = 1;
return 1;
}
@@ -1893,41 +2213,51 @@ int rspntrip_c_v2(ntrip_t *ntrip, char *msg)
{
if (ntrip->nb >= NTRIP_MAXRSP - 1)
{
std::snprintf(msg, MAXSTRMSG, "response overflow");
ntrip->state = 0;
ntrip_disconnect(ntrip, ntrip->tcp->tirecon);
return reject_ntrip_v2_response(ntrip, msg,
"NTRIP v2 response header overflow");
}
return 0;
}
char *line_end = std::strstr(data, "\r\n");
int http_major = 0;
int http_minor = 0;
int status = 0;
if (!line_end || std::sscanf(data, "HTTP/%d.%d %d", &http_major, &http_minor, &status) != 3)
if (!line_end || line_end > header_end ||
!parse_http_status_line(data, line_end, &status))
{
std::snprintf(msg, MAXSTRMSG, "invalid NTRIP v2 response");
ntrip->state = 0;
ntrip_disconnect(ntrip, ntrip->tcp->tirecon);
return 0;
return reject_ntrip_v2_response(ntrip, msg,
"invalid NTRIP v2 HTTP status line");
}
if (status != 200)
{
const int line_size = std::min(static_cast<int>(line_end - data), MAXSTRMSG - 1);
std::memcpy(msg, data, line_size);
msg[line_size] = '\0';
ntrip->state = 0;
ntrip_disconnect(ntrip, ntrip->tcp->tirecon);
return 0;
return reject_ntrip_v2_response(ntrip, msg,
std::string(data, static_cast<std::size_t>(line_size)));
}
bool content_type_present = false;
std::string content_type;
bool chunked = false;
std::string header_error;
if (!parse_ntrip_v2_headers(line_end + 2, header_end + 2,
&content_type_present, &content_type, &chunked, &header_error))
{
return reject_ntrip_v2_response(ntrip, msg, header_error);
}
if (content_type_present)
{
const char *expected_type = *ntrip->mntpnt
? "gnss/data"
: "gnss/sourcetable";
if (content_type != expected_type)
{
return reject_ntrip_v2_response(ntrip, msg,
std::string("unexpected NTRIP Content-Type: ") + content_type);
}
}
const int header_size = static_cast<int>(header_end + 4 - data);
std::string headers(data, header_size);
std::transform(headers.begin(), headers.end(), headers.begin(),
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
ntrip->chunked = headers.find("transfer-encoding:") != std::string::npos &&
headers.find("chunked") != std::string::npos;
ntrip->chunked = chunked ? 1 : 0;
ntrip->chunk_state = 0;
ntrip->chunk_remaining = 0;
ntrip_consume_buffer(ntrip, header_size);
@@ -2113,6 +2443,9 @@ int waitntrip(ntrip_t *ntrip, char *msg)
if (ntrip->tcp->svr.state < 2)
{
ntrip->state = 0; /* tcp disconnected */
/* A new connection must send the complete request, never just the
* suffix left from a short write on the previous socket. */
ntrip_clear_request(ntrip);
ntrip_reset_tls(ntrip);
}
@@ -2181,6 +2514,8 @@ ntrip_t *openntrip(const char *path, int type, char *msg)
ntrip->chunk_state = 0;
ntrip->chunk_remaining = 0;
ntrip->nb = 0;
ntrip->request_length = 0;
ntrip->request_offset = 0;
ntrip->tls_ctx = nullptr;
ntrip->url[0] = '\0';
ntrip->host[0] = ntrip->port[0] = '\0';
@@ -2188,6 +2523,7 @@ ntrip_t *openntrip(const char *path, int type, char *msg)
for (i = 0; i < NTRIP_MAXRSP; i++)
{
ntrip->buff[i] = 0;
ntrip->request_buff[i] = 0;
}
/* decode tcp/ntrip path */
@@ -2254,6 +2590,7 @@ ntrip_t *openntrip(const char *path, int type, char *msg)
void closentrip(ntrip_t *ntrip)
{
tracet(3, "closentrip: state=%d\n", ntrip->state);
ntrip_clear_request(ntrip);
delete ntrip_tls(ntrip);
closetcpcli(ntrip->tcp);
free(ntrip);
+5
View File
@@ -73,6 +73,11 @@ class Rtklib_Tls_Client::Impl
public:
explicit Impl(std::string hostname) : d_hostname(std::move(hostname)) {}
Impl(const Impl &) = delete;
Impl &operator=(const Impl &) = delete;
Impl(Impl &&) = delete;
Impl &operator=(Impl &&) = delete;
bool initialize(char *msg)
{
#ifdef USE_GNUTLS_FALLBACK
+2
View File
@@ -29,6 +29,8 @@ public:
Rtklib_Tls_Client(const Rtklib_Tls_Client &) = delete;
Rtklib_Tls_Client &operator=(const Rtklib_Tls_Client &) = delete;
Rtklib_Tls_Client(Rtklib_Tls_Client &&) = delete;
Rtklib_Tls_Client &operator=(Rtklib_Tls_Client &&) = delete;
bool initialize(char *msg);
void reset();