1 /* Copyright 2022-2025 Thales Alenia Space
2 * Licensed to CS GROUP (CS) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * CS licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.orekit.gnss.rflink.gps;
18
19 import org.orekit.errors.OrekitException;
20 import org.orekit.errors.OrekitMessages;
21 import org.orekit.gnss.metric.parser.EncodedMessage;
22
23 /**
24 * Container for sub-frames in a GPS navigation message.
25 * @author Luc Maisonobe
26 * @since 12.0
27 */
28 public abstract class SubFrame {
29
30 /** TLM preamble. */
31 public static final int PREAMBLE_VALUE = 0x8b;
32
33 /** Words size. */
34 protected static final int WORD_SIZE = 30;
35
36 /** Size of parity field. */
37 protected static final int PARITY_SIZE = 6;
38
39 /** TLM preamble index. */
40 private static final int PREAMBLE = 0;
41
42 /** Telemetry message index. */
43 private static final int MESSAGE = 1;
44
45 /** Integrity status flag index. */
46 private static final int INTEGRITY_STATUS = 2;
47
48 /** Truncated Time Of Week count index. */
49 private static final int TOW_COUNT = 3;
50
51 /** Alert flag index. */
52 private static final int ALERT = 4;
53
54 /** Anti-spoofing flag index. */
55 private static final int ANTI_SPOOFING = 5;
56
57 /** Sub-frame ID index. */
58 private static final int ID = 6;
59
60 /** Raw data fields. */
61 private final int[] fields;
62
63 /** Simple constructor.
64 * @param words raw words
65 * @param nbFields number of fields in the sub-frame
66 * (including TLM and HOW data fields, excluding non-information and parity)
67 */
68 protected SubFrame(final int[] words, final int nbFields) {
69
70 this.fields = new int[nbFields];
71
72 // common fields present in telemetry and handover words for all sub-frames
73 setField(PREAMBLE, 1, 22, 8, words);
74 setField(MESSAGE, 1, 8, 14, words);
75 setField(INTEGRITY_STATUS, 1, 7, 1, words);
76 setField(TOW_COUNT, 2, 13, 17, words);
77 setField(ALERT, 2, 12, 1, words);
78 setField(ANTI_SPOOFING, 2, 11, 1, words);
79 setField(ID, 2, 8, 3, words);
80
81 if (getField(PREAMBLE) != PREAMBLE_VALUE) {
82 throw new OrekitException(OrekitMessages.INVALID_GNSS_DATA, getField(PREAMBLE));
83 }
84
85 }
86
87 /** Builder for sub-frames.
88 * <p>
89 * This builder creates the proper sub-frame type corresponding to the ID in handover word
90 * and the SV Id for sub-frames 4 and 5.
91 * </p>
92 * @param encodedMessage encoded message containing exactly one sub-frame
93 * @return sub-frame with TLM and HOW fields already set up
94 * @see SubFrame1
95 * @see SubFrame2
96 * @see SubFrame3
97 * @see SubFrame4A0
98 * @see SubFrame4A1
99 * @see SubFrame4B
100 * @see SubFrame4C
101 * @see SubFrame4D
102 * @see SubFrame4E
103 * @see SubFrameAlmanac
104 * @see SubFrameDummyAlmanac
105 */
106 public static SubFrame parse(final EncodedMessage encodedMessage) {
107
108 encodedMessage.start();
109
110 // get the raw words
111 final int[] words = new int[10];
112 for (int i = 0; i < words.length; ++i) {
113 words[i] = (int) encodedMessage.extractBits(30);
114 }
115
116 // check parity on all words
117 for (int i = 0; i < words.length; ++i) {
118 // we assume last word of previous sub frame had parity bits set to 0,
119 // using the non_information bits at the end of each sub-frame
120 if (!checkParity(i == 0 ? 0x0 : words[i - 1], words[i])) {
121 throw new OrekitException(OrekitMessages.GNSS_PARITY_ERROR, i + 1);
122 }
123 }
124
125 final int id = (words[1] >>> 8) & 0x7;
126 switch (id) {
127 case 1 : // IS-GPS-200 figure 40-1 sheet 1
128 return new SubFrame1(words);
129 case 2 : // IS-GPS-200 figure 40-1 sheet 2
130 return new SubFrame2(words);
131 case 3 : // IS-GPS-200 figure 40-1 sheet 3
132 return new SubFrame3(words);
133 case 4 : {
134 final int svId = (words[2] >>> 22) & 0x3F;
135 // see table 20-V for mapping between SV-ID and page format
136 switch (svId) {
137 case 0 : // almanac for dummy Sv
138 return new SubFrameDummyAlmanac(words);
139 case 57 : // pages 1, 6, 11, 16, 21
140 // IS-GPS-200 figure 40-1 sheet 6
141 return new SubFrame4A0(words);
142 case 25 : case 26 : case 27 : case 28 : case 29 : case 30 : case 31 : case 32 : // pages 2, 3, 4, 5, 7, 8, 9, 10
143 // IS-GPS-200 figure 40-1 sheets 4 is also applicable to sub-frame 4
144 return new SubFrameAlmanac(words);
145 case 53 : case 54 : case 55 : // pages 14, 15, 17
146 // IS-GPS-200 figure 40-1 sheet 11
147 return new SubFrame4B(words);
148 case 58 : case 59 : case 60 : case 61 : case 62 : // pages 12, 19, 20, 22, 23, 24
149 // IS-GPS-200 figure 40-1 sheet 7
150 return new SubFrame4A1(words);
151 case 52 : // page 13
152 // IS-GPS-200 figure 40-1 sheet 10
153 return new SubFrame4C(words);
154 case 56 : // page 18
155 // IS-GPS-200 figure 40-1 sheet 8
156 return new SubFrame4D(words);
157 case 63 : // page 25
158 // IS-GPS-200 figure 40-1 sheet 9
159 return new SubFrame4E(words);
160 default :
161 throw new OrekitException(OrekitMessages.INVALID_GNSS_DATA, svId);
162 }
163 }
164 case 5 : {
165 // IS-GPS-200 figure 40-1 sheets 4 and 5
166 final int page = (words[2] >>> 22) & 0x3F;
167 return page == 25 ? new SubFrame5B(words) : new SubFrameAlmanac(words);
168 }
169 default : throw new OrekitException(OrekitMessages.INVALID_GNSS_DATA, id);
170 }
171
172 }
173
174 /** Check parity.
175 * <p>
176 * This implements algorithm in table 20-XIV from IS-GPS-200N
177 * </p>
178 * @param previous previous 30 bits word (only two least significant bits are used)
179 * @param current current 30 bits word
180 * @return true if parity check succeeded
181 */
182 public static boolean checkParity(final int previous, final int current) {
183
184 final int d29Star = previous & 0x2;
185 final int d30Star = previous & 0x1;
186
187 final int d25 = 0x1 & Integer.bitCount(d29Star | (current & 0x3B1F3480)); // 111011000111110011010010000000
188 final int d26 = 0x1 & Integer.bitCount(d30Star | (current & 0x1D8F9A40)); // 011101100011111001101001000000
189 final int d27 = 0x1 & Integer.bitCount(d29Star | (current & 0x2EC7CD00)); // 101110110001111100110100000000
190 final int d28 = 0x1 & Integer.bitCount(d30Star | (current & 0x1763E680)); // 010111011000111110011010000000
191 final int d29 = 0x1 & Integer.bitCount(d30Star | (current & 0x2BB1F340)); // 101011101100011111001101000000
192 final int d30 = 0x1 & Integer.bitCount(d29Star | (current & 0x0B7A89C0)); // 001011011110101000100111000000
193
194 final int parity = ((((d25 << 1 | d26) << 1 | d27) << 1 | d28) << 1 | d29) << 1 | d30;
195
196 return (parity & 0x3F) == (current & 0x3F);
197
198 }
199
200 /** Check if the sub-frame has parity errors.
201 * @return true if frame has parity errors
202 */
203 public boolean hasParityErrors() {
204 return false;
205 }
206
207 /** Get a field.
208 * <p>
209 * The field indices are defined as constants in the various sub-frames classes.
210 * </p>
211 * @param fieldIndex field index (counting from 0)
212 * @return field value
213 */
214 protected int getField(final int fieldIndex) {
215 return fields[fieldIndex];
216 }
217
218 /** Set a field.
219 * @param fieldIndex field index (counting from 0)
220 * @param wordIndex word index (counting from 1, to match IS-GPS-200 tables)
221 * @param shift right shift to apply (i.e. number of LSB bits for next fields that should be removed)
222 * @param nbBits number of bits in the field
223 * @param words raw 30 bits words
224 */
225 protected void setField(final int fieldIndex, final int wordIndex,
226 final int shift, final int nbBits,
227 final int[] words) {
228 fields[fieldIndex] = (words[wordIndex - 1] >>> shift) & ((0x1 << nbBits) - 1);
229 }
230
231 /** Set a field.
232 * @param fieldIndex field index (counting from 0)
233 * @param wordIndexMSB word index containing MSB (counting from 1, to match IS-GPS-200 tables)
234 * @param shiftMSB right shift to apply to MSB (i.e. number of LSB bits for next fields that should be removed)
235 * @param nbBitsMSB number of bits in the MSB
236 * @param wordIndexLSB word index containing LSB (counting from 1, to match IS-GPS-200 tables)
237 * @param shiftLSB right shift to apply to LSB (i.e. number of LSB bits for next fields that should be removed)
238 * @param nbBitsLSB number of bits in the LSB
239 * @param words raw 30 bits words
240 */
241 protected void setField(final int fieldIndex,
242 final int wordIndexMSB, final int shiftMSB, final int nbBitsMSB,
243 final int wordIndexLSB, final int shiftLSB, final int nbBitsLSB,
244 final int[] words) {
245 final int msb = (words[wordIndexMSB - 1] >>> shiftMSB) & ((0x1 << nbBitsMSB) - 1);
246 final int lsb = (words[wordIndexLSB - 1] >>> shiftLSB) & ((0x1 << nbBitsLSB) - 1);
247 fields[fieldIndex] = msb << nbBitsLSB | lsb;
248 }
249
250 /** Get telemetry preamble.
251 * @return telemetry preamble
252 */
253 public int getPreamble() {
254 return getField(PREAMBLE);
255 }
256
257 /** Get telemetry message.
258 * @return telemetry message
259 */
260 public int getMessage() {
261 return getField(MESSAGE);
262 }
263
264 /** Get integrity status flag.
265 * @return integrity status flag
266 */
267 public int getIntegrityStatus() {
268 return getField(INTEGRITY_STATUS);
269 }
270
271 /** Get Time Of Week of next 12 second message.
272 * @return Time Of Week of next 12 second message (s)
273 */
274 public int getTow() {
275 return getField(TOW_COUNT) * 6;
276 }
277
278 /** Get alert flag.
279 * @return alert flag
280 */
281 public int getAlert() {
282 return getField(ALERT);
283 }
284
285 /** Get anti-spoofing flag.
286 * @return anti-spoofing flag
287 */
288 public int getAntiSpoofing() {
289 return getField(ANTI_SPOOFING);
290 }
291
292 /** Get sub-frame id.
293 * @return sub-frame id
294 */
295 public int getId() {
296 return getField(ID);
297 }
298
299 }