source: ntrip/trunk/BNC/tests/tools/mkcrafted.py

Last change on this file was 11130, checked in by stuerze, 7 days ago

Some fixes from security point of view

  • Property svn:executable set to *
File size: 9.8 KB
Line 
1#!/usr/bin/env python3
2"""Write a BNC raw file with crafted, CRC-valid RTCM3 frames that use the
3upper end of the field ranges allowed by the format (decoder robustness test).
4
5Streams (one station ID each):
6 CRAFT0IGS0 IGS-SSR VTEC (4076/201), decoded by SsrCorrIgs, followed by
7 RTCM 1300/1301/1302 with 31-character names (BNC's RTCM scan
8 lists them only for streams that also decode successfully);
9 in the first epoch also two RTCM 1033 (antenna and receiver
10 descriptors): a complete one and one whose receiver descriptor
11 length (200) runs beyond the end of the message
12 CRAFT0RTC0 RTCM-SSR VTEC (1264), decoded by SsrCorrRtcm
13 CRAFT0NEW0 new RTCM-SSR metadata (60), not dispatched by BNC at present;
14 decoded by ssr_harness.cpp with SsrCorrRtcmNew:
15 - 7 entries (maximum), additional parameter of 40 and 0 bits
16 - 31 entries (5-bit count, more than META_MAXNUMENTRIES=7)
17 and satellite antenna messages (DF+010-DF+021):
18 - BDS (83): C01, C17, C33, C49, C64 (mask bits in all four
19 16-bit quarters of DF394), individual blocks
20 - GPS (80): G01, G02, G32 sharing one block and one frequency
21 block; nadir angle corrections incl. the invalid value
22
23VTEC epochs (every 5 s):
24 t0 2 layers: layer 1 degree 16/order 2, layer 2 degree 2/order 2
25 t0+5 4 layers: layers 1-3 degree 1/order 1, layer 4 degree 16/order 16
26 t0+10 1 layer degree 2/order 2 (decoding continues normally)
27Degree and order are transmitted as N-1 in 4 bits, so 16 is the maximum
28(DF474/DF475, IDF037/IDF038). Coefficient C(d,o) is (100*d + o)/200 TECU
29and S(d,o) its negative, so every value in the .ssr output can be checked
30by eye; the heights are 100 km * layer number.
31
32With --rawfile, a raw file with hostile chunk headers is written instead
33(test of the raw file reader, data/crafted_rawfile.raw):
34 ../ESCAPE0 station ID with a path: RTCM-SSR VTEC whose .ssr file must be
35 written inside the output directory
36 CRAFT0RTC0 RTCM-SSR VTEC, 5 s later
37 then a chunk header announcing 2000000000 bytes, which must be rejected
38
39Usage: mkcrafted.py <out.raw>
40 mkcrafted.py --rawfile <out.raw>
41"""
42
43import datetime
44import os
45import sys
46
47sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
48from bncraw import crc24q, write_chunks # noqa: E402
49
50T0 = datetime.datetime(2026, 10, 23, 12, 0, 0) # GPS time, a Friday
51NAME31 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ01234"
52
53
54class BitWriter:
55 def __init__(self):
56 self.bits = []
57
58 def u(self, value, n):
59 assert 0 <= value < (1 << n), (value, n)
60 self.bits += [(value >> (n - 1 - i)) & 1 for i in range(n)]
61
62 def s(self, value, n):
63 assert -(1 << (n - 1)) <= value < (1 << (n - 1)), (value, n)
64 self.u(value & ((1 << n) - 1), n)
65
66 def text(self, string, nlen):
67 self.u(len(string), nlen)
68 for c in string.encode("ascii"):
69 self.u(c, 8)
70
71 def payload(self):
72 bits = self.bits + [0] * (-len(self.bits) % 8)
73 return bytes(int("".join(map(str, bits[i:i + 8])), 2)
74 for i in range(0, len(bits), 8))
75
76
77def frame(payload):
78 assert len(payload) <= 1023, len(payload)
79 head = bytes([0xD3, len(payload) >> 8, len(payload) & 0xFF]) + payload
80 return head + crc24q(head).to_bytes(3, "big")
81
82
83def gps_sow(t):
84 return (t.weekday() + 1) % 7 * 86400 + t.hour * 3600 + t.minute * 60 + t.second
85
86
87def vtec_body(w, t, layers):
88 """VTEC content after the message number(s): IDF003-IDF041 / DF385-DF478."""
89 w.u(gps_sow(t), 20) # epoch time 1 s
90 w.u(2, 4) # update interval 5 s
91 w.u(0, 1) # multiple message indicator
92 w.u(1, 4) # SSR IOD
93 w.u(4711, 16) # provider ID
94 w.u(1, 4) # solution ID
95 w.u(20, 9) # VTEC quality 1.0 TECU
96 w.u(len(layers) - 1, 2)
97 for n, (degree, order) in enumerate(layers, 1):
98 w.u(10 * n, 8) # height, 10 km units
99 w.u(degree - 1, 4)
100 w.u(order - 1, 4)
101 for o in range(order + 1):
102 for d in range(o, degree + 1):
103 w.s(100 * d + o, 16)
104 for o in range(1, order + 1):
105 for d in range(o, degree + 1):
106 w.s(-(100 * d + o), 16)
107
108
109def vtec_igs(t, layers):
110 w = BitWriter()
111 w.u(4076, 12)
112 w.u(1, 3) # IGS SSR version
113 w.u(201, 8) # IGS message subtype: VTEC
114 vtec_body(w, t, layers)
115 return frame(w.payload())
116
117
118def vtec_rtcm(t, layers):
119 w = BitWriter()
120 w.u(1264, 12)
121 vtec_body(w, t, layers)
122 return frame(w.payload())
123
124
125def service_crs():
126 w = BitWriter()
127 w.u(1300, 12)
128 w.text(NAME31, 5)
129 w.u(2000, 16) # coordinate epoch 1900 + 20.00
130 return frame(w.payload())
131
132
133def helmert():
134 w = BitWriter()
135 w.u(1301, 12)
136 w.text(NAME31, 5) # source name
137 w.text(NAME31[::-1], 5) # target name
138 w.u(1, 8) # system identification number
139 w.u(0, 10) # utilized transformation message indicator
140 w.u(61000 - 44244, 16) # MJD of t0
141 for n in (23, 23, 23, 32, 32, 32, 25, 17, 17, 17, 17, 17, 17, 14):
142 w.s(0, n)
143 return frame(w.payload())
144
145
146def rtcm_crs():
147 w = BitWriter()
148 w.u(1302, 12)
149 w.text(NAME31, 5)
150 w.u(1, 1) # anchor: plate-fixed
151 w.u(7, 5) # plate number: Eurasia
152 w.u(2, 3) # number of database links
153 w.text(NAME31, 5)
154 w.text(NAME31[::-1], 5)
155 return frame(w.payload())
156
157
158def descriptors(strings, truncate=False):
159 """RTCM 1033: antenna, setup ID, serial, receiver, firmware, serial."""
160 w = BitWriter()
161 w.u(1033, 12)
162 w.u(0, 12) # reference station ID
163 ant, antser, rec, firm, recser = strings
164 w.text(ant, 8)
165 w.u(0, 8) # antenna setup ID
166 w.text(antser, 8)
167 if truncate:
168 w.u(200, 8) # receiver descriptor length beyond the message
169 for c in rec.encode("ascii"):
170 w.u(c, 8)
171 else:
172 w.text(rec, 8)
173 w.text(firm, 8)
174 w.text(recser, 8)
175 return frame(w.payload())
176
177
178def metadata(entries):
179 """entries: list of None (default model) or number of additional bits."""
180 w = BitWriter()
181 w.u(60, 12)
182 w.u(1, 4) # SSR IOD
183 w.u(4711, 16) # provider ID
184 w.u(1, 4) # solution ID
185 w.u(len(entries), 5)
186 for n, bits in enumerate(entries):
187 w.u(n % 32, 5) # model correction type
188 w.u(1, 1) # applied
189 w.u(bits is not None, 1)
190 if bits is not None:
191 w.u(1, 3) # non-default model identifier
192 w.u(0, 1) # no data IOD
193 if bits is not None:
194 w.u(1, 1) # additional parameter follows
195 w.u(bits, 8)
196 for i in range(bits):
197 w.u(i & 1, 1)
198 return frame(w.payload())
199
200
201def antenna(msg, sats, shared, nadir):
202 """Satellite antenna message; sats: PRNs (1..64) of the mask."""
203 w = BitWriter()
204 w.u(msg, 12)
205 w.u(4711, 16) # provider ID
206 w.u(5, 6) # satellite antenna IOD
207 w.u(1, 1) # phase center information
208 w.u(0, 1) # group delay information
209 w.u(1, 1) # nadir angle dependent corrections follow
210 w.u(3, 5) # maximum off-nadir angle: corrections 0..3 deg
211 w.u(1, 4) # range extension: 3 + 1 = 4 bits
212 w.u(shared, 1) # satellite set indicator
213 mask = 0
214 for prn in sats:
215 mask |= 1 << (64 - prn)
216 w.u(mask, 64)
217 for n in range(1 if shared else len(sats)):
218 w.u(shared, 1) # frequency set indicator
219 w.u(0b101000, 6) # frequencies 0 and 2
220 for f in range(1 if shared else 2):
221 w.u(1, 1) # nadir correction follows
222 w.s(100 * n + 10 * f + 1, 12) # mm
223 for deg in range(4):
224 w.s(nadir[(n + f + deg) % len(nadir)], 4) # mm, -8: invalid
225 return frame(w.payload())
226
227
228def rawfile(path):
229 chunks = []
230 for i, sta in enumerate(["../ESCAPE0", "CRAFT0RTC0"]):
231 t = T0 + datetime.timedelta(seconds=5 * i)
232 chunks.append((t.isoformat(), sta, "RTCM_3", vtec_rtcm(t, [(2, 2)])))
233 write_chunks(path, chunks)
234 t = T0 + datetime.timedelta(seconds=10)
235 with open(path, "ab") as o:
236 o.write(("%s CRAFT0RTC0 RTCM_3 2000000000\n" % t.isoformat()).encode())
237 o.write(vtec_rtcm(t, [(2, 2)]) + b"\n")
238
239
240def main():
241 if len(sys.argv) == 3 and sys.argv[1] == "--rawfile":
242 rawfile(sys.argv[2])
243 return
244 if len(sys.argv) != 2:
245 sys.exit(__doc__)
246 epochs = [
247 [(16, 2), (2, 2)],
248 [(1, 1), (1, 1), (1, 1), (16, 16)],
249 [(2, 2)],
250 ]
251 chunks = []
252 for i, layers in enumerate(epochs):
253 t = T0 + datetime.timedelta(seconds=5 * i)
254 stamp = t.isoformat()
255 data = vtec_igs(t, layers) + service_crs() + helmert() + rtcm_crs()
256 if i == 0:
257 data += descriptors(["LEIAR25.R4 LEIT", "12345", "SEPT POLARX5",
258 "5.5.0", "3001234"])
259 data += descriptors(["TRUNCATED ANTENNA", "9", "RECEIVER", "", ""],
260 truncate=True)
261 chunks.append((stamp, "CRAFT0IGS0", "RTCM_3", data))
262 chunks.append((stamp, "CRAFT0RTC0", "RTCM_3", vtec_rtcm(t, layers)))
263 stamp = (T0 + datetime.timedelta(seconds=15)).isoformat()
264 chunks.append((stamp, "CRAFT0NEW0", "RTCM_3",
265 metadata([40, 0] + [None] * 5) + metadata([None] * 31)
266 + antenna(83, [1, 17, 33, 49, 64], 0, [-7, 0, 7, 3])
267 + antenna(80, [1, 2, 32], 1, [5, -8, -1, 2])))
268 write_chunks(sys.argv[1], chunks)
269
270
271if __name__ == "__main__":
272 main()
Note: See TracBrowser for help on using the repository browser.