ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
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 PC_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 pc_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 {
30 crc ^= 0x1864CFBu;
31 }
32 }
33 }
34 return crc & 0xFFFFFFu;
35}
36
37// ---------------------------------------------------------------------------------------------
38// MSB-first bit I/O.
39// ---------------------------------------------------------------------------------------------
40
41void pc_rtcm_bw_init(RtcmBitWriter *w, uint8_t *buf, size_t cap)
42{
43 w->buf = buf;
44 w->cap_bits = cap * 8;
45 w->pos = 0;
46 w->ok = true;
47}
48
49void pc_rtcm_bw_u(RtcmBitWriter *w, uint64_t val, uint8_t nbits)
50{
51 if (!w->ok)
52 {
53 return;
54 }
55 if (nbits == 0 || nbits > 64 || w->pos + nbits > w->cap_bits)
56 {
57 w->ok = false;
58 return;
59 }
60 for (int i = (int)nbits - 1; i >= 0; i--)
61 {
62 if ((val >> i) & 1u) // buffer is pre-zeroed, so only set the 1 bits
63 {
64 size_t bp = w->pos;
65 w->buf[bp >> 3] |= (uint8_t)(0x80u >> (bp & 7u));
66 }
67 w->pos++;
68 }
69}
70
71void pc_rtcm_bw_s(RtcmBitWriter *w, int64_t val, uint8_t nbits)
72{
73 uint64_t mask = (nbits >= 64) ? ~0ULL : ((1ULL << nbits) - 1ULL);
74 pc_rtcm_bw_u(w, (uint64_t)val & mask, nbits);
75}
76
77uint64_t pc_rtcm_br_u(const uint8_t *buf, size_t *pos, uint8_t nbits)
78{
79 uint64_t v = 0;
80 for (uint8_t i = 0; i < nbits; i++)
81 {
82 size_t bp = (*pos)++;
83 uint64_t bit = (buf[bp >> 3] >> (7u - (bp & 7u))) & 1u;
84 v = (v << 1) | bit;
85 }
86 return v;
87}
88
89int64_t pc_rtcm_br_s(const uint8_t *buf, size_t *pos, uint8_t nbits)
90{
91 uint64_t v = pc_rtcm_br_u(buf, pos, nbits);
92 if (nbits < 64 && (v & (1ULL << (nbits - 1))))
93 {
94 v |= ~((1ULL << nbits) - 1ULL); // sign-extend the two's-complement value
95 }
96 return (int64_t)v;
97}
98
99// ---------------------------------------------------------------------------------------------
100// Transport frame.
101// ---------------------------------------------------------------------------------------------
102
103size_t pc_rtcm3_sync(const uint8_t *buf, size_t len)
104{
105 for (size_t i = 0; i < len; i++)
106 {
107 if (buf[i] == RTCM3_PREAMBLE)
108 {
109 return i;
110 }
111 }
112 return len;
113}
114
115size_t pc_rtcm3_frame_parse(const uint8_t *buf, size_t len, Rtcm3Frame *out)
116{
117 if (len < RTCM3_HDR_LEN || buf[0] != RTCM3_PREAMBLE)
118 {
119 return 0; // not aligned to a preamble (caller runs pc_rtcm3_sync first)
120 }
121 uint16_t payload_len = (uint16_t)(((buf[1] & 0x03u) << 8) | buf[2]);
122 size_t frame_len = (size_t)RTCM3_HDR_LEN + payload_len + RTCM3_CRC_LEN;
123 if (len < frame_len)
124 {
125 return 0; // whole frame not buffered yet
126 }
127 const uint8_t *payload = buf + RTCM3_HDR_LEN;
128 uint32_t crc_calc = pc_rtcm3_crc24q(buf, (size_t)RTCM3_HDR_LEN + payload_len);
129 uint32_t crc_frame =
130 ((uint32_t)payload[payload_len] << 16) | ((uint32_t)payload[payload_len + 1] << 8) | payload[payload_len + 2];
131 if (out)
132 {
133 out->payload = payload;
134 out->payload_len = payload_len;
135 out->crc_ok = (crc_calc == crc_frame);
136 size_t p = 0;
137 out->msg_type = (payload_len >= 2) ? (uint16_t)pc_rtcm_br_u(payload, &p, 12) : 0;
138 }
139 return frame_len;
140}
141
142size_t pc_rtcm3_frame_build(uint8_t *out, size_t cap, const uint8_t *payload, uint16_t payload_len)
143{
144 if (payload_len > RTCM3_MAX_PAYLOAD)
145 {
146 return 0;
147 }
148 size_t frame_len = (size_t)RTCM3_HDR_LEN + payload_len + RTCM3_CRC_LEN;
149 if (!out || cap < frame_len)
150 {
151 return 0;
152 }
153 out[0] = RTCM3_PREAMBLE;
154 out[1] = (uint8_t)((payload_len >> 8) & 0x03u); // top 6 bits reserved = 0
155 out[2] = (uint8_t)(payload_len & 0xFFu);
156 if (payload_len && payload)
157 {
158 memcpy(out + RTCM3_HDR_LEN, payload, payload_len);
159 }
160 uint32_t crc = pc_rtcm3_crc24q(out, (size_t)RTCM3_HDR_LEN + payload_len);
161 out[RTCM3_HDR_LEN + payload_len] = (uint8_t)((crc >> 16) & 0xFFu);
162 out[RTCM3_HDR_LEN + payload_len + 1] = (uint8_t)((crc >> 8) & 0xFFu);
163 out[RTCM3_HDR_LEN + payload_len + 2] = (uint8_t)(crc & 0xFFu);
164 return frame_len;
165}
166
167// ---------------------------------------------------------------------------------------------
168// Message 1005 / 1006 - Stationary Antenna Reference Point (fields per RTCM 10403.x).
169// ---------------------------------------------------------------------------------------------
170
171namespace
172{
173size_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,
174 bool with_h, uint16_t h)
175{
176 uint8_t payload[21];
177 memset(payload, 0, sizeof payload);
178 RtcmBitWriter w;
179 pc_rtcm_bw_init(&w, payload, sizeof payload);
180 pc_rtcm_bw_u(&w, msg, 12); // DF002 message number
181 pc_rtcm_bw_u(&w, station_id & 0xFFF, 12); // DF003 reference station id
182 pc_rtcm_bw_u(&w, 0, 6); // DF021 ITRF realization year (unspecified)
183 pc_rtcm_bw_u(&w, 1, 1); // DF022 GPS indicator
184 pc_rtcm_bw_u(&w, 0, 1); // DF023 GLONASS indicator
185 pc_rtcm_bw_u(&w, 0, 1); // DF024 Galileo indicator
186 pc_rtcm_bw_u(&w, 0, 1); // DF141 reference-station indicator (0 = real/physical)
187 pc_rtcm_bw_s(&w, x, 38); // DF025 ECEF-X
188 pc_rtcm_bw_u(&w, 0, 1); // DF142 single receiver oscillator indicator
189 pc_rtcm_bw_u(&w, 0, 1); // reserved
190 pc_rtcm_bw_s(&w, y, 38); // DF026 ECEF-Y
191 pc_rtcm_bw_u(&w, 0, 2); // DF364 quarter cycle indicator
192 pc_rtcm_bw_s(&w, z, 38); // DF027 ECEF-Z
193 uint16_t body_bytes = 19;
194 if (with_h)
195 {
196 pc_rtcm_bw_u(&w, h, 16); // DF028 antenna height (1006)
197 body_bytes = 21;
198 }
199 // Every field width above is a constant; they total 152 bits (1005) or 168 (1006), and
200 // sizeof(payload) * 8 is 168 - so the writer can never have failed by this point.
201 if (!w.ok) // GCOVR_EXCL_LINE
202 {
203 return 0; // GCOVR_EXCL_LINE
204 }
205 return pc_rtcm3_frame_build(out, cap, payload, body_bytes);
206}
207} // namespace
208
209size_t pc_rtcm3_build_1005(uint8_t *out, size_t cap, uint16_t station_id, int64_t ecef_x_01mm, int64_t ecef_y_01mm,
210 int64_t ecef_z_01mm)
211{
212 return build_arp(out, cap, 1005, station_id, ecef_x_01mm, ecef_y_01mm, ecef_z_01mm, false, 0);
213}
214
215size_t pc_rtcm3_build_1006(uint8_t *out, size_t cap, uint16_t station_id, int64_t ecef_x_01mm, int64_t ecef_y_01mm,
216 int64_t ecef_z_01mm, uint16_t antenna_height_01mm)
217{
218 return build_arp(out, cap, 1006, station_id, ecef_x_01mm, ecef_y_01mm, ecef_z_01mm, true, antenna_height_01mm);
219}
220
221bool pc_rtcm3_parse_1005(const uint8_t *payload, uint16_t payload_len, Rtcm3StationArp *out)
222{
223 if (!payload || !out || payload_len < 19)
224 {
225 return false;
226 }
227 size_t p = 0;
228 uint16_t msg = (uint16_t)pc_rtcm_br_u(payload, &p, 12);
229 if (msg != 1005 && msg != 1006)
230 {
231 return false;
232 }
233 if (msg == 1006 && payload_len < 21)
234 {
235 return false;
236 }
237 out->station_id = (uint16_t)pc_rtcm_br_u(payload, &p, 12);
238 pc_rtcm_br_u(payload, &p, 6); // DF021
239 pc_rtcm_br_u(payload, &p, 1); // DF022
240 pc_rtcm_br_u(payload, &p, 1); // DF023
241 pc_rtcm_br_u(payload, &p, 1); // DF024
242 pc_rtcm_br_u(payload, &p, 1); // DF141
243 out->ecef_x_01mm = pc_rtcm_br_s(payload, &p, 38);
244 pc_rtcm_br_u(payload, &p, 1); // DF142
245 pc_rtcm_br_u(payload, &p, 1); // reserved
246 out->ecef_y_01mm = pc_rtcm_br_s(payload, &p, 38);
247 pc_rtcm_br_u(payload, &p, 2); // DF364
248 out->ecef_z_01mm = pc_rtcm_br_s(payload, &p, 38);
249 if (msg == 1006)
250 {
251 out->antenna_height_01mm = (uint16_t)pc_rtcm_br_u(payload, &p, 16);
252 out->has_height = true;
253 }
254 else
255 {
256 out->antenna_height_01mm = 0;
257 out->has_height = false;
258 }
259 return true;
260}
261
262#endif // PC_ENABLE_NTRIP_CASTER
uint32_t mask(uint8_t width)
Mask of width low bits (width 32 handled without a 32-bit shift, which is UB).
Definition crc.h:61
RTCM 3.x framing + station-reference message codec (PC_ENABLE_NTRIP_CASTER).