MRTKLIB 0.7.14
Modernized GNSS Positioning Library
Loading...
Searching...
No Matches
ntrip_chunk.h
Go to the documentation of this file.
1/*------------------------------------------------------------------------------
2 * ntrip_chunk.h : NTRIP v2 chunked transfer encoding and HTTP helpers
3 *
4 * Copyright (C) 2026 H.SHIONO (MRTKLIB Project)
5 *
6 * SPDX-License-Identifier: BSD-2-Clause
7 *
8 * This is a header-only implementation intended to be #included by both
9 * mrtk_stream.c and test code (t_ntrip.c). All functions are static.
10 *----------------------------------------------------------------------------*/
11#ifndef NTRIP_CHUNK_H
12#define NTRIP_CHUNK_H
13
14#include <limits.h>
15#include <stdint.h>
16#include <stdio.h>
17#include <stdlib.h>
18#include <string.h>
19#if defined(_WIN32) || defined(_WIN64)
20#ifndef strncasecmp
21#define strncasecmp _strnicmp
22#endif
23#else
24#include <strings.h> /* strncasecmp on POSIX (Linux, macOS, BSD, etc.) */
25#endif
26
27/*============================================================================
28 * NTRIP Version Constants
29 *===========================================================================*/
30
31#define NTRIP_VER_AUTO 0 /* auto-detect (try v2, fallback v1) */
32#define NTRIP_VER_1 1 /* NTRIP version 1.0 */
33#define NTRIP_VER_2 2 /* NTRIP version 2.0 */
34
35/*============================================================================
36 * Chunked Transfer Encoding Types
37 *===========================================================================*/
38
39#define CHUNK_HDR_MAX 16 /* max chunk-size header length ("FFFFFFFF\r\n") */
40#define CHUNK_SIZE_MAX 0xFFFFFF /* max chunk size (~16 MB, well within int range) */
41
42typedef struct { /* chunked transfer decoder state */
43 int state; /* 0:size, 1:data, 2:trail, 3:done, 4:final_trail */
44 int remain; /* remaining bytes in current chunk */
45 char hdr[CHUNK_HDR_MAX]; /* partial chunk-size line buffer (hex digits only) */
46 int nhdr; /* bytes in hdr[] (-1: frozen after extension) */
47 int at_sol; /* final_trail: at start of line flag */
49
50/*============================================================================
51 * Chunked Transfer Decoder
52 *===========================================================================*/
53
54/* initialize chunked decoder state */
55static void chunk_dec_init(chunk_dec_t* dec) {
56 dec->state = 0;
57 dec->remain = 0;
58 dec->nhdr = 0;
59 dec->at_sol = 1;
60}
61
62/* decode chunked transfer encoding (incremental, non-blocking)
63 *
64 * Reads from *pin (length *pnin), writes decoded payload to out (max nout).
65 * Advances *pin and decrements *pnin as input is consumed.
66 * Returns number of decoded bytes written to out, or -1 on error.
67 *
68 * State machine:
69 * 0 (SIZE) : accumulate hex digits + optional extension until \r\n
70 * 1 (DATA) : copy `remain` bytes from input to output
71 * 2 (TRAIL) : consume trailing \r\n after chunk data
72 * 3 (DONE) : terminal state (zero-length chunk received)
73 */
74static int chunk_decode(chunk_dec_t* dec, const uint8_t** pin, int* pnin, uint8_t* out, int nout) {
75 const uint8_t* in = *pin;
76 int nin = *pnin;
77 int nw = 0; /* bytes written to out */
78
79 while (nin > 0 && dec->state != 3) { /* 3 = DONE (terminal) */
80 switch (dec->state) {
81 case 0: /* SIZE: read chunk-size line
82 * Only hex digits are stored in hdr[]. Once a non-hex char
83 * (extension ';' etc.) is seen, remaining chars are skipped
84 * until \r\n, so extensions cannot overflow hdr[]. */
85 while (nin > 0) {
86 char c = (char)*in++;
87 nin--;
88
89 if (c == '\n') {
90 /* end of chunk-size line: parse accumulated hex digits */
91 unsigned long size = 0;
92 int i, nhex, ndigits = 0;
93
94 /* nhdr == -1 means frozen after extension; use strlen */
95 nhex = (dec->nhdr >= 0) ? dec->nhdr : (int)strlen(dec->hdr);
96 for (i = 0; i < nhex; i++) {
97 int d;
98 char h = dec->hdr[i];
99 if (h >= '0' && h <= '9') {
100 d = h - '0';
101 } else if (h >= 'a' && h <= 'f') {
102 d = h - 'a' + 10;
103 } else if (h >= 'A' && h <= 'F') {
104 d = h - 'A' + 10;
105 } else {
106 break;
107 }
108 size = (size << 4) | d;
109 ndigits++;
110 }
111 dec->nhdr = 0;
112
113 /* reject empty size line or overflow */
114 if (ndigits == 0 || size > CHUNK_SIZE_MAX) {
115 *pin = in;
116 *pnin = nin;
117 return -1;
118 }
119 dec->remain = (int)size;
120
121 if (dec->remain == 0) {
122 dec->state = 4; /* consume final trailer CRLF */
123 dec->at_sol = 1; /* start at line beginning */
124 } else {
125 dec->state = 1; /* data follows */
126 }
127 break;
128 }
129 /* accumulate hex digits only; once a non-hex char is seen
130 * (';' extension, space, etc.), stop accumulating.
131 * Use nhdr == -1 as "frozen" flag to skip further chars */
132 if (dec->nhdr >= 0) {
133 if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) {
134 if (dec->nhdr < CHUNK_HDR_MAX - 1) {
135 dec->hdr[dec->nhdr++] = c;
136 } else {
137 /* too many hex digits: reject as malformed */
138 *pin = in;
139 *pnin = nin;
140 return -1;
141 }
142 } else if (c != '\r') {
143 /* non-hex, non-CR: freeze accumulation (extension/etc.) */
144 dec->hdr[dec->nhdr] = '\0';
145 dec->nhdr = -1;
146 }
147 }
148 /* \r and chars after freeze are silently skipped until \n */
149 }
150 break;
151
152 case 1: { /* DATA: copy chunk payload */
153 int avail = nin < dec->remain ? nin : dec->remain;
154 int space = nout - nw;
155 int copy = avail < space ? avail : space;
156
157 if (copy > 0) {
158 memcpy(out + nw, in, copy);
159 nw += copy;
160 in += copy;
161 nin -= copy;
162 dec->remain -= copy;
163 }
164 if (dec->remain == 0) {
165 dec->state = 2; /* expect trailing \r\n */
166 }
167 if (nw >= nout) {
168 /* output full, return what we have */
169 goto done;
170 }
171 break;
172 }
173 case 2: /* TRAIL: consume \r\n after chunk data */
174 while (nin > 0) {
175 char c = (char)*in++;
176 nin--;
177
178 if (c == '\n') {
179 dec->state = 0; /* next chunk */
180 break;
181 }
182 /* skip \r, ignore other chars in trailer */
183 }
184 break;
185
186 case 4: /* FINAL_TRAIL: consume optional trailer headers + final \r\n
187 * Per RFC 7230, the last-chunk (0\r\n) is followed by optional
188 * trailer headers and terminated by an empty line (\r\n).
189 * dec->at_sol persists across calls for incremental parsing. */
190 while (nin > 0 && dec->state != 3) {
191 char c = (char)*in++;
192 nin--;
193
194 if (c == '\r') {
195 continue; /* skip CR, check LF next */
196 }
197 if (c == '\n') {
198 if (dec->at_sol) {
199 dec->state = 3; /* DONE: empty line = end of trailers */
200 break;
201 }
202 dec->at_sol = 1; /* next char starts a new line */
203 } else {
204 dec->at_sol = 0; /* non-empty line content (trailer header) */
205 }
206 }
207 break;
208 }
209 }
210done:
211 *pin = in;
212 *pnin = nin;
213 return nw;
214}
215
216/*============================================================================
217 * Chunked Transfer Encoder
218 *===========================================================================*/
219
220/* encode data as a single HTTP chunk: "<hex-size>\r\n<data>\r\n"
221 *
222 * Writes to out (max nout bytes). Returns total bytes written, or -1 if
223 * output buffer is too small. Caller must ensure nout >= ndata + 20.
224 */
225static int chunk_encode(uint8_t* out, int nout, const uint8_t* data, int ndata) {
226 char hdr[16];
227 int hlen = snprintf(hdr, sizeof(hdr), "%x\r\n", ndata);
228
229 if (hlen <= 0 || hlen >= (int)sizeof(hdr)) {
230 return -1; /* snprintf error or truncation */
231 }
232 if (hlen + ndata + 2 > nout) {
233 return -1; /* buffer too small */
234 }
235 memcpy(out, hdr, hlen);
236 if (ndata > 0) {
237 memcpy(out + hlen, data, ndata);
238 }
239 out[hlen + ndata] = '\r';
240 out[hlen + ndata + 1] = '\n';
241 return hlen + ndata + 2;
242}
243
244/*============================================================================
245 * HTTP Header Helpers
246 *===========================================================================*/
247
248/* case-insensitive substring search (portable replacement for strcasestr) */
249static const char* stristr(const char* haystack, const char* needle) {
250 int nlen = (int)strlen(needle);
251 if (nlen == 0) {
252 return haystack;
253 }
254 for (; *haystack; haystack++) {
255 if (strncasecmp(haystack, needle, nlen) == 0) {
256 return haystack;
257 }
258 }
259 return NULL;
260}
261
262/* find end of HTTP headers in buffer (bounded search for \r\n\r\n)
263 * returns offset to body start, or 0 if headers are incomplete */
264static int http_header_end(const uint8_t* buff, int nb) {
265 int i;
266 if (nb < 4) {
267 return 0;
268 }
269 for (i = 0; i <= nb - 4; i++) {
270 if (buff[i] == '\r' && buff[i + 1] == '\n' && buff[i + 2] == '\r' && buff[i + 3] == '\n') {
271 return i + 4;
272 }
273 }
274 return 0;
275}
276
277/* extract HTTP status code from first line (e.g. "HTTP/1.1 200 OK\r\n")
278 * returns status code (e.g. 200), or 0 if not found */
279static int http_status_code(const uint8_t* buff, int nb) {
280 int i;
281 if (nb < 12) {
282 return 0;
283 }
284 if (strncmp((const char*)buff, "HTTP/", 5) != 0) {
285 return 0;
286 }
287 /* find first space within nb, then parse exactly 3 digits within bounds */
288 for (i = 5; i < nb; i++) {
289 if (buff[i] == ' ') {
290 if (i + 3 >= nb) {
291 return 0;
292 }
293 if (buff[i + 1] >= '0' && buff[i + 1] <= '9' && buff[i + 2] >= '0' && buff[i + 2] <= '9' &&
294 buff[i + 3] >= '0' && buff[i + 3] <= '9') {
295 return (buff[i + 1] - '0') * 100 + (buff[i + 2] - '0') * 10 + (buff[i + 3] - '0');
296 }
297 return 0;
298 }
299 }
300 return 0;
301}
302
303/* search for a specific HTTP header value (case-insensitive header name)
304 * copies value (up to vmax-1 chars) into val, returns 1 if found */
305static int http_find_header(const uint8_t* buff, int nb, const char* name, char* val, int vmax) {
306 int nlen = (int)strlen(name);
307 const char* p = (const char*)buff;
308 const char* end = p + nb;
309
310 while (p < end) {
311 /* check if enough room for "name:" */
312 if (p + nlen + 1 <= end && strncasecmp(p, name, nlen) == 0 && p[nlen] == ':') {
313 const char* v = p + nlen + 1;
314 int i = 0;
315 /* skip leading whitespace */
316 while (v < end && (*v == ' ' || *v == '\t')) {
317 v++;
318 }
319 /* copy until \r or \n or end */
320 while (v < end && *v != '\r' && *v != '\n' && i < vmax - 1) {
321 val[i++] = *v++;
322 }
323 val[i] = '\0';
324 return 1;
325 }
326 /* advance to next line */
327 while (p < end && *p != '\n') {
328 p++;
329 }
330 if (p < end) {
331 p++; /* skip \n */
332 }
333 }
334 val[0] = '\0';
335 return 0;
336}
337
338#endif /* NTRIP_CHUNK_H */
#define CHUNK_SIZE_MAX
Definition ntrip_chunk.h:40
#define CHUNK_HDR_MAX
Definition ntrip_chunk.h:39
static int chunk_decode(chunk_dec_t *dec, const uint8_t **pin, int *pnin, uint8_t *out, int nout)
Definition ntrip_chunk.h:74
static int http_find_header(const uint8_t *buff, int nb, const char *name, char *val, int vmax)
static const char * stristr(const char *haystack, const char *needle)
static int http_header_end(const uint8_t *buff, int nb)
static int chunk_encode(uint8_t *out, int nout, const uint8_t *data, int ndata)
static void chunk_dec_init(chunk_dec_t *dec)
Definition ntrip_chunk.h:55
static int http_status_code(const uint8_t *buff, int nb)
char hdr[16]
Definition ntrip_chunk.h:45