DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
rtcm3.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file rtcm3.cpp
6 * @brief RTCM 3.x framing + 1005/1006 codec - implementation. See rtcm3.h.
7 */
8
10
11#if DETWS_ENABLE_NTRIP_CASTER
12
13#include <string.h>
14
15// ---------------------------------------------------------------------------------------------
16// CRC-24Q (poly 0x1864CFB, init 0). Computed over the preamble + header + payload of a frame.
17// ---------------------------------------------------------------------------------------------
18
19uint32_t rtcm3_crc24q(const uint8_t *data, size_t len)
20{
21 uint32_t crc = 0;
22 for (size_t i = 0; i < len; i++)
23 {
24 crc ^= (uint32_t)data[i] << 16;
25 for (int b = 0; b < 8; b++)
26 {
27 crc <<= 1;
28 if (crc & 0x1000000u)
29 crc ^= 0x1864CFBu;
30 }
31 }
32 return crc & 0xFFFFFFu;
33}
34
35// ---------------------------------------------------------------------------------------------
36// MSB-first bit I/O.
37// ---------------------------------------------------------------------------------------------
38
39void rtcm_bw_init(RtcmBitWriter *w, uint8_t *buf, size_t cap)
40{
41 w->buf = buf;
42 w->cap_bits = cap * 8;
43 w->pos = 0;
44 w->ok = true;
45}
46
47void rtcm_bw_u(RtcmBitWriter *w, uint64_t val, uint8_t nbits)
48{
49 if (!w->ok)
50 return;
51 if (nbits == 0 || nbits > 64 || w->pos + nbits > w->cap_bits)
52 {
53 w->ok = false;
54 return;
55 }
56 for (int i = (int)nbits - 1; i >= 0; i--)
57 {
58 if ((val >> i) & 1u) // buffer is pre-zeroed, so only set the 1 bits
59 {
60 size_t bp = w->pos;
61 w->buf[bp >> 3] |= (uint8_t)(0x80u >> (bp & 7u));
62 }
63 w->pos++;
64 }
65}
66
67void rtcm_bw_s(RtcmBitWriter *w, int64_t val, uint8_t nbits)
68{
69 uint64_t mask = (nbits >= 64) ? ~0ULL : ((1ULL << nbits) - 1ULL);
70 rtcm_bw_u(w, (uint64_t)val & mask, nbits);
71}
72
73uint64_t rtcm_br_u(const uint8_t *buf, size_t *pos, uint8_t nbits)
74{
75 uint64_t v = 0;
76 for (uint8_t i = 0; i < nbits; i++)
77 {
78 size_t bp = (*pos)++;
79 uint64_t bit = (buf[bp >> 3] >> (7u - (bp & 7u))) & 1u;
80 v = (v << 1) | bit;
81 }
82 return v;
83}
84
85int64_t rtcm_br_s(const uint8_t *buf, size_t *pos, uint8_t nbits)
86{
87 uint64_t v = rtcm_br_u(buf, pos, nbits);
88 if (nbits < 64 && (v & (1ULL << (nbits - 1))))
89 v |= ~((1ULL << nbits) - 1ULL); // sign-extend the two's-complement value
90 return (int64_t)v;
91}
92
93// ---------------------------------------------------------------------------------------------
94// Transport frame.
95// ---------------------------------------------------------------------------------------------
96
97size_t rtcm3_sync(const uint8_t *buf, size_t len)
98{
99 for (size_t i = 0; i < len; i++)
100 if (buf[i] == RTCM3_PREAMBLE)
101 return i;
102 return len;
103}
104
105size_t rtcm3_frame_parse(const uint8_t *buf, size_t len, Rtcm3Frame *out)
106{
107 if (len < RTCM3_HDR_LEN || buf[0] != RTCM3_PREAMBLE)
108 return 0; // not aligned to a preamble (caller runs rtcm3_sync first)
109 uint16_t payload_len = (uint16_t)(((buf[1] & 0x03u) << 8) | buf[2]);
110 size_t frame_len = (size_t)RTCM3_HDR_LEN + payload_len + RTCM3_CRC_LEN;
111 if (len < frame_len)
112 return 0; // whole frame not buffered yet
113 const uint8_t *payload = buf + RTCM3_HDR_LEN;
114 uint32_t crc_calc = rtcm3_crc24q(buf, (size_t)RTCM3_HDR_LEN + payload_len);
115 uint32_t crc_frame =
116 ((uint32_t)payload[payload_len] << 16) | ((uint32_t)payload[payload_len + 1] << 8) | payload[payload_len + 2];
117 if (out)
118 {
119 out->payload = payload;
120 out->payload_len = payload_len;
121 out->crc_ok = (crc_calc == crc_frame);
122 size_t p = 0;
123 out->msg_type = (payload_len >= 2) ? (uint16_t)rtcm_br_u(payload, &p, 12) : 0;
124 }
125 return frame_len;
126}
127
128size_t rtcm3_frame_build(uint8_t *out, size_t cap, const uint8_t *payload, uint16_t payload_len)
129{
130 if (payload_len > RTCM3_MAX_PAYLOAD)
131 return 0;
132 size_t frame_len = (size_t)RTCM3_HDR_LEN + payload_len + RTCM3_CRC_LEN;
133 if (!out || cap < frame_len)
134 return 0;
135 out[0] = RTCM3_PREAMBLE;
136 out[1] = (uint8_t)((payload_len >> 8) & 0x03u); // top 6 bits reserved = 0
137 out[2] = (uint8_t)(payload_len & 0xFFu);
138 if (payload_len && payload)
139 memcpy(out + RTCM3_HDR_LEN, payload, payload_len);
140 uint32_t crc = rtcm3_crc24q(out, (size_t)RTCM3_HDR_LEN + payload_len);
141 out[RTCM3_HDR_LEN + payload_len] = (uint8_t)((crc >> 16) & 0xFFu);
142 out[RTCM3_HDR_LEN + payload_len + 1] = (uint8_t)((crc >> 8) & 0xFFu);
143 out[RTCM3_HDR_LEN + payload_len + 2] = (uint8_t)(crc & 0xFFu);
144 return frame_len;
145}
146
147// ---------------------------------------------------------------------------------------------
148// Message 1005 / 1006 - Stationary Antenna Reference Point (fields per RTCM 10403.x).
149// ---------------------------------------------------------------------------------------------
150
151namespace
152{
153size_t build_arp(uint8_t *out, size_t cap, uint16_t msg, uint16_t station_id, int64_t x, int64_t y, int64_t z,
154 bool with_h, uint16_t h)
155{
156 uint8_t payload[21];
157 memset(payload, 0, sizeof payload);
158 RtcmBitWriter w;
159 rtcm_bw_init(&w, payload, sizeof payload);
160 rtcm_bw_u(&w, msg, 12); // DF002 message number
161 rtcm_bw_u(&w, station_id & 0xFFF, 12); // DF003 reference station id
162 rtcm_bw_u(&w, 0, 6); // DF021 ITRF realization year (unspecified)
163 rtcm_bw_u(&w, 1, 1); // DF022 GPS indicator
164 rtcm_bw_u(&w, 0, 1); // DF023 GLONASS indicator
165 rtcm_bw_u(&w, 0, 1); // DF024 Galileo indicator
166 rtcm_bw_u(&w, 0, 1); // DF141 reference-station indicator (0 = real/physical)
167 rtcm_bw_s(&w, x, 38); // DF025 ECEF-X
168 rtcm_bw_u(&w, 0, 1); // DF142 single receiver oscillator indicator
169 rtcm_bw_u(&w, 0, 1); // reserved
170 rtcm_bw_s(&w, y, 38); // DF026 ECEF-Y
171 rtcm_bw_u(&w, 0, 2); // DF364 quarter cycle indicator
172 rtcm_bw_s(&w, z, 38); // DF027 ECEF-Z
173 uint16_t body_bytes = 19;
174 if (with_h)
175 {
176 rtcm_bw_u(&w, h, 16); // DF028 antenna height (1006)
177 body_bytes = 21;
178 }
179 if (!w.ok)
180 return 0;
181 return rtcm3_frame_build(out, cap, payload, body_bytes);
182}
183} // namespace
184
185size_t rtcm3_build_1005(uint8_t *out, size_t cap, uint16_t station_id, int64_t ecef_x_01mm, int64_t ecef_y_01mm,
186 int64_t ecef_z_01mm)
187{
188 return build_arp(out, cap, 1005, station_id, ecef_x_01mm, ecef_y_01mm, ecef_z_01mm, false, 0);
189}
190
191size_t rtcm3_build_1006(uint8_t *out, size_t cap, uint16_t station_id, int64_t ecef_x_01mm, int64_t ecef_y_01mm,
192 int64_t ecef_z_01mm, uint16_t antenna_height_01mm)
193{
194 return build_arp(out, cap, 1006, station_id, ecef_x_01mm, ecef_y_01mm, ecef_z_01mm, true, antenna_height_01mm);
195}
196
197bool rtcm3_parse_1005(const uint8_t *payload, uint16_t payload_len, Rtcm3StationArp *out)
198{
199 if (!payload || !out || payload_len < 19)
200 return false;
201 size_t p = 0;
202 uint16_t msg = (uint16_t)rtcm_br_u(payload, &p, 12);
203 if (msg != 1005 && msg != 1006)
204 return false;
205 if (msg == 1006 && payload_len < 21)
206 return false;
207 out->station_id = (uint16_t)rtcm_br_u(payload, &p, 12);
208 rtcm_br_u(payload, &p, 6); // DF021
209 rtcm_br_u(payload, &p, 1); // DF022
210 rtcm_br_u(payload, &p, 1); // DF023
211 rtcm_br_u(payload, &p, 1); // DF024
212 rtcm_br_u(payload, &p, 1); // DF141
213 out->ecef_x_01mm = rtcm_br_s(payload, &p, 38);
214 rtcm_br_u(payload, &p, 1); // DF142
215 rtcm_br_u(payload, &p, 1); // reserved
216 out->ecef_y_01mm = rtcm_br_s(payload, &p, 38);
217 rtcm_br_u(payload, &p, 2); // DF364
218 out->ecef_z_01mm = rtcm_br_s(payload, &p, 38);
219 if (msg == 1006)
220 {
221 out->antenna_height_01mm = (uint16_t)rtcm_br_u(payload, &p, 16);
222 out->has_height = true;
223 }
224 else
225 {
226 out->antenna_height_01mm = 0;
227 out->has_height = false;
228 }
229 return true;
230}
231
232#endif // DETWS_ENABLE_NTRIP_CASTER
RTCM 3.x framing + station-reference message codec (DETWS_ENABLE_NTRIP_CASTER).