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 }