12#if PC_ENABLE_NTRIP_CASTER
21 return (c >=
'A' && c <=
'Z') ? (char)(c -
'A' +
'a') : c;
25bool ci_prefix(
const char *s,
const char *end,
const char *prefix)
29 if (s >= end || lower(*s) != lower(*prefix))
40const char *skip_ws(
const char *s,
const char *end)
42 while (s < end && (*s ==
' ' || *s ==
'\t'))
50void fmt_deg2(
char *out,
size_t cap,
double v)
52 int hundredths = (int)(v * 100.0 + (v >= 0.0 ? 0.5 : -0.5));
53 int whole = hundredths / 100;
54 int frac = hundredths % 100;
59 const char *sign = (v < 0.0 && whole == 0) ?
"-" :
"";
60 pc_sb sb_out = {out, cap, 0,
true};
73bool find_header_end(
const char *buf,
size_t len,
size_t *hend)
75 for (
size_t i = 0; i < len; i++)
77 if (i + 3 < len && buf[i] ==
'\r' && buf[i + 1] ==
'\n' && buf[i + 2] ==
'\r' && buf[i + 3] ==
'\n')
82 if (i + 1 < len && buf[i] ==
'\n' && buf[i + 1] ==
'\n')
92void scan_headers(
const char *buf,
const char *end, NtripRequest *out)
94 const char *line = buf;
97 const char *le = line;
101 while (le < end && *le !=
'\n')
105 const char *lend = le;
106 if (lend > line && *(lend - 1) ==
'\r')
111 if (ci_prefix(line, lend,
"ntrip-version:"))
113 const char *v = line + 14;
114 while (v + 2 < lend && !(v[0] ==
'2' && v[1] ==
'.' && v[2] ==
'0'))
120 out->version = NtripVersion::NTRIP_V2;
123 else if (ci_prefix(line, lend,
"authorization:"))
125 const char *v = skip_ws(line + 14, lend);
126 if (ci_prefix(v, lend,
"basic "))
128 v = skip_ws(v + 6, lend);
130 out->auth_b64_len = (uint16_t)(lend - v);
138bool pc_ntrip_request_parse(
const char *buf,
size_t len, NtripRequest *out)
140 memset(out, 0,
sizeof(*out));
141 out->version = NtripVersion::NTRIP_V1;
145 if (!find_header_end(buf, len, &hend))
149 out->complete =
true;
151 const char *end = buf + hend;
155 if (!ci_prefix(p, end,
"GET "))
161 p = skip_ws(p + 4, end);
162 const char *target = p;
165 while (p < end && *p !=
' ' && *p !=
'\r' && *p !=
'\n' && *p !=
'?')
169 size_t tlen = (size_t)(p - target);
171 if (tlen == 0 || (tlen == 1 && target[0] ==
'/'))
173 out->want_sourcetable =
true;
177 const char *mp = target;
184 if (mlen >=
sizeof(out->mountpoint))
186 mlen =
sizeof(out->mountpoint) - 1;
188 memcpy(out->mountpoint, mp, mlen);
189 out->mountpoint[mlen] =
'\0';
193 scan_headers(buf, end, out);
197size_t pc_ntrip_build_stream_response(
char *out,
size_t cap, NtripVersion version)
201 pc_sb sb_out = {out, cap, 0,
true};
202 pc_sb_put(&sb_out, (version == NtripVersion::NTRIP_V2)
203 ?
"HTTP/1.1 200 OK\r\nNtrip-Version: Ntrip/2.0\r\nServer: PC\r\nContent-Type: "
204 "gnss/data\r\nConnection: close\r\n\r\n"
205 :
"ICY 200 OK\r\n\r\n");
214size_t pc_ntrip_build_error_response(
char *out,
size_t cap, NtripVersion version)
217 if (version == NtripVersion::NTRIP_V2)
219 pc_sb sb_out4 = {out, cap, 0,
true};
220 pc_sb_put(&sb_out4,
"HTTP/1.1 400 Bad Request\r\nContent-Length: 0\r\nConnection: close\r\n\r\n");
225 pc_sb sb_out5 = {out, cap, 0,
true};
226 pc_sb_put(&sb_out5,
"ERROR - Bad Request\r\n");
236size_t pc_ntrip_build_unauthorized_response(
char *out,
size_t cap, NtripVersion version)
239 if (version == NtripVersion::NTRIP_V2)
241 pc_sb sb_out6 = {out, cap, 0,
true};
242 pc_sb_put(&sb_out6,
"HTTP/1.1 401 Unauthorized\r\nWWW-Authenticate: Basic realm=\"NTRIP\"\r\nContent-Length: "
243 "0\r\nConnection: close\r\n\r\n");
248 pc_sb sb_out7 = {out, cap, 0,
true};
249 pc_sb_put(&sb_out7,
"ERROR - Bad Password\r\n");
259size_t pc_ntrip_build_str_record(
char *out,
size_t cap,
const NtripMount *m)
261 if (!m || !m->mountpoint)
267 fmt_deg2(lat,
sizeof(lat), m->lat_deg);
268 fmt_deg2(lon,
sizeof(lon), m->lon_deg);
269 const char *ident = m->identifier ? m->identifier :
"";
270 const char *fmtd = m->format_details ? m->format_details :
"1005(1)";
271 const char *nav = m->nav_system ? m->nav_system :
"GPS";
272 const char *ctry = m->country ? m->country :
"";
273 const char *gen = m->generator ? m->generator :
"PC";
276 pc_sb sb_out8 = {out, cap, 0,
true};
292 pc_sb_i64(&sb_out8, (int64_t)(m->nmea_required ? 1 : 0));
304size_t pc_ntrip_build_sourcetable(
char *out,
size_t cap, NtripVersion version,
const NtripMount *mounts,
307 static const char ENDLINE[] =
"ENDSOURCETABLE\r\n";
312 for (
size_t i = 0; i < mount_count; i++)
314 size_t rn = pc_ntrip_build_str_record(rec,
sizeof(rec), &mounts[i]);
321 body_len +=
sizeof(ENDLINE) - 1;
325 if (version == NtripVersion::NTRIP_V2)
327 pc_sb sb_out9 = {out, cap, 0,
true};
328 pc_sb_put(&sb_out9,
"HTTP/1.1 200 OK\r\nNtrip-Version: Ntrip/2.0\r\nServer: PC\r\nContent-Type: "
329 "gnss/sourcetable\r\nContent-Length: ");
330 pc_sb_u32(&sb_out9, (uint32_t)((
unsigned)body_len));
331 pc_sb_put(&sb_out9,
"\r\nConnection: close\r\n\r\n");
336 pc_sb sb_out10 = {out, cap, 0,
true};
337 pc_sb_put(&sb_out10,
"SOURCETABLE 200 OK\r\nServer: PC\r\nContent-Type: text/plain\r\nContent-Length: ");
338 pc_sb_u32(&sb_out10, (uint32_t)((
unsigned)body_len));
347 size_t pos = (size_t)hn;
348 for (
size_t i = 0; i < mount_count; i++)
350 size_t rn = pc_ntrip_build_str_record(out + pos, cap - pos, &mounts[i]);
351 if (rn == 0 || pos + rn + 2 >= cap)
359 if (pos + (
sizeof(ENDLINE) - 1) >= cap)
363 memcpy(out + pos, ENDLINE,
sizeof(ENDLINE) - 1);
364 pos +=
sizeof(ENDLINE) - 1;
Bounded no-heap string builder that fails closed on overflow (one shared copy).
size_t pc_sb_finish(pc_sb *b)
NUL-terminate and return the built length, or 0 if the build overflowed.
void pc_sb_u32w(pc_sb *b, uint32_t v, unsigned min_digits)
Append v as decimal, zero-padded to at least min_digits (printf "%0Nu").
void pc_sb_i64(pc_sb *b, int64_t v)
Append v as signed decimal (64-bit), with a leading '-' when negative.
void pc_sb_put(pc_sb *b, const char *s)
Append NUL-terminated s; leaves the buffer untouched and clears ok if it would not fit.
void pc_sb_u32(pc_sb *b, uint32_t v)
Append v as decimal (no leading zeros; "0" for zero).
Bump-append target; ok latches false once an append would overflow cap.