1 /* subframe.c -- interpret satellite subframe data.
2  *
3  * This file is Copyright (c) 2010-2018 by the GPSD project
4  * SPDX-License-Identifier: BSD-2-clause
5  */
6 
7 #include "gpsd_config.h"  /* must be before all includes */
8 
9 #include <math.h>
10 
11 #include "gpsd.h"
12 
13 /* convert unsigned to signed */
14 #define uint2int( u, bit) ( (u & (1<<(bit-1))) ? u - (1<<bit) : u)
15 
gpsd_interpret_subframe_raw(struct gps_device_t * session,unsigned int tSVID,uint32_t words[])16 gps_mask_t gpsd_interpret_subframe_raw(struct gps_device_t *session,
17 				unsigned int tSVID, uint32_t words[])
18 {
19     unsigned int i;
20     uint8_t preamble;
21 
22     if (session->subframe_count++ == 0) {
23 	speed_t speed = gpsd_get_speed(session);
24 
25 	if (speed < 38400)
26 	    GPSD_LOG(LOG_WARN, &session->context->errout,
27 		     "speed less than 38,400 may cause data lag and loss of functionality\n");
28     }
29 
30     /*
31      * This function assumes an array of 10 ints, each of which carries
32      * a raw 30-bit GPS word use your favorite search engine to find the
33      * latest version of the specification: IS-GPS-200.
34      *
35      * Each raw 30-bit word is made of 24 data bits and 6 parity bits. The
36      * raw word and transport word are emitted from the GPS MSB-first and
37      * right justified. In other words, masking the raw word against 0x3f
38      * will return just the parity bits. Masking with 0x3fffffff and shifting
39      * 6 bits to the right returns just the 24 data bits. The top two bits
40      * (b31 and b30) are undefined; chipset designers may store copies of
41      * the bits D29* and D30* here to aid parity checking.
42      *
43      * Since bits D29* and D30* are not available in word 0, it is tested for
44      * a known preamble to help check its validity and determine whether the
45      * word is inverted.
46      *
47      */
48     GPSD_LOG(LOG_DATA, &session->context->errout,
49 	     "50B: gpsd_interpret_subframe_raw: "
50 	     "%08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n",
51 	     words[0], words[1], words[2], words[3], words[4],
52 	     words[5], words[6], words[7], words[8], words[9]);
53 
54     preamble = (uint8_t)((words[0] >> 22) & 0xFF);
55     if (preamble == 0x8b) {	/* preamble is inverted */
56 	words[0] ^= 0x3fffffc0;	/* invert */
57     } else if (preamble != 0x74) {
58 	/* strangely this is very common, so don't log it */
59 	GPSD_LOG(LOG_DATA, &session->context->errout,
60 		 "50B: gpsd_interpret_subframe_raw: bad preamble 0x%x\n",
61 		 preamble);
62 	return 0;
63     }
64     words[0] = (words[0] >> 6) & 0xffffff;
65 
66     for (i = 1; i < 10; i++) {
67 	int invert;
68 	uint32_t parity;
69 	/* D30* says invert */
70 	invert = (words[i] & 0x40000000) ? 1 : 0;
71 	/* inverted data, invert it back */
72 	if (invert) {
73 	    words[i] ^= 0x3fffffc0;
74 	}
75 	parity = (uint32_t)isgps_parity((isgps30bits_t)words[i]);
76 	if (parity != (words[i] & 0x3f)) {
77 	    GPSD_LOG(LOG_DATA, &session->context->errout,
78 		     "50B: gpsd_interpret_subframe_raw parity fail words[%d] 0x%x != 0x%x\n",
79 		     i, parity, (words[i] & 0x1));
80 	    return 0;
81 	}
82 	words[i] = (words[i] >> 6) & 0xffffff;
83     }
84 
85     return gpsd_interpret_subframe(session, tSVID, words);
86 }
87 
88 /* you can find up to date almanac data for comparision here:
89  * https://gps.afspc.af.mil/gps/Current/current.alm
90  */
subframe_almanac(const struct gpsd_errout_t * errout,uint8_t tSVID,uint32_t words[],uint8_t subframe,uint8_t sv,uint8_t data_id,struct almanac_t * almp)91 static void subframe_almanac(const struct gpsd_errout_t *errout,
92 			     uint8_t tSVID, uint32_t words[],
93 			     uint8_t subframe, uint8_t sv,
94 			     uint8_t data_id,
95 			     struct almanac_t *almp)
96 {
97     almp->sv     = sv; /* ignore the 0 sv problem for now */
98     almp->e      = ( words[2] & 0x00FFFF);
99     almp->d_eccentricity  = pow(2.0,-21) * almp->e;
100     /* carefull, each SV can have more than 2 toa's active at the same time
101      * you can not just store one or two almanacs for each sat */
102     almp->toa      = ((words[3] >> 16) & 0x0000FF);
103     almp->l_toa    = almp->toa << 12;
104     almp->deltai   = ( words[3] & 0x00FFFF);
105     almp->d_deltai = pow(2.0, -19) * almp->deltai;
106     almp->Omegad   = ((words[4] >>  8) & 0x00FFFF);
107     almp->d_Omegad = pow(2.0, -38) * almp->Omegad;
108     almp->svh      = ( words[4] & 0x0000FF);
109     almp->sqrtA    = ( words[5] & 0xFFFFFF);
110     almp->d_sqrtA  = pow(2.0,-11) * almp->sqrtA;
111     almp->Omega0   = ( words[6] & 0xFFFFFF);
112     almp->Omega0   = uint2int(almp->Omega0, 24);
113     almp->d_Omega0 = pow(2.0, -23) * almp->Omega0;
114     almp->omega    = ( words[7] & 0xFFFFFF);
115     almp->omega    = uint2int(almp->omega, 24);
116     almp->d_omega  = pow(2.0, -23) * almp->omega;
117     almp->M0       = ( words[8] & 0x00FFFFFF);
118     almp->M0       = uint2int(almp->M0, 24);
119     /* if you want radians, multiply by GPS_PI, but we do semi-circles
120      * to match IS-GPS-200E */
121     almp->d_M0     = pow(2.0,-23) * almp->M0;
122     almp->af1      = ((words[9] >>  5) & 0x0007FF);
123     almp->af1      = (short)uint2int(almp->af1, 11);
124     almp->d_af1    = pow(2.0,-38) * almp->af1;
125     almp->af0      = ((words[9] >> 16) & 0x0000FF);
126     almp->af0    <<= 3;
127     almp->af0     |= ((words[9] >>  2) & 0x000007);
128     almp->af0      = (short)uint2int(almp->af0, 11);
129     almp->d_af0    = pow(2.0,-20) * almp->af0;
130     GPSD_LOG(LOG_PROG, errout,
131 	     "50B: SF:%d SV:%2u TSV:%2u data_id %d e:%g toa:%lu "
132 	     "deltai:%.10e Omegad:%.5e svh:%u sqrtA:%.10g Omega0:%.10e "
133 	     "omega:%.10e M0:%.11e af0:%.5e af1:%.5e\n",
134 	     subframe, almp->sv, tSVID, data_id,
135 	     almp->d_eccentricity,
136 	     almp->l_toa,
137 	     almp->d_deltai,
138 	     almp->d_Omegad,
139 	     almp->svh,
140 	     almp->d_sqrtA,
141 	     almp->d_Omega0,
142 	     almp->d_omega,
143 	     almp->d_M0,
144 	     almp->d_af0,
145 	     almp->d_af1);
146 }
147 
gpsd_interpret_subframe(struct gps_device_t * session,unsigned int tSVID,uint32_t words[])148 gps_mask_t gpsd_interpret_subframe(struct gps_device_t *session,
149 			     unsigned int tSVID, uint32_t words[])
150 {
151     /*
152      * Heavy black magic begins here!
153      *
154      * A description of how to decode these bits is at
155      * <http://home-2.worldonline.nl/~samsvl/nav2eu.htm>
156      *
157      * We're mostly looking for subframe 4 page 18 word 9, the leap second
158      * correction. This functions assumes an array of words without parity
159      * or inversion (inverted word 0 is OK). It may be called directly by a
160      * driver if the chipset emits acceptable data.
161      *
162      * To date this code has been tested on iTrax, SiRF and ublox.
163      */
164     /* FIXME!! I really doubt this is Big Endian compatible */
165     uint8_t preamble;
166     struct subframe_t *subp = &session->gpsdata.subframe;
167 
168     GPSD_LOG(LOG_DATA, &session->context->errout,
169 	     "50B: gpsd_interpret_subframe: (%d) "
170 	     "%06x %06x %06x %06x %06x %06x %06x %06x %06x %06x\n",
171 	     tSVID, words[0], words[1], words[2], words[3], words[4],
172 	     words[5], words[6], words[7], words[8], words[9]);
173 
174     preamble = (uint8_t)((words[0] >> 16) & 0x0FF);
175     if (preamble == 0x8b) {
176 	/* somehow missed an inversion */
177 	preamble ^= 0xff;
178 	words[0] ^= 0xffffff;
179     }
180     if (preamble != 0x74) {
181 	GPSD_LOG(LOG_WARN, &session->context->errout,
182 		 "50B: gpsd_interpret_subframe bad preamble: 0x%x header 0x%x\n",
183 		 preamble, words[0]);
184 	return 0;
185     }
186     subp->integrity = (bool)((words[0] >> 1) & 0x01);
187     /* The subframe ID is in the Hand Over Word (page 80) */
188     subp->TOW17 = ((words[1] >> 7) & 0x01FFFF);
189     subp->l_TOW17 = (long)(subp->TOW17 * 6);
190     subp->tSVID = (uint8_t)tSVID;
191     subp->subframe_num = ((words[1] >> 2) & 0x07);
192     subp->alert = (bool)((words[1] >> 6) & 0x01);
193     subp->antispoof = (bool)((words[1] >> 6) & 0x01);
194     GPSD_LOG(LOG_PROG, &session->context->errout,
195 	     "50B: SF:%d SV:%2u TOW17:%7lu Alert:%u AS:%u IF:%d\n",
196 	     subp->subframe_num, subp->tSVID, subp->l_TOW17,
197 	     (unsigned)subp->alert, (unsigned)subp->antispoof,
198 	     (unsigned)subp->integrity);
199     /*
200      * Consult the latest revision of IS-GPS-200 for the mapping
201      * between magic SVIDs and pages.
202      */
203     subp->pageid  = (words[2] >> 16) & 0x00003F; /* only in frames 4 & 5 */
204     subp->data_id = (words[2] >> 22) & 0x3;      /* only in frames 4 & 5 */
205     subp->is_almanac = 0;
206 
207     switch (subp->subframe_num) {
208     case 1:
209 	/* subframe 1: clock parameters for transmitting SV */
210 	/* get Week Number (WN) from subframe 1 */
211 	/*
212 	 * This only extracts 10 bits of GPS week.
213 	 * 13 bits are available in the extension CNAV message,
214 	 * which we don't decode yet because we don't know
215 	 * of any receiver that reports it.
216 	 */
217 	session->context->gps_week =
218 	    (unsigned short)((words[2] >> 14) & 0x03ff);
219 	subp->sub1.WN   = (uint16_t)session->context->gps_week;
220 	subp->sub1.l2   = (uint8_t)((words[2] >> 12) & 0x000003); /* L2 Code */
221 	subp->sub1.ura  = (unsigned int)((words[2] >>  8) & 0x00000F); /* URA Index */
222 	subp->sub1.hlth = (unsigned int)((words[2] >>  2) & 0x00003F); /* SV health */
223 	subp->sub1.IODC = (words[2] & 0x000003); /* IODC 2 MSB */
224 	subp->sub1.l2p  = ((words[3] >> 23) & 0x000001); /* L2 P flag */
225 	subp->sub1.Tgd  = (int8_t)( words[6] & 0x0000FF);
226 	subp->sub1.d_Tgd  = pow(2.0, -31) * (int)subp->sub1.Tgd;
227 	subp->sub1.toc  = ( words[7] & 0x00FFFF);
228 	subp->sub1.l_toc = (long)subp->sub1.toc  << 4;
229 	subp->sub1.af2  = (int8_t)((words[8] >> 16) & 0x0FF);
230 	subp->sub1.d_af2  = pow(2.0, -55) * (int)subp->sub1.af2;
231 	subp->sub1.af1  = (int16_t)( words[8] & 0x00FFFF);
232 	subp->sub1.d_af1  = pow(2.0, -43) * subp->sub1.af1;
233 	subp->sub1.af0  = (int32_t)((words[9] >>  2) & 0x03FFFFF);
234 	subp->sub1.af0  = uint2int(subp->sub1.af0, 22);
235 	subp->sub1.d_af0  = pow(2.0, -31) * subp->sub1.af0;
236 	subp->sub1.IODC <<= 8;
237 	subp->sub1.IODC |= ((words[7] >> 16) & 0x00FF);
238 	GPSD_LOG(LOG_PROG, &session->context->errout,
239 		 "50B: SF:1 SV:%2u WN:%4u IODC:%4u"
240 		 " L2:%u ura:%u hlth:%u L2P:%u Tgd:%g toc:%lu af2:%.4g"
241 		 " af1:%.6e af0:%.7e\n",
242 		 subp->tSVID,
243 		 subp->sub1.WN,
244 		 subp->sub1.IODC,
245 		 subp->sub1.l2,
246 		 subp->sub1.ura,
247 		 subp->sub1.hlth,
248 		 subp->sub1.l2p,
249 		 subp->sub1.d_Tgd,
250 		 subp->sub1.l_toc,
251 		 subp->sub1.d_af2,
252 		 subp->sub1.d_af1,
253 		 subp->sub1.d_af0);
254 	break;
255     case 2:
256 	/* subframe 2: ephemeris for transmitting SV */
257 	subp->sub2.IODE   = ((words[2] >> 16) & 0x00FF);
258 	subp->sub2.Crs    = (int16_t)( words[2] & 0x00FFFF);
259 	subp->sub2.d_Crs  = pow(2.0,-5) * subp->sub2.Crs;
260 	subp->sub2.deltan = (int16_t)((words[3] >>  8) & 0x00FFFF);
261 	subp->sub2.d_deltan  = pow(2.0,-43) * subp->sub2.deltan;
262 	subp->sub2.M0     = (int32_t)( words[3] & 0x0000FF);
263 	subp->sub2.M0   <<= 24;
264 	subp->sub2.M0    |= ( words[4] & 0x00FFFFFF);
265 	subp->sub2.d_M0   = pow(2.0,-31) * subp->sub2.M0 * GPS_PI;
266 	subp->sub2.Cuc    = (int16_t)((words[5] >>  8) & 0x00FFFF);
267 	subp->sub2.d_Cuc  = pow(2.0,-29) * subp->sub2.Cuc;
268 	subp->sub2.e      = ( words[5] & 0x0000FF);
269 	subp->sub2.e    <<= 24;
270 	subp->sub2.e     |= ( words[6] & 0x00FFFFFF);
271 	subp->sub2.d_eccentricity  = pow(2.0,-33) * subp->sub2.e;
272 	subp->sub2.Cus    = (int16_t)((words[7] >>  8) & 0x00FFFF);
273 	subp->sub2.d_Cus  = pow(2.0,-29) * subp->sub2.Cus;
274 	subp->sub2.sqrtA  = ( words[7] & 0x0000FF);
275 	subp->sub2.sqrtA <<= 24;
276 	subp->sub2.sqrtA |= ( words[8] & 0x00FFFFFF);
277 	subp->sub2.d_sqrtA = pow(2.0, -19) * subp->sub2.sqrtA;
278 	subp->sub2.toe    = ((words[9] >>  8) & 0x00FFFF);
279 	subp->sub2.l_toe  = (long)(subp->sub2.toe << 4);
280 	subp->sub2.fit    = ((words[9] >>  7) & 0x000001);
281 	subp->sub2.AODO   = ((words[9] >>  2) & 0x00001F);
282 	subp->sub2.u_AODO   = subp->sub2.AODO * 900;
283 	GPSD_LOG(LOG_PROG, &session->context->errout,
284 		 "50B: SF:2 SV:%2u IODE:%3u Crs:%.6e deltan:%.6e "
285 		 "M0:%.11e Cuc:%.6e e:%f Cus:%.6e sqrtA:%.11g "
286 		 "toe:%lu FIT:%u AODO:%5u\n",
287 		 subp->tSVID,
288 		 subp->sub2.IODE,
289 		 subp->sub2.d_Crs,
290 		 subp->sub2.d_deltan,
291 		 subp->sub2.d_M0,
292 		 subp->sub2.d_Cuc,
293 		 subp->sub2.d_eccentricity,
294 		 subp->sub2.d_Cus,
295 		 subp->sub2.d_sqrtA,
296 		 subp->sub2.l_toe,
297 		 subp->sub2.fit,
298 		 subp->sub2.u_AODO);
299 	break;
300     case 3:
301 	/* subframe 3: ephemeris for transmitting SV */
302 	subp->sub3.Cic      = (int16_t)((words[2] >>  8) & 0x00FFFF);
303 	subp->sub3.d_Cic    = pow(2.0, -29) * subp->sub3.Cic;
304 	subp->sub3.Omega0   = (int32_t)(words[2] & 0x0000FF);
305 	subp->sub3.Omega0 <<= 24;
306 	subp->sub3.Omega0  |= ( words[3] & 0x00FFFFFF);
307 	subp->sub3.d_Omega0 = pow(2.0, -31) * subp->sub3.Omega0;
308 	subp->sub3.Cis      = (int16_t)((words[4] >>  8) & 0x00FFFF);
309 	subp->sub3.d_Cis    = pow(2.0, -29) * subp->sub3.Cis;
310 	subp->sub3.i0       = (int32_t)(words[4] & 0x0000FF);
311 	subp->sub3.i0     <<= 24;
312 	subp->sub3.i0      |= ( words[5] & 0x00FFFFFF);
313 	subp->sub3.d_i0     = pow(2.0, -31) * subp->sub3.i0;
314 	subp->sub3.Crc      = (int16_t)((words[6] >>  8) & 0x00FFFF);
315 	subp->sub3.d_Crc    = pow(2.0, -5) * subp->sub3.Crc;
316 	subp->sub3.omega    = (int32_t)(words[6] & 0x0000FF);
317 	subp->sub3.omega  <<= 24;
318 	subp->sub3.omega   |= ( words[7] & 0x00FFFFFF);
319 	subp->sub3.d_omega  = pow(2.0, -31) * subp->sub3.omega;
320 	subp->sub3.Omegad   = (int32_t)(words[8] & 0x00FFFFFF);
321 	subp->sub3.Omegad   = uint2int(subp->sub3.Omegad, 24);
322 	subp->sub3.d_Omegad = pow(2.0, -43) * subp->sub3.Omegad;
323 	subp->sub3.IODE     = ((words[9] >> 16) & 0x0000FF);
324 	subp->sub3.IDOT     = (int16_t)((words[9] >>  2) & 0x003FFF);
325 	subp->sub3.IDOT     = uint2int(subp->sub3.IDOT, 14);
326 	subp->sub3.d_IDOT   = pow(2.0, -43) * subp->sub3.IDOT;
327 	GPSD_LOG(LOG_PROG, &session->context->errout,
328 		 "50B: SF:3 SV:%2u IODE:%3u I IDOT:%.6g Cic:%.6e Omega0:%.11e "
329 		 " Cis:%.7g i0:%.11e Crc:%.7g omega:%.11e Omegad:%.6e\n",
330 		 subp->tSVID, subp->sub3.IODE, subp->sub3.d_IDOT,
331 		 subp->sub3.d_Cic, subp->sub3.d_Omega0, subp->sub3.d_Cis,
332 		 subp->sub3.d_i0, subp->sub3.d_Crc, subp->sub3.d_omega,
333 		 subp->sub3.d_Omegad );
334 	break;
335     case 4:
336 	{
337 	    int i = 0;   /* handy loop counter */
338 	    int sv = -2;
339 	    switch (subp->pageid) {
340 	    case 0:
341 		/* almanac for dummy sat 0, which is same as transmitting sat */
342 		sv = 0;
343 		break;
344 	    case 1:
345 	    case 6:
346 	    case 11:
347 	    case 16:
348 	    case 21:
349 	    case 57:
350 		/* for some inscutable reason these pages are all sent
351 		 * as page 57, IS-GPS-200E Table 20-V */
352 		break;
353 	    case 12:
354 	    case 24:
355 	    case 62:
356 		/* for some inscrutable reason these pages are all sent
357 		 * as page 62, IS-GPS-200E Table 20-V */
358 		break;
359 	    case 14:
360 	    case 53:
361 		/* for some inscrutable reason page 14 is sent
362 		 * as page 53, IS-GPS-200E Table 20-V */
363 		break;
364 	    case 15:
365 	    case 54:
366 		/* for some inscrutable reason page 15 is sent
367 		 * as page 54, IS-GPS-200E Table 20-V */
368 		break;
369 	    case 19:
370 		/* for some inscrutable reason page 20 is sent
371 		 * as page 58, IS-GPS-200E Table 20-V */
372 		/* reserved page */
373 		break;
374 	    case 20:
375 		/* for some inscrutable reason page 20 is sent
376 		 * as page 59, IS-GPS-200E Table 20-V */
377 		/* reserved page */
378 		break;
379 	    case 22:
380 	    case 60:
381 		/* for some inscrutable reason page 22 is sent
382 		 * as page 60, IS-GPS-200E Table 20-V */
383 		/* reserved page */
384 		break;
385 	    case 23:
386 	    case 61:
387 		/* for some inscrutable reason page 23 is sent
388 		 * as page 61, IS-GPS-200E Table 20-V */
389 		/* reserved page */
390 		break;
391 
392 	    /* almanac data for SV 25 through 32 respectively; */
393 	    case 2:
394 		sv = 25;
395 		break;
396 	    case 3:
397 		sv = 26;
398 		break;
399 	    case 4:
400 		sv = 27;
401 		break;
402 	    case 5:
403 		sv = 28;
404 		break;
405 	    case 7:
406 		sv = 29;
407 		break;
408 	    case 8:
409 		sv = 30;
410 		break;
411 	    case 9:
412 		sv = 31;
413 		break;
414 	    case 10:
415 		sv = 32;
416 		break;
417 
418 	    case 13:
419 	    case 52:
420 		/* NMCT */
421 		sv = -1;
422 		subp->sub4_13.ai      = (unsigned char)((words[2] >> 22) & 0x000003);
423 		subp->sub4_13.ERD[1]  = (char)((words[2] >>  8) & 0x00003F);
424 		subp->sub4_13.ERD[2]  = (char)((words[2] >>  2) & 0x00003F);
425 		subp->sub4_13.ERD[3]  = (char)((words[2] >>  0) & 0x000003);
426 		subp->sub4_13.ERD[3] <<= 2;
427 		subp->sub4_13.ERD[3] |= (char)((words[3] >> 20) & 0x00000F);
428 
429 		subp->sub4_13.ERD[4]  = (char)((words[3] >> 14) & 0x00003F);
430 		subp->sub4_13.ERD[5]  = (char)((words[3] >>  8) & 0x00003F);
431 		subp->sub4_13.ERD[6]  = (char)((words[3] >>  2) & 0x00003F);
432 		subp->sub4_13.ERD[7]  = (char)((words[3] >>  0) & 0x000003);
433 
434 		subp->sub4_13.ERD[7] <<= 2;
435 		subp->sub4_13.ERD[7] |= (char)((words[4] >> 20) & 0x00000F);
436 		subp->sub4_13.ERD[8]  = (char)((words[4] >> 14) & 0x00003F);
437 		subp->sub4_13.ERD[9]  = (char)((words[4] >>  8) & 0x00003F);
438 		subp->sub4_13.ERD[10] = (char)((words[4] >>  2) & 0x00003F);
439 		subp->sub4_13.ERD[11] = (char)((words[4] >>  0) & 0x00000F);
440 
441 		subp->sub4_13.ERD[11] <<= 2;
442 		subp->sub4_13.ERD[11] |= (char)((words[5] >> 20) & 0x00000F);
443 		subp->sub4_13.ERD[12]  = (char)((words[5] >> 14) & 0x00003F);
444 		subp->sub4_13.ERD[13]  = (char)((words[5] >>  8) & 0x00003F);
445 		subp->sub4_13.ERD[14]  = (char)((words[5] >>  2) & 0x00003F);
446 		subp->sub4_13.ERD[15]  = (char)((words[5] >>  0) & 0x000003);
447 
448 		subp->sub4_13.ERD[15] <<= 2;
449 		subp->sub4_13.ERD[15] |= (char)((words[6] >> 20) & 0x00000F);
450 		subp->sub4_13.ERD[16]  = (char)((words[6] >> 14) & 0x00003F);
451 		subp->sub4_13.ERD[17]  = (char)((words[6] >>  8) & 0x00003F);
452 		subp->sub4_13.ERD[18]  = (char)((words[6] >>  2) & 0x00003F);
453 		subp->sub4_13.ERD[19]  = (char)((words[6] >>  0) & 0x000003);
454 
455 		subp->sub4_13.ERD[19] <<= 2;
456 		subp->sub4_13.ERD[19] |= (char)((words[7] >> 20) & 0x00000F);
457 		subp->sub4_13.ERD[20]  = (char)((words[7] >> 14) & 0x00003F);
458 		subp->sub4_13.ERD[21]  = (char)((words[7] >>  8) & 0x00003F);
459 		subp->sub4_13.ERD[22]  = (char)((words[7] >>  2) & 0x00003F);
460 		subp->sub4_13.ERD[23]  = (char)((words[7] >>  0) & 0x000003);
461 
462 		subp->sub4_13.ERD[23] <<= 2;
463 		subp->sub4_13.ERD[23] |= (char)((words[8] >> 20) & 0x00000F);
464 		subp->sub4_13.ERD[24]  = (char)((words[8] >> 14) & 0x00003F);
465 		subp->sub4_13.ERD[25]  = (char)((words[8] >>  8) & 0x00003F);
466 		subp->sub4_13.ERD[26]  = (char)((words[8] >>  2) & 0x00003F);
467 		subp->sub4_13.ERD[27]  = (char)((words[8] >>  0) & 0x000003);
468 
469 		subp->sub4_13.ERD[27] <<= 2;
470 		subp->sub4_13.ERD[27] |= (char)((words[9] >> 20) & 0x00000F);
471 		subp->sub4_13.ERD[28]  = (char)((words[9] >> 14) & 0x00003F);
472 		subp->sub4_13.ERD[29]  = (char)((words[9] >>  8) & 0x00003F);
473 		subp->sub4_13.ERD[30]  = (char)((words[9] >>  2) & 0x00003F);
474 
475 		for ( i = 1; i < 31; i++ ) {
476 		    subp->sub4_13.ERD[i]  = uint2int(subp->sub4_13.ERD[i], 6);
477 		}
478 
479 		GPSD_LOG(LOG_PROG, &session->context->errout,
480 			 "50B: SF:4-13 data_id %d ai:%u "
481 			 "ERD1:%d ERD2:%d ERD3:%d ERD4:%d "
482 			 "ERD5:%d ERD6:%d ERD7:%d ERD8:%d "
483 			 "ERD9:%d ERD10:%d ERD11:%d ERD12:%d "
484 			 "ERD13:%d ERD14:%d ERD15:%d ERD16:%d "
485 			 "ERD17:%d ERD18:%d ERD19:%d ERD20:%d "
486 			 "ERD21:%d ERD22:%d ERD23:%d ERD24:%d "
487 			 "ERD25:%d ERD26:%d ERD27:%d ERD28:%d "
488 			 "ERD29:%d ERD30:%d\n",
489 			 subp->data_id, subp->sub4_13.ai,
490 			 subp->sub4_13.ERD[1], subp->sub4_13.ERD[2],
491 			 subp->sub4_13.ERD[3], subp->sub4_13.ERD[4],
492 			 subp->sub4_13.ERD[5], subp->sub4_13.ERD[6],
493 			 subp->sub4_13.ERD[7], subp->sub4_13.ERD[8],
494 			 subp->sub4_13.ERD[9], subp->sub4_13.ERD[10],
495 			 subp->sub4_13.ERD[11], subp->sub4_13.ERD[12],
496 			 subp->sub4_13.ERD[13], subp->sub4_13.ERD[14],
497 			 subp->sub4_13.ERD[15], subp->sub4_13.ERD[16],
498 			 subp->sub4_13.ERD[17], subp->sub4_13.ERD[18],
499 			 subp->sub4_13.ERD[19], subp->sub4_13.ERD[20],
500 			 subp->sub4_13.ERD[21], subp->sub4_13.ERD[22],
501 			 subp->sub4_13.ERD[23], subp->sub4_13.ERD[24],
502 			 subp->sub4_13.ERD[25], subp->sub4_13.ERD[26],
503 			 subp->sub4_13.ERD[27], subp->sub4_13.ERD[28],
504 			 subp->sub4_13.ERD[29], subp->sub4_13.ERD[30]);
505 		break;
506 
507 	    case 25:
508 	    case 63:
509 		/* for some inscrutable reason page 25 is sent
510 		 * as page 63, IS-GPS-200E Table 20-V */
511 		/* A-S flags/SV configurations for 32 SVs,
512 		 * plus SV health for SV 25 through 32
513 		 */
514 
515 		sv = -1;
516 		subp->sub4_25.svf[1]  = (unsigned char)((words[2] >> 12) & 0x0F);
517 		subp->sub4_25.svf[2]  = (unsigned char)((words[2] >>  8) & 0x0F);
518 		subp->sub4_25.svf[3]  = (unsigned char)((words[2] >>  4) & 0x0F);
519 		subp->sub4_25.svf[4]  = (unsigned char)((words[2] >>  0) & 0x0F);
520 		subp->sub4_25.svf[5]  = (unsigned char)((words[3] >> 20) & 0x0F);
521 		subp->sub4_25.svf[6]  = (unsigned char)((words[3] >> 16) & 0x0F);
522 		subp->sub4_25.svf[7]  = (unsigned char)((words[3] >> 12) & 0x0F);
523 		subp->sub4_25.svf[8]  = (unsigned char)((words[3] >>  8) & 0x0F);
524 		subp->sub4_25.svf[9]  = (unsigned char)((words[3] >>  4) & 0x0F);
525 		subp->sub4_25.svf[10] = (unsigned char)((words[3] >>  0) & 0x0F);
526 		subp->sub4_25.svf[11] = (unsigned char)((words[4] >> 20) & 0x0F);
527 		subp->sub4_25.svf[12] = (unsigned char)((words[4] >> 16) & 0x0F);
528 		subp->sub4_25.svf[13] = (unsigned char)((words[4] >> 12) & 0x0F);
529 		subp->sub4_25.svf[14] = (unsigned char)((words[4] >>  8) & 0x0F);
530 		subp->sub4_25.svf[15] = (unsigned char)((words[4] >>  4) & 0x0F);
531 		subp->sub4_25.svf[16] = (unsigned char)((words[4] >>  0) & 0x0F);
532 		subp->sub4_25.svf[17] = (unsigned char)((words[5] >> 20) & 0x0F);
533 		subp->sub4_25.svf[18] = (unsigned char)((words[5] >> 16) & 0x0F);
534 		subp->sub4_25.svf[19] = (unsigned char)((words[5] >> 12) & 0x0F);
535 		subp->sub4_25.svf[20] = (unsigned char)((words[5] >>  8) & 0x0F);
536 		subp->sub4_25.svf[21] = (unsigned char)((words[5] >>  4) & 0x0F);
537 		subp->sub4_25.svf[22] = (unsigned char)((words[5] >>  0) & 0x0F);
538 		subp->sub4_25.svf[23] = (unsigned char)((words[6] >> 20) & 0x0F);
539 		subp->sub4_25.svf[24] = (unsigned char)((words[6] >> 16) & 0x0F);
540 		subp->sub4_25.svf[25] = (unsigned char)((words[6] >> 12) & 0x0F);
541 		subp->sub4_25.svf[26] = (unsigned char)((words[6] >>  8) & 0x0F);
542 		subp->sub4_25.svf[27] = (unsigned char)((words[6] >>  4) & 0x0F);
543 		subp->sub4_25.svf[28] = (unsigned char)((words[6] >>  0) & 0x0F);
544 		subp->sub4_25.svf[29] = (unsigned char)((words[7] >> 20) & 0x0F);
545 		subp->sub4_25.svf[30] = (unsigned char)((words[7] >> 16) & 0x0F);
546 		subp->sub4_25.svf[31] = (unsigned char)((words[7] >> 12) & 0x0F);
547 		subp->sub4_25.svf[32] = (unsigned char)((words[7] >>  8) & 0x0F);
548 
549 		subp->sub4_25.svhx[0] = ((words[7] >>  0) & 0x00003F);
550 		subp->sub4_25.svhx[1] = ((words[8] >> 18) & 0x00003F);
551 		subp->sub4_25.svhx[2] = ((words[8] >> 12) & 0x00003F);
552 		subp->sub4_25.svhx[3] = ((words[8] >>  6) & 0x00003F);
553 		subp->sub4_25.svhx[4] = ((words[8] >>  0) & 0x00003F);
554 		subp->sub4_25.svhx[5] = ((words[9] >> 18) & 0x00003F);
555 		subp->sub4_25.svhx[6] = ((words[9] >> 12) & 0x00003F);
556 		subp->sub4_25.svhx[7] = ((words[9] >>  6) & 0x00003F);
557 
558 		GPSD_LOG(LOG_PROG, &session->context->errout,
559 			 "50B: SF:4-25 data_id %d "
560 			 "SV1:%u SV2:%u SV3:%u SV4:%u "
561 			 "SV5:%u SV6:%u SV7:%u SV8:%u "
562 			 "SV9:%u SV10:%u SV11:%u SV12:%u "
563 			 "SV13:%u SV14:%u SV15:%u SV16:%u "
564 			 "SV17:%u SV18:%u SV19:%u SV20:%u "
565 			 "SV21:%u SV22:%u SV23:%u SV24:%u "
566 			 "SV25:%u SV26:%u SV27:%u SV28:%u "
567 			 "SV29:%u SV30:%u SV31:%u SV32:%u "
568 			 "SVH25:%u SVH26:%u SVH27:%u SVH28:%u "
569 			 "SVH29:%u SVH30:%u SVH31:%u SVH32:%u\n",
570 			 subp->data_id,
571 			 subp->sub4_25.svf[1],  subp->sub4_25.svf[2],
572 			 subp->sub4_25.svf[3],  subp->sub4_25.svf[4],
573 			 subp->sub4_25.svf[5],  subp->sub4_25.svf[6],
574 			 subp->sub4_25.svf[7],  subp->sub4_25.svf[8],
575 			 subp->sub4_25.svf[9],  subp->sub4_25.svf[10],
576 			 subp->sub4_25.svf[11], subp->sub4_25.svf[12],
577 			 subp->sub4_25.svf[13], subp->sub4_25.svf[14],
578 			 subp->sub4_25.svf[15], subp->sub4_25.svf[16],
579 			 subp->sub4_25.svf[17], subp->sub4_25.svf[18],
580 			 subp->sub4_25.svf[19], subp->sub4_25.svf[20],
581 			 subp->sub4_25.svf[21], subp->sub4_25.svf[22],
582 			 subp->sub4_25.svf[23], subp->sub4_25.svf[24],
583 			 subp->sub4_25.svf[25], subp->sub4_25.svf[26],
584 			 subp->sub4_25.svf[27], subp->sub4_25.svf[28],
585 			 subp->sub4_25.svf[29], subp->sub4_25.svf[30],
586 			 subp->sub4_25.svf[31], subp->sub4_25.svf[32],
587 			 subp->sub4_25.svhx[0],   subp->sub4_25.svhx[1],
588 			 subp->sub4_25.svhx[2],   subp->sub4_25.svhx[3],
589 			 subp->sub4_25.svhx[4],   subp->sub4_25.svhx[5],
590 			 subp->sub4_25.svhx[6],   subp->sub4_25.svhx[7]);
591 		break;
592 
593 	    case 33:
594 	    case 34:
595 	    case 35:
596 	    case 36:
597 	    case 37:
598 	    case 38:
599 	    case 39:
600 	    case 40:
601 	    case 41:
602 	    case 42:
603 	    case 43:
604 	    case 44:
605 	    case 45:
606 	    case 46:
607 	    case 47:
608 	    case 48:
609 	    case 49:
610 	    case 50:
611 		/* unassigned */
612 		break;
613 
614 	    case 51:
615 		/* unknown */
616 		break;
617 
618 	    case 17:
619 	    case 55:
620 		/* for some inscrutable reason page 17 is sent
621 		 * as page 55, IS-GPS-200E Table 20-V */
622 		sv = -1;
623 		/*
624 		 * "The requisite 176 bits shall occupy bits 9 through 24
625 		 * of word TWO, the 24 MSBs of words THREE through EIGHT,
626 		 * plus the 16 MSBs of word NINE." (word numbers changed
627  		 * to account for zero-indexing)
628 		 * Since we've already stripped the low six parity bits,
629 		 * and shifted the data to a byte boundary, we can just
630 		 * copy it out. */
631 
632 		i = 0;
633 		subp->sub4_17.str[i++] = (words[2] >> 8) & 0xff;
634 		subp->sub4_17.str[i++] = (words[2]) & 0xff;
635 
636 		subp->sub4_17.str[i++] = (words[3] >> 16) & 0xff;
637 		subp->sub4_17.str[i++] = (words[3] >> 8) & 0xff;
638 		subp->sub4_17.str[i++] = (words[3]) & 0xff;
639 
640 		subp->sub4_17.str[i++] = (words[4] >> 16) & 0xff;
641 		subp->sub4_17.str[i++] = (words[4] >> 8) & 0xff;
642 		subp->sub4_17.str[i++] = (words[4]) & 0xff;
643 
644 		subp->sub4_17.str[i++] = (words[5] >> 16) & 0xff;
645 		subp->sub4_17.str[i++] = (words[5] >> 8) & 0xff;
646 		subp->sub4_17.str[i++] = (words[5]) & 0xff;
647 
648 		subp->sub4_17.str[i++] = (words[6] >> 16) & 0xff;
649 		subp->sub4_17.str[i++] = (words[6] >> 8) & 0xff;
650 		subp->sub4_17.str[i++] = (words[6]) & 0xff;
651 
652 		subp->sub4_17.str[i++] = (words[7] >> 16) & 0xff;
653 		subp->sub4_17.str[i++] = (words[7] >> 8) & 0xff;
654 		subp->sub4_17.str[i++] = (words[7]) & 0xff;
655 
656 		subp->sub4_17.str[i++] = (words[8] >> 16) & 0xff;
657 		subp->sub4_17.str[i++] = (words[8] >> 8) & 0xff;
658 		subp->sub4_17.str[i++] = (words[8]) & 0xff;
659 
660 		subp->sub4_17.str[i++] = (words[9] >> 16) & 0xff;
661 		subp->sub4_17.str[i++] = (words[9] >> 8) & 0xff;
662 		subp->sub4_17.str[i] = '\0';
663 		GPSD_LOG(LOG_PROG, &session->context->errout,
664 			 "50B: SF:4-17 system message: %.24s\n",
665 			 subp->sub4_17.str);
666 		break;
667 	    case 18:
668 	    case 56:
669 		/* for some inscrutable reason page 18 is sent
670 		 * as page 56, IS-GPS-200E Table 20-V */
671 		/* ionospheric and UTC data */
672 
673 		sv = -1;
674 		/* current leap seconds */
675 		subp->sub4_18.alpha0 = (int8_t)((words[2] >> 8) & 0x0000FF);
676 		subp->sub4_18.d_alpha0 = pow(2.0, -30) * (int)subp->sub4_18.alpha0;
677 		subp->sub4_18.alpha1 = (int8_t)((words[2] >> 0) & 0x0000FF);
678 		subp->sub4_18.d_alpha1 = pow(2.0, -27) * (int)subp->sub4_18.alpha1;
679 		subp->sub4_18.alpha2 = (int8_t)((words[3] >> 16) & 0x0000FF);
680 		subp->sub4_18.d_alpha2 = pow(2.0, -24) * (int)subp->sub4_18.alpha2;
681 		subp->sub4_18.alpha3 = (int8_t)((words[3] >>  8) & 0x0000FF);
682 		subp->sub4_18.d_alpha3 = pow(2.0, -24) * (int)subp->sub4_18.alpha3;
683 
684 		subp->sub4_18.beta0  = (int8_t)((words[3] >>  0) & 0x0000FF);
685 		subp->sub4_18.d_beta0 = pow(2.0, 11) * (int)subp->sub4_18.beta0;
686 		subp->sub4_18.beta1  = (int8_t)((words[4] >> 16) & 0x0000FF);
687 		subp->sub4_18.d_beta1 = pow(2.0, 14) * (int)subp->sub4_18.beta1;
688 		subp->sub4_18.beta2  = (int8_t)((words[4] >>  8) & 0x0000FF);
689 		subp->sub4_18.d_beta2 = pow(2.0, 16) * (int)subp->sub4_18.beta2;
690 		subp->sub4_18.beta3  = (int8_t)((words[4] >>  0) & 0x0000FF);
691 		subp->sub4_18.d_beta3 = pow(2.0, 16) * (int)subp->sub4_18.beta3;
692 
693 		subp->sub4_18.A1     = (int32_t)((words[5] >>  0) & 0xFFFFFF);
694 		subp->sub4_18.A1     = uint2int(subp->sub4_18.A1, 24);
695 		subp->sub4_18.d_A1   = pow(2.0,-50) * subp->sub4_18.A1;
696 		subp->sub4_18.A0     = (int32_t)((words[6] >>  0) & 0xFFFFFF);
697 		subp->sub4_18.A0   <<= 8;
698 		subp->sub4_18.A0    |= ((words[7] >> 16) & 0x0000FF);
699 		subp->sub4_18.d_A0   = pow(2.0,-30) * subp->sub4_18.A0;
700 
701 		/* careful WN is 10 bits, but WNt is 8 bits! */
702 		/* WNt (Week Number of LSF) */
703 		subp->sub4_18.tot    = ((words[7] >> 8) & 0x0000FF);
704 		subp->sub4_18.t_tot  = 2e12 * subp->sub4_18.tot;
705 		subp->sub4_18.WNt    = ((words[7] >> 0) & 0x0000FF);
706 		subp->sub4_18.leap  = (int8_t)((words[8] >> 16) & 0x0000FF);
707 		subp->sub4_18.WNlsf  = ((words[8] >>  8) & 0x0000FF);
708 
709 		/* DN (Day Number of LSF) */
710 		subp->sub4_18.DN = (words[8] & 0x0000FF);
711 		/* leap second future */
712 		subp->sub4_18.lsf = (int8_t)((words[9] >> 16) & 0x0000FF);
713 
714 		GPSD_LOG(LOG_PROG, &session->context->errout,
715 			 "50B: SF:4-18 a0:%.5g a1:%.5g a2:%.5g a3:%.5g "
716 			 "b0:%.5g b1:%.5g b2:%.5g b3:%.5g "
717 			 "A1:%.11e A0:%.11e tot:%lld WNt:%u "
718 			 "ls: %d WNlsf:%u DN:%u, lsf:%d\n",
719 			 subp->sub4_18.d_alpha0, subp->sub4_18.d_alpha1,
720 			 subp->sub4_18.d_alpha2, subp->sub4_18.d_alpha3,
721 			 subp->sub4_18.d_beta0, subp->sub4_18.d_beta1,
722 			 subp->sub4_18.d_beta2, subp->sub4_18.d_beta3,
723 			 subp->sub4_18.d_A1, subp->sub4_18.d_A0,
724 			 (long long)subp->sub4_18.t_tot, subp->sub4_18.WNt,
725 			 subp->sub4_18.leap, subp->sub4_18.WNlsf,
726 			 subp->sub4_18.DN, subp->sub4_18.lsf);
727 
728 		/* notify the leap seconds correction in the end
729 		 * of current day */
730 		/* IS-GPS-200 Revision E, paragraph 20.3.3.5.2.4 */
731                 /* FIXME: only allow LEAPs in June and December */
732 		// only need to check whole seconds
733 		if (((session->context->gps_week % 256) ==
734                      (unsigned short)subp->sub4_18.WNlsf) &&
735 		    (((subp->sub4_18.DN - 1) * SECS_PER_DAY) <
736                      session->context->gps_tow.tv_sec) &&
737 		    ((subp->sub4_18.DN * SECS_PER_DAY) >
738                      session->context->gps_tow.tv_sec)) {
739 
740 		   if (subp->sub4_18.leap < subp->sub4_18.lsf) {
741 			session->context->leap_notify = LEAP_ADDSECOND;
742 		   } else if (subp->sub4_18.leap > subp->sub4_18.lsf) {
743 			session->context->leap_notify = LEAP_DELSECOND;
744 		   } else {
745 			session->context->leap_notify = LEAP_NOWARNING;
746                    }
747 		} else {
748 		   session->context->leap_notify = LEAP_NOWARNING;
749                 }
750 
751 		session->context->leap_seconds = (int)subp->sub4_18.leap;
752 		session->context->valid |= LEAP_SECOND_VALID;
753 		break;
754 	    default:
755 		;			/* no op */
756 	    }
757 	    if ( -1 < sv ) {
758 		subp->is_almanac = 1;
759 		subframe_almanac(&session->context->errout,
760 				 subp->tSVID, words, subp->subframe_num,
761 				 (uint8_t)sv, subp->data_id,
762 				 &subp->sub4.almanac);
763 	    } else if ( -2 == sv ) {
764 		/* unknown or secret page */
765 		GPSD_LOG(LOG_PROG, &session->context->errout,
766 			 "50B: SF:4-%d data_id %d\n",
767 			 subp->pageid, subp->data_id);
768 		return 0;
769 	    }
770 	    /* else, already handled */
771 	}
772 	break;
773     case 5:
774 	/* Pages 0, dummy almanac for dummy SV 0
775 	 * Pages 1 through 24: almanac data for SV 1 through 24
776 	 * Page 25: SV health data for SV 1 through 24, the almanac
777 	 * reference time, the almanac reference week number.
778 	 */
779 	if ( 25 > subp->pageid ) {
780 	    subp->is_almanac = 1;
781 	    subframe_almanac(&session->context->errout,
782 			     subp->tSVID, words, subp->subframe_num,
783 			     subp->pageid, subp->data_id, &subp->sub5.almanac);
784 	} else if ( 51 == subp->pageid ) {
785 	    /* for some inscrutable reason page 25 is sent as page 51
786 	     * IS-GPS-200E Table 20-V */
787 
788 	    subp->sub5_25.toa   = ((words[2] >> 8) & 0x0000FF);
789 	    subp->sub5_25.l_toa <<= 12;
790 	    subp->sub5_25.WNa   = ( words[2] & 0x0000FF);
791 	    subp->sub5_25.sv[1] = ((words[2] >> 18) & 0x00003F);
792 	    subp->sub5_25.sv[2] = ((words[2] >> 12) & 0x00003F);
793 	    subp->sub5_25.sv[3] = ((words[2] >>  6) & 0x00003F);
794 	    subp->sub5_25.sv[4] = ((words[2] >>  0) & 0x00003F);
795 	    subp->sub5_25.sv[5] = ((words[3] >> 18) & 0x00003F);
796 	    subp->sub5_25.sv[6] = ((words[3] >> 12) & 0x00003F);
797 	    subp->sub5_25.sv[7] = ((words[3] >>  6) & 0x00003F);
798 	    subp->sub5_25.sv[8] = ((words[3] >>  0) & 0x00003F);
799 	    subp->sub5_25.sv[9] = ((words[4] >> 18) & 0x00003F);
800 	    subp->sub5_25.sv[10] = ((words[4] >> 12) & 0x00003F);
801 	    subp->sub5_25.sv[11] = ((words[4] >>  6) & 0x00003F);
802 	    subp->sub5_25.sv[12] = ((words[4] >>  0) & 0x00003F);
803 	    subp->sub5_25.sv[13] = ((words[5] >> 18) & 0x00003F);
804 	    subp->sub5_25.sv[14] = ((words[5] >> 12) & 0x00003F);
805 	    subp->sub5_25.sv[15] = ((words[5] >>  6) & 0x00003F);
806 	    subp->sub5_25.sv[16] = ((words[5] >>  0) & 0x00003F);
807 	    subp->sub5_25.sv[17] = ((words[6] >> 18) & 0x00003F);
808 	    subp->sub5_25.sv[18] = ((words[6] >> 12) & 0x00003F);
809 	    subp->sub5_25.sv[19] = ((words[6] >>  6) & 0x00003F);
810 	    subp->sub5_25.sv[20] = ((words[6] >>  0) & 0x00003F);
811 	    subp->sub5_25.sv[21] = ((words[7] >> 18) & 0x00003F);
812 	    subp->sub5_25.sv[22] = ((words[7] >> 12) & 0x00003F);
813 	    subp->sub5_25.sv[23] = ((words[7] >>  6) & 0x00003F);
814 	    subp->sub5_25.sv[24] = ((words[7] >>  0) & 0x00003F);
815 	    GPSD_LOG(LOG_PROG, &session->context->errout,
816 		     "50B: SF:5-25 SV:%2u ID:%u toa:%lu WNa:%u "
817 		     "SV1:%u SV2:%u SV3:%u SV4:%u "
818 		     "SV5:%u SV6:%u SV7:%u SV8:%u "
819 		     "SV9:%u SV10:%u SV11:%u SV12:%u "
820 		     "SV13:%u SV14:%u SV15:%u SV16:%u "
821 		     "SV17:%u SV18:%u SV19:%u SV20:%u "
822 		     "SV21:%u SV22:%u SV23:%u SV24:%u\n",
823 		     subp->tSVID, subp->data_id,
824 		     subp->sub5_25.l_toa, subp->sub5_25.WNa,
825 		     subp->sub5_25.sv[1], subp->sub5_25.sv[2],
826 		     subp->sub5_25.sv[3], subp->sub5_25.sv[4],
827 		     subp->sub5_25.sv[5], subp->sub5_25.sv[6],
828 		     subp->sub5_25.sv[7], subp->sub5_25.sv[8],
829 		     subp->sub5_25.sv[9], subp->sub5_25.sv[10],
830 		     subp->sub5_25.sv[11], subp->sub5_25.sv[12],
831 		     subp->sub5_25.sv[13], subp->sub5_25.sv[14],
832 		     subp->sub5_25.sv[15], subp->sub5_25.sv[16],
833 		     subp->sub5_25.sv[17], subp->sub5_25.sv[18],
834 		     subp->sub5_25.sv[19], subp->sub5_25.sv[20],
835 		     subp->sub5_25.sv[21], subp->sub5_25.sv[22],
836 		     subp->sub5_25.sv[23], subp->sub5_25.sv[24]);
837 	} else {
838 	    /* unknown page */
839 	    GPSD_LOG(LOG_PROG, &session->context->errout,
840 		     "50B: SF:5-%d data_id %d uknown page\n",
841 		     subp->pageid, subp->data_id);
842 	    return 0;
843 	}
844 	break;
845     default:
846 	/* unknown/illegal subframe */
847 	return 0;
848     }
849     return SUBFRAME_SET;
850 }
851 
852