source: ntrip/trunk/rtcm3torinex/rtcm3torinex.c@ 755

Last change on this file since 755 was 744, checked in by stoecker, 17 years ago

Fixed INVALID data bug reported by Edgar Johansen

File size: 74.2 KB
RevLine 
[27]1/*
2 Converter for RTCM3 data to RINEX.
[744]3 $Id: rtcm3torinex.c,v 1.26 2007/12/20 10:42:10 stoecker Exp $
4 Copyright (C) 2005-2008 by Dirk Stöcker <stoecker@alberding.eu>
[27]5
[268]6 This software is a complete NTRIP-RTCM3 to RINEX converter as well as
7 a module of the BNC tool for multiformat conversion. Contact Dirk
[744]8 Stöcker for suggestions and bug reports related to the RTCM3 to RINEX
[268]9 conversion problems and the author of BNC for all the other problems.
10
[27]11 This program is free software; you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation; either version 2 of the License, or
14 (at your option) any later version.
15
16 This program is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License for more details.
20
21 You should have received a copy of the GNU General Public License
22 along with this program; if not, write to the Free Software
23 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 or read http://www.gnu.org/licenses/gpl.txt
25*/
26
27#include <ctype.h>
28#include <errno.h>
29#include <math.h>
30#include <signal.h>
[318]31#include <stdarg.h>
[27]32#include <stdio.h>
33#include <stdlib.h>
34#include <string.h>
35#include <sys/types.h>
36#include <time.h>
37#include <unistd.h>
38
[268]39#ifndef NO_RTCM3_MAIN
40#include <getopt.h>
41#include <netdb.h>
42#include <netinet/in.h>
43#include <sys/socket.h>
44#endif
[27]45
[269]46#ifndef sparc
47#include <stdint.h>
48#endif
49
[268]50#include "rtcm3torinex.h"
[27]51
52/* CVS revision and version */
[744]53static char revisionstr[] = "$Revision: 1.26 $";
[27]54
[439]55#ifndef COMPILEDATE
56#define COMPILEDATE " built " __DATE__
57#endif
58
[27]59static uint32_t CRC24(long size, const unsigned char *buf)
60{
61 uint32_t crc = 0;
62 int i;
63
64 while(size--)
65 {
66 crc ^= (*buf++) << (16);
67 for(i = 0; i < 8; i++)
68 {
69 crc <<= 1;
70 if(crc & 0x1000000)
71 crc ^= 0x01864cfb;
72 }
73 }
74 return crc;
75}
76
77static int GetMessage(struct RTCM3ParserData *handle)
78{
79 unsigned char *m, *e;
80 int i;
81
82 m = handle->Message+handle->SkipBytes;
83 e = handle->Message+handle->MessageSize;
84 handle->NeedBytes = handle->SkipBytes = 0;
85 while(e-m >= 3)
86 {
87 if(m[0] == 0xD3)
88 {
89 handle->size = ((m[1]&3)<<8)|m[2];
90 if(e-m >= handle->size+6)
91 {
92 if((uint32_t)((m[3+handle->size]<<16)|(m[3+handle->size+1]<<8)
93 |(m[3+handle->size+2])) == CRC24(handle->size+3, m))
94 {
95 handle->SkipBytes = handle->size;
96 break;
97 }
98 else
99 ++m;
100 }
101 else
102 {
103 handle->NeedBytes = handle->size+6;
104 break;
105 }
106 }
107 else
108 ++m;
109 }
110 if(e-m < 3)
111 handle->NeedBytes = 3;
[744]112
[27]113 /* copy buffer to front */
114 i = m - handle->Message;
115 if(i && m < e)
[318]116 memmove(handle->Message, m, (size_t)(handle->MessageSize-i));
[27]117 handle->MessageSize -= i;
118
119 return !handle->NeedBytes;
120}
121
122#define LOADBITS(a) \
123{ \
124 while((a) > numbits) \
125 { \
126 if(!size--) break; \
127 bitfield = (bitfield<<8)|*(data++); \
128 numbits += 8; \
129 } \
130}
131
132/* extract bits from data stream
133 b = variable to store result, a = number of bits */
134#define GETBITS(b, a) \
135{ \
136 LOADBITS(a) \
137 b = (bitfield<<(64-numbits))>>(64-(a)); \
138 numbits -= (a); \
139}
140
[502]141/* extract floating value from data stream
142 b = variable to store result, a = number of bits */
143#define GETFLOAT(b, a, c) \
144{ \
145 LOADBITS(a) \
146 b = ((double)((bitfield<<(64-numbits))>>(64-(a))))*(c); \
147 numbits -= (a); \
148}
149
150/* extract signed floating value from data stream
151 b = variable to store result, a = number of bits */
152#define GETFLOATSIGN(b, a, c) \
153{ \
154 LOADBITS(a) \
155 b = ((double)(((int64_t)(bitfield<<(64-numbits)))>>(64-(a))))*(c); \
156 numbits -= (a); \
157}
158
[27]159/* extract bits from data stream
160 b = variable to store result, a = number of bits */
161#define GETBITSSIGN(b, a) \
162{ \
163 LOADBITS(a) \
164 b = ((int64_t)(bitfield<<(64-numbits)))>>(64-(a)); \
165 numbits -= (a); \
166}
167
[502]168#define GETFLOATSIGNM(b, a, c) \
169{ int l; \
170 LOADBITS(a) \
171 l = (bitfield<<(64-numbits))>>(64-1); \
172 b = ((double)(((bitfield<<(64-(numbits-1))))>>(64-(a-1))))*(c); \
173 numbits -= (a); \
174 if(l) b *= -1.0; \
175}
176
[27]177#define SKIPBITS(b) { LOADBITS(b) numbits -= (b); }
178
[270]179struct leapseconds { /* specify the day of leap second */
180 int day; /* this is the day, where 23:59:59 exists 2 times */
181 int month; /* not the next day! */
182 int year;
183 int taicount;
[744]184};
[270]185static const int months[13] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
186static const struct leapseconds leap[] = {
187/*{31, 12, 1971, 11},*/
188/*{31, 12, 1972, 12},*/
189/*{31, 12, 1973, 13},*/
190/*{31, 12, 1974, 14},*/
191/*{31, 12, 1975, 15},*/
192/*{31, 12, 1976, 16},*/
193/*{31, 12, 1977, 17},*/
194/*{31, 12, 1978, 18},*/
195/*{31, 12, 1979, 19},*/
196{30, 06, 1981,20},
197{30, 06, 1982,21},
198{30, 06, 1983,22},
199{30, 06, 1985,23},
200{31, 12, 1987,24},
201{31, 12, 1989,25},
202{31, 12, 1990,26},
203{30, 06, 1992,27},
204{30, 06, 1993,28},
205{30, 06, 1994,29},
206{31, 12, 1995,30},
207{30, 06, 1997,31},
208{31, 12, 1998,32},
209{31, 12, 2005,33},
210{0,0,0,0} /* end marker */
211};
[328]212#define LEAPSECONDS 14 /* only needed for approx. time */
[270]213#define GPSLEAPSTART 19 /* 19 leap seconds existed at 6.1.1980 */
214
215static int longyear(int year, int month)
216{
217 if(!(year % 4) && (!(year % 400) || (year % 100)))
218 {
219 if(!month || month == 2)
220 return 1;
221 }
222 return 0;
223}
224
225static int gnumleap(int year, int month, int day)
226{
227 int ls = 0;
228 const struct leapseconds *l;
229
230 for(l = leap; l->taicount && year >= l->year; ++l)
231 {
232 if(year > l->year || month > l->month || day > l->day)
233 ls = l->taicount - GPSLEAPSTART;
234 }
235 return ls;
236}
237
[502]238static void updatetime(int *week, int *tow, int tk, int fixnumleap)
[270]239{
[502]240 int y,m,d,k,l, nul;
[270]241 unsigned int j = *week*(7*24*60*60) + *tow + 5*24*60*60+3*60*60;
242 int glo_daynumber = 0, glo_timeofday;
243 for(y = 1980; j >= (unsigned int)(k = (l = (365+longyear(y,0)))*24*60*60)
244 + gnumleap(y+1,1,1); ++y)
245 {
246 j -= k; glo_daynumber += l;
247 }
248 for(m = 1; j >= (unsigned int)(k = (l = months[m]+longyear(y, m))*24*60*60)
249 + gnumleap(y, m+1, 1); ++m)
250 {
251 j -= k; glo_daynumber += l;
252 }
253 for(d = 1; j >= 24UL*60UL*60UL + gnumleap(y, m, d+1); ++d)
254 j -= 24*60*60;
255 glo_daynumber -= 16*365+4-d;
[502]256 nul = gnumleap(y, m, d);
257 glo_timeofday = j-nul;
[270]258
259 if(tk < 5*60*1000 && glo_timeofday > 23*60*60)
260 *tow += 24*60*60;
261 else if(glo_timeofday < 5*60 && tk > 23*60*60*1000)
262 *tow -= 24*60*60;
263 *tow += tk/1000-glo_timeofday;
[502]264 if(fixnumleap)
265 *tow -= nul;
[270]266 if(*tow < 0) {*tow += 24*60*60*7; --*week; }
267 if(*tow >= 24*60*60*7) {*tow -= 24*60*60*7; ++*week; }
268}
269
[268]270int RTCM3Parser(struct RTCM3ParserData *handle)
[27]271{
272 int ret=0;
273
274 while(!ret && GetMessage(handle))
275 {
276 /* using 64 bit integer types, as it is much easier than handling
277 the long datatypes in 32 bit */
278 uint64_t numbits = 0, bitfield = 0;
279 int size = handle->size, type;
[270]280 int syncf, old = 0;
[27]281 unsigned char *data = handle->Message+3;
282
283 GETBITS(type,12)
284 switch(type)
285 {
[502]286 case 1019:
287 {
288 struct gpsephemeris *ge;
289 int sv;
290
291 ge = &handle->ephemerisGPS;
292 memset(ge, 0, sizeof(*ge));
293
294 GETBITS(sv, 6)
295 ge->satellite = (sv < 40 ? sv : sv+80);
296 GETBITS(ge->GPSweek, 10)
297 ge->GPSweek += 1024;
298 GETBITS(ge->URAindex, 4)
299 GETBITS(sv, 2)
300 if(sv & 1)
301 ge->flags |= GPSEPHF_L2PCODE;
302 if(sv & 2)
303 ge->flags |= GPSEPHF_L2CACODE;
304 GETFLOATSIGN(ge->IDOT, 14, PI/(double)(1<<30)/(double)(1<<13))
305 GETBITS(ge->IODE, 8)
306 GETBITS(ge->TOC, 16)
307 ge->TOC <<= 4;
308 GETFLOATSIGN(ge->clock_driftrate, 8, 1.0/(double)(1<<30)/(double)(1<<25))
309 GETFLOATSIGN(ge->clock_drift, 16, 1.0/(double)(1<<30)/(double)(1<<13))
310 GETFLOATSIGN(ge->clock_bias, 22, 1.0/(double)(1<<30)/(double)(1<<1))
311 GETBITS(ge->IODC, 10)
312 GETFLOATSIGN(ge->Crs, 16, 1.0/(double)(1<<5))
313 GETFLOATSIGN(ge->Delta_n, 16, PI/(double)(1<<30)/(double)(1<<13))
314 GETFLOATSIGN(ge->M0, 32, PI/(double)(1<<30)/(double)(1<<1))
315 GETFLOATSIGN(ge->Cuc, 16, 1.0/(double)(1<<29))
316 GETFLOAT(ge->e, 32, 1.0/(double)(1<<30)/(double)(1<<3))
317 GETFLOATSIGN(ge->Cus, 16, 1.0/(double)(1<<29))
318 GETFLOAT(ge->sqrt_A, 32, 1.0/(double)(1<<19))
319 GETBITS(ge->TOE, 16)
320 ge->TOE <<= 4;
321
322 GETFLOATSIGN(ge->Cic, 16, 1.0/(double)(1<<29))
323 GETFLOATSIGN(ge->OMEGA0, 32, PI/(double)(1<<30)/(double)(1<<1))
324 GETFLOATSIGN(ge->Cis, 16, 1.0/(double)(1<<29))
325 GETFLOATSIGN(ge->i0, 32, PI/(double)(1<<30)/(double)(1<<1))
326 GETFLOATSIGN(ge->Crc, 16, 1.0/(double)(1<<5))
327 GETFLOATSIGN(ge->omega, 32, PI/(double)(1<<30)/(double)(1<<1))
328 GETFLOATSIGN(ge->OMEGADOT, 24, PI/(double)(1<<30)/(double)(1<<13))
329 GETFLOATSIGN(ge->TGD, 8, 1.0/(double)(1<<30)/(double)(1<<1))
330 GETBITS(ge->SVhealth, 6)
331 GETBITS(sv, 1)
332 if(sv)
333 ge->flags |= GPSEPHF_L2PCODEDATA;
334
335 ret = 1019;
336 }
337 break;
338 case 1020:
339 {
340 struct glonassephemeris *ge;
341 int i;
342
343 ge = &handle->ephemerisGLONASS;
344 memset(ge, 0, sizeof(*ge));
345
346 ge->flags |= GLOEPHF_PAVAILABLE;
347 GETBITS(ge->almanac_number, 6)
348 GETBITS(i, 5)
349 ge->frequency_number = i-7;
350 GETBITS(i, 1)
351 if(i)
352 ge->flags |= GLOEPHF_ALMANACHEALTHY;
353 GETBITS(i, 1)
354 if(i)
355 ge->flags |= GLOEPHF_ALMANACHEALTHOK;
356 GETBITS(i, 2)
357 if(i & 1)
358 ge->flags |= GLOEPHF_P10TRUE;
359 if(i & 2)
360 ge->flags |= GLOEPHF_P11TRUE;
361 GETBITS(i, 5)
362 ge->tk = i*60*60;
363 GETBITS(i, 6)
364 ge->tk += i*60;
365 GETBITS(i, 1)
366 ge->tk += i*30;
367 GETBITS(i, 1)
368 if(i)
369 ge->flags |= GLOEPHF_UNHEALTHY;
370 GETBITS(i, 1)
371 if(i)
372 ge->flags |= GLOEPHF_P2TRUE;
373 GETBITS(i, 7)
374 ge->tb = i*15*60;
375 GETFLOATSIGNM(ge->x_velocity, 24, 1.0/(double)(1<<20))
376 GETFLOATSIGNM(ge->x_pos, 27, 1.0/(double)(1<<11))
377 GETFLOATSIGNM(ge->x_acceleration, 5, 1.0/(double)(1<<30))
378 GETFLOATSIGNM(ge->y_velocity, 24, 1.0/(double)(1<<20))
379 GETFLOATSIGNM(ge->y_pos, 27, 1.0/(double)(1<<11))
380 GETFLOATSIGNM(ge->y_acceleration, 5, 1.0/(double)(1<<30))
381 GETFLOATSIGNM(ge->z_velocity, 24, 1.0/(double)(1<<20))
382 GETFLOATSIGNM(ge->z_pos, 27, 1.0/(double)(1<<11))
383 GETFLOATSIGNM(ge->z_acceleration, 5, 1.0/(double)(1<<30))
384 GETBITS(i, 1)
385 if(i)
386 ge->flags |= GLOEPHF_P3TRUE;
387 GETFLOATSIGNM(ge->gamma, 11, 1.0/(double)(1<<30)/(double)(1<<10))
388 SKIPBITS(3) /* GLONASS-M P, GLONASS-M ln (third string) */
389 GETFLOATSIGNM(ge->tau, 22, 1.0/(double)(1<<30)) /* GLONASS tau n(tb) */
390 SKIPBITS(5) /* GLONASS-M delta tau n(tb) */
391 GETBITS(ge->E, 5)
392 /* GETBITS(b, 1) / * GLONASS-M P4 */
393 /* GETBITS(b, 4) / * GLONASS-M Ft */
394 /* GETBITS(b, 11) / * GLONASS-M Nt */
395 /* GETBITS(b, 2) / * GLONASS-M M */
396 /* GETBITS(b, 1) / * GLONASS-M The Availability of Additional Data */
397 /* GETBITS(b, 11) / * GLONASS-M Na */
398 /* GETFLOATSIGNM(b, 32, 1.0/(double)(1<<30)/(double)(1<<1)) / * GLONASS tau c */
399 /* GETBITS(b, 5) / * GLONASS-M N4 */
400 /* GETFLOATSIGNM(b, 22, 1.0/(double)(1<<30)/(double)(1<<1)) / * GLONASS-M tau GPS */
401 /* GETBITS(b, 1) / * GLONASS-M ln (fifth string) */
402 ge->GPSWeek = handle->GPSWeek;
403 ge->GPSTOW = handle->GPSTOW;
404 ret = 1020;
405 }
406 break;
[33]407 case 1001: case 1002: case 1003: case 1004:
[27]408 if(handle->GPSWeek)
409 {
410 int lastlockl1[64];
411 int lastlockl2[64];
412 struct gnssdata *gnss;
413 int i, num, wasamb=0;
414
415 for(i = 0; i < 64; ++i)
416 lastlockl1[i] = lastlockl2[i] = 0;
417
[270]418 gnss = &handle->DataNew;
[27]419
420 SKIPBITS(12) /* id */
421 GETBITS(i,30)
422 if(i/1000 < (int)handle->GPSTOW - 86400)
423 ++handle->GPSWeek;
424 handle->GPSTOW = i/1000;
[270]425 if(gnss->week && (gnss->timeofweek != i || gnss->week
426 != handle->GPSWeek))
427 {
428 handle->Data = *gnss;
429 memset(gnss, 0, sizeof(*gnss));
430 old = 1;
431 }
[27]432 gnss->timeofweek = i;
433 gnss->week = handle->GPSWeek;
434
[270]435 GETBITS(syncf,1) /* sync */
[27]436 GETBITS(i,5)
437 gnss->numsats = i;
438 SKIPBITS(4) /* smind, smint */
439
440 for(num = 0; num < gnss->numsats; ++num)
441 {
[33]442 int sv, code, l1range, c,l,s,ce,le,se,amb=0;
[27]443
444 GETBITS(sv, 6);
445 gnss->satellites[num] = (sv < 40 ? sv : sv+80);
446 /* L1 */
447 GETBITS(code, 1);
448 if(code)
449 {
450 c = GNSSDF_P1DATA; ce = GNSSENTRY_P1DATA;
451 l = GNSSDF_L1PDATA; le = GNSSENTRY_L1PDATA;
452 s = GNSSDF_S1PDATA; se = GNSSENTRY_S1PDATA;
453 }
454 else
455 {
456 c = GNSSDF_C1DATA; ce = GNSSENTRY_C1DATA;
457 l = GNSSDF_L1CDATA; le = GNSSENTRY_L1CDATA;
458 s = GNSSDF_S1CDATA; se = GNSSENTRY_S1CDATA;
459 }
460 GETBITS(l1range, 24);
461 GETBITSSIGN(i, 20);
[325]462 if((i&((1<<20)-1)) != 0x80000)
[27]463 {
[744]464 gnss->dataflags[num] |= (c|l);
465 gnss->measdata[num][ce] = l1range*0.02;
[27]466 gnss->measdata[num][le] = l1range*0.02+i*0.0005;
467 }
468 GETBITS(i, 7);
469 lastlockl1[sv] = i;
470 if(handle->lastlockl1[sv] > i)
471 gnss->dataflags[num] |= GNSSDF_LOCKLOSSL1;
[33]472 if(type == 1002 || type == 1004)
[27]473 {
[33]474 GETBITS(amb,8);
475 if(amb && (gnss->dataflags[num] & c))
476 {
477 gnss->measdata[num][ce] += amb*299792.458;
478 gnss->measdata[num][le] += amb*299792.458;
479 ++wasamb;
480 }
481 GETBITS(i, 8);
482 if(i)
483 {
484 gnss->dataflags[num] |= s;
485 gnss->measdata[num][se] = i*0.25;
486 i /= 4*4;
487 if(i > 9) i = 9;
488 else if(i < 1) i = 1;
489 gnss->snrL1[num] = i;
490 }
[27]491 }
[81]492 gnss->measdata[num][le] /= GPS_WAVELENGTH_L1;
[33]493 if(type == 1003 || type == 1004)
[27]494 {
[33]495 /* L2 */
496 GETBITS(code,2);
497 if(code)
498 {
499 c = GNSSDF_P2DATA; ce = GNSSENTRY_P2DATA;
500 l = GNSSDF_L2PDATA; le = GNSSENTRY_L2PDATA;
501 s = GNSSDF_S2PDATA; se = GNSSENTRY_S2PDATA;
502 }
503 else
504 {
505 c = GNSSDF_C2DATA; ce = GNSSENTRY_C2DATA;
506 l = GNSSDF_L2CDATA; le = GNSSENTRY_L2CDATA;
507 s = GNSSDF_S2CDATA; se = GNSSENTRY_S2CDATA;
508 }
509 GETBITSSIGN(i,14);
[325]510 if((i&((1<<14)-1)) != 0x2000)
[33]511 {
512 gnss->dataflags[num] |= c;
513 gnss->measdata[num][ce] = l1range*0.02+i*0.02
514 +amb*299792.458;
515 }
516 GETBITSSIGN(i,20);
[325]517 if((i&((1<<20)-1)) != 0x80000)
[33]518 {
519 gnss->dataflags[num] |= l;
520 gnss->measdata[num][le] = l1range*0.02+i*0.0005
521 +amb*299792.458;
522 }
523 GETBITS(i,7);
524 lastlockl2[sv] = i;
525 if(handle->lastlockl2[sv] > i)
526 gnss->dataflags[num] |= GNSSDF_LOCKLOSSL2;
527 if(type == 1004)
528 {
529 GETBITS(i, 8);
530 if(i)
531 {
532 gnss->dataflags[num] |= s;
533 gnss->measdata[num][se] = i*0.25;
534 i /= 4*4;
535 if(i > 9) i = 9;
536 else if(i < 1) i = 1;
537 gnss->snrL2[num] = i;
538 }
539 }
[81]540 gnss->measdata[num][le] /= GPS_WAVELENGTH_L2;
[27]541 }
542 }
543 for(i = 0; i < 64; ++i)
544 {
545 handle->lastlockl1[i] = lastlockl1[i];
546 handle->lastlockl2[i] = lastlockl2[i];
547 }
[270]548 if(!syncf && !old)
549 {
550 handle->Data = *gnss;
551 memset(gnss, 0, sizeof(*gnss));
552 }
553 if(!syncf || old)
554 {
555 if(wasamb) /* not RINEX compatible without */
556 ret = 1;
557 else
558 ret = 2;
559 }
[27]560 }
561 break;
[270]562 case 1009: case 1010: case 1011: case 1012:
563 {
564 int lastlockl1[64];
565 int lastlockl2[64];
566 struct gnssdata *gnss;
567 int i, num;
568 int wasamb=0;
569
570 for(i = 0; i < 64; ++i)
571 lastlockl1[i] = lastlockl2[i] = 0;
572
573 gnss = &handle->DataNew;
574
575 SKIPBITS(12) /* id */;
576 GETBITS(i,27) /* tk */
577
[502]578 updatetime(&handle->GPSWeek, &handle->GPSTOW, i, 0);
[270]579 i = handle->GPSTOW*1000;
580 if(gnss->week && (gnss->timeofweek != i || gnss->week
581 != handle->GPSWeek))
582 {
583 handle->Data = *gnss;
584 memset(gnss, 0, sizeof(*gnss));
585 old = 1;
586 }
587
588 gnss->timeofweek = i;
589 gnss->week = handle->GPSWeek;
590
591 GETBITS(syncf,1) /* sync */
592 GETBITS(i,5)
593 gnss->numsats += i;
594
595 SKIPBITS(4) /* smind, smint */
596
597 for(num = gnss->numsats-i; num < gnss->numsats; ++num)
598 {
599 int sv, code, l1range, c,l,s,ce,le,se,amb=0;
[325]600 int freq;
[270]601
602 GETBITS(sv, 6)
[325]603 gnss->satellites[num] = sv-1 + PRN_GLONASS_START;
604 /* L1 */
605 GETBITS(code, 1)
606 GETBITS(freq, 5)
607 if(code)
[270]608 {
[325]609 c = GNSSDF_P1DATA; ce = GNSSENTRY_P1DATA;
610 l = GNSSDF_L1PDATA; le = GNSSENTRY_L1PDATA;
611 s = GNSSDF_S1PDATA; se = GNSSENTRY_S1PDATA;
[270]612 }
613 else
614 {
[325]615 c = GNSSDF_C1DATA; ce = GNSSENTRY_C1DATA;
616 l = GNSSDF_L1CDATA; le = GNSSENTRY_L1CDATA;
617 s = GNSSDF_S1CDATA; se = GNSSENTRY_S1CDATA;
618 }
619 GETBITS(l1range, 25)
620 GETBITSSIGN(i, 20)
621 if((i&((1<<20)-1)) != 0x80000)
622 {
[744]623 /* Handle this like GPS. Actually for GLONASS L1 range is always
624 valid. To be on the save side, we handle it as invalid like we
625 do for GPS and also remove range in case of 0x80000. */
626 gnss->dataflags[num] |= (c|l);
627 gnss->measdata[num][ce] = l1range*0.02;
[325]628 gnss->measdata[num][le] = l1range*0.02+i*0.0005;
629 }
630 GETBITS(i, 7)
631 lastlockl1[sv] = i;
632 if(handle->lastlockl1[sv] > i)
633 gnss->dataflags[num] |= GNSSDF_LOCKLOSSL1;
634 if(type == 1010 || type == 1012)
635 {
636 GETBITS(amb,7)
637 if(amb && (gnss->dataflags[num] & c))
638 {
639 gnss->measdata[num][ce] += amb*599584.916;
640 gnss->measdata[num][le] += amb*599584.916;
641 ++wasamb;
642 }
643 GETBITS(i, 8)
644 if(i)
645 {
646 gnss->dataflags[num] |= s;
647 gnss->measdata[num][se] = i*0.25;
648 i /= 4*4;
649 if(i > 9) i = 9;
650 else if(i < 1) i = 1;
651 gnss->snrL1[num] = i;
652 }
653 }
654 gnss->measdata[num][le] /= GLO_WAVELENGTH_L1(freq-7);
655 if(type == 1011 || type == 1012)
656 {
657 /* L2 */
658 GETBITS(code,2)
[270]659 if(code)
660 {
[325]661 c = GNSSDF_P2DATA; ce = GNSSENTRY_P2DATA;
662 l = GNSSDF_L2PDATA; le = GNSSENTRY_L2PDATA;
663 s = GNSSDF_S2PDATA; se = GNSSENTRY_S2PDATA;
[270]664 }
665 else
666 {
[325]667 c = GNSSDF_C2DATA; ce = GNSSENTRY_C2DATA;
668 l = GNSSDF_L2CDATA; le = GNSSENTRY_L2CDATA;
669 s = GNSSDF_S2CDATA; se = GNSSENTRY_S2CDATA;
[270]670 }
[325]671 GETBITSSIGN(i,14)
672 if((i&((1<<14)-1)) != 0x2000)
[270]673 {
674 gnss->dataflags[num] |= c;
[325]675 gnss->measdata[num][ce] = l1range*0.02+i*0.02
676 +amb*599584.916;
[270]677 }
[325]678 GETBITSSIGN(i,20)
679 if((i&((1<<20)-1)) != 0x80000)
[270]680 {
681 gnss->dataflags[num] |= l;
[325]682 gnss->measdata[num][le] = l1range*0.02+i*0.0005
683 +amb*599584.915;
[270]684 }
[325]685 GETBITS(i,7)
686 lastlockl2[sv] = i;
687 if(handle->lastlockl2[sv] > i)
688 gnss->dataflags[num] |= GNSSDF_LOCKLOSSL2;
689 if(type == 1012)
[270]690 {
691 GETBITS(i, 8)
692 if(i)
693 {
694 gnss->dataflags[num] |= s;
695 gnss->measdata[num][se] = i*0.25;
696 i /= 4*4;
697 if(i > 9) i = 9;
698 else if(i < 1) i = 1;
[325]699 gnss->snrL2[num] = i;
[270]700 }
701 }
[325]702 gnss->measdata[num][le] /= GLO_WAVELENGTH_L2(freq-7);
[270]703 }
[325]704 if(!sv || sv > 24)
705 {
706 --num; --gnss->numsats;
707 }
[270]708 }
709 for(i = 0; i < 64; ++i)
710 {
711 handle->lastlockl1[i] = lastlockl1[i];
712 handle->lastlockl2[i] = lastlockl2[i];
713 }
714 if(!syncf && !old)
715 {
716 handle->Data = *gnss;
717 memset(gnss, 0, sizeof(*gnss));
718 }
719 if(!syncf || old)
720 {
721 if(wasamb) /* not RINEX compatible without */
722 ret = 1;
723 else
724 ret = 2;
725 }
726 }
727 break;
[27]728 }
729 }
730 return ret;
731}
732
733struct Header
734{
735 const char *version;
736 const char *pgm;
737 const char *marker;
[503]738 const char *markertype;
[27]739 const char *observer;
740 const char *receiver;
741 const char *antenna;
742 const char *position;
743 const char *antennaposition;
744 const char *wavelength;
745 const char *typesofobs; /* should not be modified outside */
[502]746 const char *typesofobsG; /* should not be modified outside */
747 const char *typesofobsR; /* should not be modified outside */
748 const char *typesofobsS; /* should not be modified outside */
[27]749 const char *timeoffirstobs; /* should not be modified outside */
750};
751
752#define MAXHEADERLINES 50
753#define MAXHEADERBUFFERSIZE 4096
754struct HeaderData
755{
756 union
757 {
758 struct Header named;
759 const char *unnamed[MAXHEADERLINES];
760 } data;
761 int numheaders;
762};
763
[270]764struct converttimeinfo {
765 int second; /* seconds of GPS time [0..59] */
766 int minute; /* minutes of GPS time [0..59] */
767 int hour; /* hour of GPS time [0..24] */
768 int day; /* day of GPS time [1..28..30(31)*/
769 int month; /* month of GPS time [1..12]*/
770 int year; /* year of GPS time [1980..] */
771};
772
773static void converttime(struct converttimeinfo *c, int week, int tow)
774{
775 int i, k, doy, j; /* temporary variables */
776 j = week*(7*24*60*60) + tow + 5*24*60*60;
777 for(i = 1980; j >= (k = (365+longyear(i,0))*24*60*60); ++i)
778 j -= k;
779 c->year = i;
780 doy = 1+ (j / (24*60*60));
781 j %= (24*60*60);
782 c->hour = j / (60*60);
783 j %= (60*60);
784 c->minute = j / 60;
785 c->second = j % 60;
786 j = 0;
787 for(i = 1; j + (k = months[i] + longyear(c->year,i)) < doy; ++i)
788 j += k;
789 c->month = i;
790 c->day = doy - j;
791}
792
[318]793#ifndef NO_RTCM3_MAIN
794void RTCM3Error(const char *fmt, ...)
795{
796 va_list v;
797 va_start(v, fmt);
798 vfprintf(stderr, fmt, v);
799 va_end(v);
800}
801#endif
802
803void RTCM3Text(const char *fmt, ...)
804{
805 va_list v;
806 va_start(v, fmt);
807 vprintf(fmt, v);
808 va_end(v);
809}
810
[503]811static int HandleRunBy(char *buffer, int buffersize, const char **u,
812int rinex3)
[502]813{
814 const char *user;
815 time_t t;
816 struct tm * t2;
817
818#ifdef NO_RTCM3_MAIN
819 if(revisionstr[0] == '$')
820 {
821 char *a;
822 int i=0;
823 for(a = revisionstr+11; *a && *a != ' '; ++a)
824 revisionstr[i++] = *a;
825 revisionstr[i] = 0;
826 }
827#endif
828
829 user= getenv("USER");
830 if(!user) user = "";
831 t = time(&t);
832 t2 = gmtime(&t);
833 if(u) *u = user;
834 return 1+snprintf(buffer, buffersize,
[503]835 rinex3 ?
836 "RTCM3TORINEX %-7.7s%-20.20s%04d%02d%02d %02d%02d%02d UTC "
837 "PGM / RUN BY / DATE" :
[502]838 "RTCM3TORINEX %-7.7s%-20.20s%04d-%02d-%02d %02d:%02d "
839 "PGM / RUN BY / DATE", revisionstr, user, 1900+t2->tm_year,
[503]840 t2->tm_mon+1, t2->tm_mday, t2->tm_hour, t2->tm_min, t2->tm_sec);
[502]841}
842
[318]843#define NUMSTARTSKIP 3
[268]844void HandleHeader(struct RTCM3ParserData *Parser)
[27]845{
846 struct HeaderData hdata;
847 char thebuffer[MAXHEADERBUFFERSIZE];
848 char *buffer = thebuffer;
[318]849 size_t buffersize = sizeof(thebuffer);
[27]850 int i;
851
[549]852 memset(&hdata, 0, sizeof(hdata));
853
[502]854 hdata.data.named.version = buffer;
855 i = 1+snprintf(buffer, buffersize,
856 "%9.2f OBSERVATION DATA M (Mixed)"
857 " RINEX VERSION / TYPE", Parser->rinex3 ? 3.0 : 2.11);
858 buffer += i; buffersize -= i;
[27]859
860 {
[30]861 const char *str;
[27]862 hdata.data.named.pgm = buffer;
[503]863 i = HandleRunBy(buffer, buffersize, &str, Parser->rinex3);
[27]864 buffer += i; buffersize -= i;
865 hdata.data.named.observer = buffer;
866 i = 1+snprintf(buffer, buffersize,
867 "%-20.20s "
868 "OBSERVER / AGENCY", str);
869 buffer += i; buffersize -= i;
870 }
871
872 hdata.data.named.marker =
873 "RTCM3TORINEX "
874 "MARKER NAME";
875
[503]876 hdata.data.named.markertype = !Parser->rinex3 ? 0 :
877 "GEODETIC "
878 "MARKER TYPE";
879
[27]880 hdata.data.named.receiver =
881 " "
882 "REC # / TYPE / VERS";
883
884 hdata.data.named.antenna =
885 " "
886 "ANT # / TYPE";
887
888 hdata.data.named.position =
889 " .0000 .0000 .0000 "
890 "APPROX POSITION XYZ";
891
892 hdata.data.named.antennaposition =
893 " .0000 .0000 .0000 "
894 "ANTENNA: DELTA H/E/N";
[744]895
[502]896 hdata.data.named.wavelength = Parser->rinex3 ? 0 :
[27]897 " 1 1 "
898 "WAVELENGTH FACT L1/2";
899
[502]900 if(Parser->rinex3)
[27]901 {
[502]902#define CHECKFLAGSNEW(a, b, c) \
903 if(flags & GNSSDF_##b##DATA) \
904 { \
905 Parser->dataflag##a[Parser->numdatatypes##a] = GNSSDF_##b##DATA; \
906 Parser->datapos##a[Parser->numdatatypes##a] = GNSSENTRY_##b##DATA; \
907 ++Parser->numdatatypes##a; \
908 snprintf(tbuffer+tbufferpos, sizeof(tbuffer)-tbufferpos, " "#c); \
909 tbufferpos += 4; \
910 }
911
912 int flags = Parser->startflags;
913 char tbuffer[6*RINEXENTRY_NUMBER+1];
914 int tbufferpos = 0;
915 for(i = 0; i < Parser->Data.numsats; ++i)
916 flags |= Parser->Data.dataflags[i];
917
918 CHECKFLAGSNEW(GPS, C1, C1C)
919 CHECKFLAGSNEW(GPS, L1C, L1C)
920 CHECKFLAGSNEW(GPS, D1C, D1C)
921 CHECKFLAGSNEW(GPS, S1C, S1C)
922 CHECKFLAGSNEW(GPS, P1, C1P)
923 CHECKFLAGSNEW(GPS, L1P, L1P)
924 CHECKFLAGSNEW(GPS, D1P, D1P)
925 CHECKFLAGSNEW(GPS, S1P, S1P)
926
927 hdata.data.named.typesofobsS = buffer;
928 i = 1+snprintf(buffer, buffersize,
929 "S %3d%-52.52s SYS / # / OBS TYPES", Parser->numdatatypesGPS, tbuffer);
930 buffer += i; buffersize -= i;
931
932 CHECKFLAGSNEW(GPS, P2, C2P)
933 CHECKFLAGSNEW(GPS, L2P, L2P)
934 CHECKFLAGSNEW(GPS, D2P, D2P)
935 CHECKFLAGSNEW(GPS, S2P, S2P)
936 CHECKFLAGSNEW(GPS, C2, C2X)
937 CHECKFLAGSNEW(GPS, L2C, L2X)
938 CHECKFLAGSNEW(GPS, D2C, D2X)
939 CHECKFLAGSNEW(GPS, S2C, S2X)
940
941 hdata.data.named.typesofobsG = buffer;
942 i = 1+snprintf(buffer, buffersize,
943 "G %3d%-52.52s SYS / # / OBS TYPES", Parser->numdatatypesGPS, tbuffer);
944 if(Parser->numdatatypesGPS>13)
945 {
946 i += snprintf(buffer+i-1, buffersize,
947 "\n %-52.52s SYS / # / OBS TYPES", tbuffer+13*4);
948 }
949 buffer += i; buffersize -= i;
950
951 tbufferpos = 0;
952
953 CHECKFLAGSNEW(GLO, C1, C1C)
954 CHECKFLAGSNEW(GLO, L1C, L1C)
955 CHECKFLAGSNEW(GLO, D1C, D1C)
956 CHECKFLAGSNEW(GLO, S1C, S1C)
957 CHECKFLAGSNEW(GLO, P1, C1P)
958 CHECKFLAGSNEW(GLO, L1P, L1P)
959 CHECKFLAGSNEW(GLO, D1P, D1P)
960 CHECKFLAGSNEW(GLO, S1P, S1P)
961 CHECKFLAGSNEW(GLO, P2, C2P)
962 CHECKFLAGSNEW(GLO, L2P, L2P)
963 CHECKFLAGSNEW(GLO, D2P, D2P)
964 CHECKFLAGSNEW(GLO, S2P, S2P)
965 CHECKFLAGSNEW(GLO, C2, C2C)
966 CHECKFLAGSNEW(GLO, L2C, L2C)
967 CHECKFLAGSNEW(GLO, D2C, D2C)
968 CHECKFLAGSNEW(GLO, S2C, S2C)
969
970 hdata.data.named.typesofobsR = buffer;
971 i = 1+snprintf(buffer, buffersize,
972 "R %3d%-52.52s SYS / # / OBS TYPES", Parser->numdatatypesGLO, tbuffer);
973 if(Parser->numdatatypesGLO>13)
974 {
975 i += snprintf(buffer+i-1, buffersize,
976 "\n %-52.52s SYS / # / OBS TYPES", tbuffer+13*4);
977 }
978 buffer += i; buffersize -= i;
979 }
980 else
981 {
[27]982#define CHECKFLAGS(a, b) \
[363]983 if(flags & GNSSDF_##a##DATA) \
[27]984 { \
[363]985 if(data[RINEXENTRY_##b##DATA]) \
986 { \
[502]987 Parser->dataflagGPS[data[RINEXENTRY_##b##DATA]-1] = GNSSDF_##a##DATA; \
988 Parser->dataposGPS[data[RINEXENTRY_##b##DATA]-1] = GNSSENTRY_##a##DATA; \
[363]989 } \
990 else \
991 { \
[502]992 Parser->dataflag[Parser->numdatatypesGPS] = GNSSDF_##a##DATA; \
993 Parser->datapos[Parser->numdatatypesGPS] = GNSSENTRY_##a##DATA; \
994 data[RINEXENTRY_##b##DATA] = ++Parser->numdatatypesGPS; \
[364]995 snprintf(tbuffer+tbufferpos, sizeof(tbuffer)-tbufferpos, " "#b); \
996 tbufferpos += 6; \
[363]997 } \
[27]998 }
999
[318]1000 int flags = Parser->startflags;
[27]1001 int data[RINEXENTRY_NUMBER];
1002 char tbuffer[6*RINEXENTRY_NUMBER+1];
1003 int tbufferpos = 0;
1004 for(i = 0; i < RINEXENTRY_NUMBER; ++i)
1005 data[i] = 0;
1006 for(i = 0; i < Parser->Data.numsats; ++i)
1007 flags |= Parser->Data.dataflags[i];
1008
1009 CHECKFLAGS(C1,C1)
1010 CHECKFLAGS(C2,C2)
1011 CHECKFLAGS(P1,P1)
1012 CHECKFLAGS(P2,P2)
1013 CHECKFLAGS(L1C,L1)
1014 CHECKFLAGS(L1P,L1)
1015 CHECKFLAGS(L2C,L2)
1016 CHECKFLAGS(L2P,L2)
1017 CHECKFLAGS(D1C,D1)
1018 CHECKFLAGS(D1P,D1)
1019 CHECKFLAGS(D2C,D2)
1020 CHECKFLAGS(D2P,D2)
1021 CHECKFLAGS(S1C,S1)
1022 CHECKFLAGS(S1P,S1)
1023 CHECKFLAGS(S2C,S2)
1024 CHECKFLAGS(S2P,S2)
1025
1026 hdata.data.named.typesofobs = buffer;
1027 i = 1+snprintf(buffer, buffersize,
[502]1028 "%6d%-54.54s# / TYPES OF OBSERV", Parser->numdatatypesGPS, tbuffer);
1029 if(Parser->numdatatypesGPS>9)
[27]1030 {
1031 i += snprintf(buffer+i-1, buffersize,
1032 "\n %-54.54s# / TYPES OF OBSERV", tbuffer+9*6);
1033 }
1034 buffer += i; buffersize -= i;
1035 }
1036
1037 {
1038 struct converttimeinfo cti;
1039 converttime(&cti, Parser->Data.week,
[268]1040 (int)floor(Parser->Data.timeofweek/1000.0));
[27]1041 hdata.data.named.timeoffirstobs = buffer;
[502]1042 i = 1+snprintf(buffer, buffersize,
[27]1043 " %4d %2d %2d %2d %2d %10.7f GPS "
[378]1044 "TIME OF FIRST OBS", cti.year, cti.month, cti.day, cti.hour,
[27]1045 cti.minute, cti.second + fmod(Parser->Data.timeofweek/1000.0,1.0));
1046
1047 buffer += i; buffersize -= i;
1048 }
1049
[503]1050 hdata.numheaders = 15;
[27]1051
1052 if(Parser->headerfile)
1053 {
1054 FILE *fh;
1055 if((fh = fopen(Parser->headerfile, "r")))
1056 {
[318]1057 size_t siz;
[27]1058 char *lastblockstart;
1059 if((siz = fread(buffer, 1, buffersize-1, fh)) > 0)
1060 {
1061 buffer[siz] = '\n';
1062 if(siz == buffersize)
1063 {
[318]1064 RTCM3Error("Header file is too large. Only %d bytes read.",
[364]1065 (int)siz);
[27]1066 }
1067 /* scan the file line by line and enter the entries in the list */
1068 /* warn for "# / TYPES OF OBSERV" and "TIME OF FIRST OBS" */
1069 /* overwrites entries, except for comments */
1070 lastblockstart = buffer;
[318]1071 for(i = 0; i < (int)siz; ++i)
[27]1072 {
1073 if(buffer[i] == '\n')
1074 { /* we found a line */
1075 char *end;
1076 while(buffer[i+1] == '\r')
1077 ++i; /* skip \r in case there are any */
1078 end = buffer+i;
1079 while(*end == '\t' || *end == ' ' || *end == '\r' || *end == '\n')
1080 *(end--) = 0;
1081 if(end-lastblockstart < 60+5) /* short line */
[318]1082 RTCM3Error("Short Header line '%s' ignored.\n", lastblockstart);
[27]1083 else
1084 {
1085 int pos;
1086 if(!strcmp("COMMENT", lastblockstart+60))
1087 pos = hdata.numheaders;
1088 else
1089 {
1090 for(pos = 0; pos < hdata.numheaders; ++pos)
1091 {
1092 if(!strcmp(hdata.data.unnamed[pos]+60, lastblockstart+60))
1093 break;
1094 }
1095 if(!strcmp("# / TYPES OF OBSERV", lastblockstart+60)
1096 || !strcmp("TIME OF FIRST OBS", lastblockstart+60))
1097 {
[318]1098 RTCM3Error("Overwriting header '%s' is dangerous.\n",
[27]1099 lastblockstart+60);
1100 }
1101 }
1102 if(pos >= MAXHEADERLINES)
1103 {
[318]1104 RTCM3Error("Maximum number of header lines of %d reached.\n",
[27]1105 MAXHEADERLINES);
1106 }
1107 else if(!strcmp("END OF HEADER", lastblockstart+60))
1108 {
[318]1109 RTCM3Error("End of header ignored.\n");
[27]1110 }
1111 else
1112 {
1113 hdata.data.unnamed[pos] = lastblockstart;
1114 if(pos == hdata.numheaders)
1115 ++hdata.numheaders;
1116 }
1117 }
1118 lastblockstart = buffer+i+1;
1119 }
1120 }
1121 }
1122 else
1123 {
[318]1124 RTCM3Error("Could not read data from headerfile '%s'.\n",
[27]1125 Parser->headerfile);
1126 }
1127 fclose(fh);
1128 }
1129 else
1130 {
[318]1131 RTCM3Error("Could not open header datafile '%s'.\n",
[27]1132 Parser->headerfile);
1133 }
1134 }
1135
[287]1136#ifndef NO_RTCM3_MAIN
[27]1137 for(i = 0; i < hdata.numheaders; ++i)
[502]1138 {
1139 if(hdata.data.unnamed[i] && hdata.data.unnamed[i][0])
1140 RTCM3Text("%s\n", hdata.data.unnamed[i]);
1141 }
[318]1142 RTCM3Text(" "
[27]1143 "END OF HEADER\n");
[287]1144#endif
[27]1145}
1146
[502]1147static void ConvLine(FILE *file, const char *fmt, ...)
1148{
1149 char buffer[100], *b;
1150 va_list v;
1151 va_start(v, fmt);
1152 vsnprintf(buffer, sizeof(buffer), fmt, v);
1153 for(b = buffer; *b; ++b)
1154 {
1155 if(*b == 'e') *b = 'D';
1156 }
1157 fprintf(file, "%s", buffer);
1158 va_end(v);
1159}
1160
[268]1161void HandleByte(struct RTCM3ParserData *Parser, unsigned int byte)
[27]1162{
1163 Parser->Message[Parser->MessageSize++] = byte;
1164 if(Parser->MessageSize >= Parser->NeedBytes)
1165 {
1166 int r;
1167 while((r = RTCM3Parser(Parser)))
1168 {
[502]1169 if(r == 1020 || r == 1019)
[27]1170 {
[502]1171 FILE *file = 0;
[318]1172
[502]1173 if(Parser->rinex3 && !(file = Parser->gpsfile))
1174 {
1175 const char *n = Parser->gpsephemeris ? Parser->gpsephemeris : Parser->glonassephemeris;
1176 if(n)
1177 {
1178 if(!(Parser->gpsfile = fopen(n, "w")))
1179 {
1180 RTCM3Error("Could not open ephemeris output file.\n");
1181 }
1182 else
1183 {
1184 char buffer[100];
1185 fprintf(Parser->gpsfile,
1186 "%9.2f%11sN: GNSS NAV DATA M: Mixed%12sRINEX VERSION / TYPE\n", 3.0, "", "");
[503]1187 HandleRunBy(buffer, sizeof(buffer), 0, Parser->rinex3);
[502]1188 fprintf(Parser->gpsfile, "%s\n%60sEND OF HEADER\n", buffer, "");
1189 }
1190 Parser->gpsephemeris = 0;
1191 Parser->glonassephemeris = 0;
1192 file = Parser->gpsfile;
1193 }
1194 }
[318]1195 else
1196 {
[502]1197 if(r == 1020)
1198 {
1199 if(Parser->glonassephemeris)
1200 {
1201 if(!(Parser->glonassfile = fopen(Parser->glonassephemeris, "w")))
1202 {
1203 RTCM3Error("Could not open GLONASS ephemeris output file.\n");
1204 }
1205 else
1206 {
1207 char buffer[100];
1208 fprintf(Parser->glonassfile,
1209 "%9.2f%11sG: GLONASS NAV DATA%21sRINEX VERSION / TYPE\n", 2.1, "", "");
[503]1210 HandleRunBy(buffer, sizeof(buffer), 0, Parser->rinex3);
[502]1211 fprintf(Parser->glonassfile, "%s\n%60sEND OF HEADER\n", buffer, "");
1212 }
1213 Parser->glonassephemeris = 0;
1214 }
1215 file = Parser->glonassfile;
1216 }
1217 else if(r == 1019)
1218 {
1219 if(Parser->gpsephemeris)
1220 {
1221 if(!(Parser->gpsfile = fopen(Parser->gpsephemeris, "w")))
1222 {
1223 RTCM3Error("Could not open GPS ephemeris output file.\n");
1224 }
1225 else
1226 {
1227 char buffer[100];
1228 fprintf(Parser->gpsfile,
1229 "%9.2f%11sN: GPS NAV DATA%25sRINEX VERSION / TYPE\n", 2.1, "", "");
[503]1230 HandleRunBy(buffer, sizeof(buffer), 0, Parser->rinex3);
[502]1231 fprintf(Parser->gpsfile, "%s\n%60sEND OF HEADER\n", buffer, "");
1232 }
1233 Parser->gpsephemeris = 0;
1234 }
1235 file = Parser->gpsfile;
1236 }
[318]1237 }
[502]1238 if(file)
1239 {
1240 if(r == 1020)
1241 {
1242 struct glonassephemeris *e = &Parser->ephemerisGLONASS;
1243 int w = e->GPSWeek, tow = e->GPSTOW, i;
1244 struct converttimeinfo cti;
1245
1246 updatetime(&w, &tow, e->tb*1000, 1);
1247 converttime(&cti, w, tow);
1248
1249 i = e->tk-3*60*60; if(i < 0) i += 86400;
1250
1251 if(Parser->rinex3)
1252 ConvLine(file, "R%02d %04d %02d %02d %02d %02d %02d%19.12e%19.12e%19.12e\n",
1253 e->almanac_number, cti.year, cti.month, cti.day, cti.hour, cti.minute,
1254 cti.second, -e->tau, e->gamma, (double) i);
1255 else
1256 ConvLine(file, "%02d %02d %02d %02d %02d %02d%5.1f%19.12e%19.12e%19.12e\n",
1257 e->almanac_number, cti.year%100, cti.month, cti.day, cti.hour, cti.minute,
1258 (double) cti.second, -e->tau, e->gamma, (double) i);
1259 ConvLine(file, " %19.12e%19.12e%19.12e%19.12e\n", e->x_pos,
1260 e->x_velocity, e->x_acceleration, (e->flags & GLOEPHF_UNHEALTHY) ? 1.0 : 0.0);
1261 ConvLine(file, " %19.12e%19.12e%19.12e%19.12e\n", e->y_pos,
1262 e->y_velocity, e->y_acceleration, (double) e->frequency_number);
1263 ConvLine(file, " %19.12e%19.12e%19.12e%19.12e\n", e->z_pos,
1264 e->z_velocity, e->z_acceleration, (double) e->E);
1265 }
1266 else /* if(r == 1019) */
1267 {
1268 struct gpsephemeris *e = &Parser->ephemerisGPS;
1269 double d; /* temporary variable */
1270 unsigned long int i; /* temporary variable */
1271 struct converttimeinfo cti;
1272 converttime(&cti, e->GPSweek, e->TOC);
1273
1274 if(Parser->rinex3)
1275 ConvLine(file, "G%02d %04d %02d %02d %02d %02d %02d%19.12e%19.12e%19.12e\n",
1276 e->satellite, cti.year, cti.month, cti.day, cti.hour,
1277 cti.minute, cti.second, e->clock_bias, e->clock_drift,
1278 e->clock_driftrate);
1279 else
1280 ConvLine(file, "%02d %02d %02d %02d %02d %02d%05.1f%19.12e%19.12e%19.12e\n",
1281 e->satellite, cti.year%100, cti.month, cti.day, cti.hour,
1282 cti.minute, (double) cti.second, e->clock_bias, e->clock_drift,
1283 e->clock_driftrate);
1284 ConvLine(file, " %19.12e%19.12e%19.12e%19.12e\n", (double)e->IODE,
1285 e->Crs, e->Delta_n, e->M0);
1286 ConvLine(file, " %19.12e%19.12e%19.12e%19.12e\n", e->Cuc,
1287 e->e, e->Cus, e->sqrt_A);
1288 ConvLine(file, " %19.12e%19.12e%19.12e%19.12e\n",
1289 (double) e->TOE, e->Cic, e->OMEGA0, e->Cis);
1290 ConvLine(file, " %19.12e%19.12e%19.12e%19.12e\n", e->i0,
1291 e->Crc, e->omega, e->OMEGADOT);
1292 d = 0;
1293 i = e->flags;
1294 if(i & GPSEPHF_L2CACODE)
1295 d += 2.0;
1296 if(i & GPSEPHF_L2PCODE)
1297 d += 1.0;
1298 ConvLine(file, " %19.12e%19.12e%19.12e%19.12e\n", e->IDOT, d,
1299 (double) e->GPSweek, i & GPSEPHF_L2PCODEDATA ? 1.0 : 0.0);
1300 if(e->URAindex <= 6) /* URA index */
1301 d = ceil(10.0*pow(2.0, 1.0+((double)e->URAindex)/2.0))/10.0;
1302 else
1303 d = ceil(10.0*pow(2.0, ((double)e->URAindex)/2.0))/10.0;
1304 /* 15 indicates not to use satellite. We can't handle this special
1305 case, so we create a high "non"-accuracy value. */
1306 ConvLine(file, " %19.12e%19.12e%19.12e%19.12e\n", d,
1307 ((double) e->SVhealth), e->TGD, ((double) e->IODC));
1308
1309 ConvLine(file, " %19.12e%19.12e\n", ((double)e->TOW), 0.0);
1310 /* TOW */
1311 }
1312 }
[27]1313 }
[502]1314 else
[27]1315 {
[502]1316 int i, j, o;
1317 struct converttimeinfo cti;
[27]1318
[502]1319 if(Parser->init < NUMSTARTSKIP) /* skip first epochs to detect correct data types */
[27]1320 {
[502]1321 ++Parser->init;
1322
1323 if(Parser->init == NUMSTARTSKIP)
1324 HandleHeader(Parser);
[27]1325 else
[502]1326 {
1327 for(i = 0; i < Parser->Data.numsats; ++i)
1328 Parser->startflags |= Parser->Data.dataflags[i];
1329 continue;
1330 }
[27]1331 }
[502]1332 if(r == 2 && !Parser->validwarning)
[27]1333 {
[502]1334 RTCM3Text("No valid RINEX! All values are modulo 299792.458!"
1335 " COMMENT\n");
1336 Parser->validwarning = 1;
1337 }
1338
1339 converttime(&cti, Parser->Data.week,
1340 (int)floor(Parser->Data.timeofweek/1000.0));
1341 if(Parser->rinex3)
1342 {
1343 RTCM3Text("> %04d %02d %02d %02d %02d%11.7f 0%3d\n",
1344 cti.year, cti.month, cti.day, cti.hour, cti.minute, cti.second
1345 + fmod(Parser->Data.timeofweek/1000.0,1.0), Parser->Data.numsats);
1346 for(i = 0; i < Parser->Data.numsats; ++i)
[363]1347 {
[502]1348 int glo = 0;
1349 if(Parser->Data.satellites[i] <= PRN_GPS_END)
1350 RTCM3Text("G%02d", Parser->Data.satellites[i]);
1351 else if(Parser->Data.satellites[i] >= PRN_GLONASS_START
1352 && Parser->Data.satellites[i] <= PRN_GLONASS_END)
1353 {
1354 RTCM3Text("R%02d", Parser->Data.satellites[i] - (PRN_GLONASS_START-1));
1355 glo = 1;
1356 }
1357 else if(Parser->Data.satellites[i] >= PRN_WAAS_START
1358 && Parser->Data.satellites[i] <= PRN_WAAS_END)
1359 RTCM3Text("S%02d", Parser->Data.satellites[i] - PRN_WAAS_START+20);
1360 else
1361 RTCM3Text("%3d", Parser->Data.satellites[i]);
[363]1362
[502]1363 if(glo)
[363]1364 {
[502]1365 for(j = 0; j < Parser->numdatatypesGLO; ++j)
1366 {
1367 int df = Parser->dataflagGLO[j];
1368 int pos = Parser->dataposGLO[j];
1369 if((Parser->Data.dataflags[i] & df)
1370 && !isnan(Parser->Data.measdata[i][pos])
1371 && !isinf(Parser->Data.measdata[i][pos]))
1372 {
1373 char lli = ' ';
1374 char snr = ' ';
1375 if(df & (GNSSDF_L1CDATA|GNSSDF_L1PDATA))
1376 {
1377 if(Parser->Data.dataflags[i] & GNSSDF_LOCKLOSSL1)
1378 lli = '1';
1379 snr = '0'+Parser->Data.snrL1[i];
1380 }
1381 if(df & (GNSSDF_L2CDATA|GNSSDF_L2PDATA))
1382 {
1383 if(Parser->Data.dataflags[i] & GNSSDF_LOCKLOSSL2)
1384 lli = '1';
1385 snr = '0'+Parser->Data.snrL2[i];
1386 }
1387 RTCM3Text("%14.3f%c%c",
1388 Parser->Data.measdata[i][pos],lli,snr);
1389 }
1390 else
1391 { /* no or illegal data */
1392 RTCM3Text(" ");
1393 }
1394 }
[363]1395 }
[502]1396 else
1397 {
1398 for(j = 0; j < Parser->numdatatypesGPS; ++j)
1399 {
1400 int df = Parser->dataflagGPS[j];
1401 int pos = Parser->dataposGPS[j];
1402 if((Parser->Data.dataflags[i] & df)
1403 && !isnan(Parser->Data.measdata[i][pos])
1404 && !isinf(Parser->Data.measdata[i][pos]))
1405 {
1406 char lli = ' ';
1407 char snr = ' ';
1408 if(df & (GNSSDF_L1CDATA|GNSSDF_L1PDATA))
1409 {
1410 if(Parser->Data.dataflags[i] & GNSSDF_LOCKLOSSL1)
1411 lli = '1';
1412 snr = '0'+Parser->Data.snrL1[i];
1413 }
1414 if(df & (GNSSDF_L2CDATA|GNSSDF_L2PDATA))
1415 {
1416 if(Parser->Data.dataflags[i] & GNSSDF_LOCKLOSSL2)
1417 lli = '1';
1418 snr = '0'+Parser->Data.snrL2[i];
1419 }
1420 RTCM3Text("%14.3f%c%c",
1421 Parser->Data.measdata[i][pos],lli,snr);
1422 }
1423 else
1424 { /* no or illegal data */
1425 RTCM3Text(" ");
1426 }
1427 }
1428 }
1429 RTCM3Text("\n");
[363]1430 }
[502]1431 }
1432 else
1433 {
1434 RTCM3Text(" %02d %2d %2d %2d %2d %10.7f 0%3d",
1435 cti.year%100, cti.month, cti.day, cti.hour, cti.minute, cti.second
1436 + fmod(Parser->Data.timeofweek/1000.0,1.0), Parser->Data.numsats);
1437 for(i = 0; i < 12 && i < Parser->Data.numsats; ++i)
1438 {
1439 if(Parser->Data.satellites[i] <= PRN_GPS_END)
1440 RTCM3Text("G%02d", Parser->Data.satellites[i]);
1441 else if(Parser->Data.satellites[i] >= PRN_GLONASS_START
1442 && Parser->Data.satellites[i] <= PRN_GLONASS_END)
1443 RTCM3Text("R%02d", Parser->Data.satellites[i] - (PRN_GLONASS_START-1));
1444 else if(Parser->Data.satellites[i] >= PRN_WAAS_START
1445 && Parser->Data.satellites[i] <= PRN_WAAS_END)
1446 RTCM3Text("S%02d", Parser->Data.satellites[i] - PRN_WAAS_START+20);
1447 else
1448 RTCM3Text("%3d", Parser->Data.satellites[i]);
[27]1449 }
[502]1450 RTCM3Text("\n");
1451 o = 12;
1452 j = Parser->Data.numsats - 12;
1453 while(j > 0)
[27]1454 {
[502]1455 RTCM3Text(" ");
1456 for(i = o; i < o+12 && i < Parser->Data.numsats; ++i)
[27]1457 {
[502]1458 if(Parser->Data.satellites[i] <= PRN_GPS_END)
1459 RTCM3Text("G%02d", Parser->Data.satellites[i]);
1460 else if(Parser->Data.satellites[i] >= PRN_GLONASS_START
1461 && Parser->Data.satellites[i] <= PRN_GLONASS_END)
1462 RTCM3Text("R%02d", Parser->Data.satellites[i] - (PRN_GLONASS_START-1));
1463 else if(Parser->Data.satellites[i] >= PRN_WAAS_START
1464 && Parser->Data.satellites[i] <= PRN_WAAS_END)
1465 RTCM3Text("S%02d", Parser->Data.satellites[i] - PRN_WAAS_START+20);
1466 else
1467 RTCM3Text("%3d", Parser->Data.satellites[i]);
[27]1468 }
[502]1469 RTCM3Text("\n");
1470 j -= 12;
1471 o += 12;
1472 }
1473 for(i = 0; i < Parser->Data.numsats; ++i)
1474 {
1475 for(j = 0; j < Parser->numdatatypesGPS; ++j)
[27]1476 {
[502]1477 int v = 0;
1478 int df = Parser->dataflag[j];
1479 int pos = Parser->datapos[j];
1480 if((Parser->Data.dataflags[i] & df)
1481 && !isnan(Parser->Data.measdata[i][pos])
1482 && !isinf(Parser->Data.measdata[i][pos]))
1483 {
1484 v = 1;
1485 }
1486 else
1487 {
1488 df = Parser->dataflagGPS[j];
1489 pos = Parser->dataposGPS[j];
1490
1491 if((Parser->Data.dataflags[i] & df)
1492 && !isnan(Parser->Data.measdata[i][pos])
1493 && !isinf(Parser->Data.measdata[i][pos]))
1494 {
1495 v = 1;
1496 }
1497 }
1498
1499 if(!v)
1500 { /* no or illegal data */
1501 RTCM3Text(" ");
1502 }
1503 else
1504 {
1505 char lli = ' ';
1506 char snr = ' ';
1507 if(df & (GNSSDF_L1CDATA|GNSSDF_L1PDATA))
1508 {
1509 if(Parser->Data.dataflags[i] & GNSSDF_LOCKLOSSL1)
1510 lli = '1';
1511 snr = '0'+Parser->Data.snrL1[i];
1512 }
1513 if(df & (GNSSDF_L2CDATA|GNSSDF_L2PDATA))
1514 {
1515 if(Parser->Data.dataflags[i] & GNSSDF_LOCKLOSSL2)
1516 lli = '1';
1517 snr = '0'+Parser->Data.snrL2[i];
1518 }
1519 RTCM3Text("%14.3f%c%c",
1520 Parser->Data.measdata[i][pos],lli,snr);
1521 }
1522 if(j%5 == 4 || j == Parser->numdatatypesGPS-1)
1523 RTCM3Text("\n");
[27]1524 }
1525 }
1526 }
1527 }
1528 }
1529 }
1530}
1531
[268]1532#ifndef NO_RTCM3_MAIN
[744]1533static char datestr[] = "$Date: 2007/12/20 10:42:10 $";
[269]1534
[268]1535/* The string, which is send as agent in HTTP request */
1536#define AGENTSTRING "NTRIP NtripRTCM3ToRINEX"
1537
1538#define MAXDATASIZE 1000 /* max number of bytes we can get at once */
1539
[269]1540static const char encodingTable [64] = {
1541 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
1542 'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
1543 'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
1544 'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/'
1545};
1546
1547/* does not buffer overrun, but breaks directly after an error */
1548/* returns the number of required bytes */
1549static int encode(char *buf, int size, const char *user, const char *pwd)
1550{
1551 unsigned char inbuf[3];
1552 char *out = buf;
1553 int i, sep = 0, fill = 0, bytes = 0;
1554
1555 while(*user || *pwd)
1556 {
1557 i = 0;
1558 while(i < 3 && *user) inbuf[i++] = *(user++);
1559 if(i < 3 && !sep) {inbuf[i++] = ':'; ++sep; }
1560 while(i < 3 && *pwd) inbuf[i++] = *(pwd++);
1561 while(i < 3) {inbuf[i++] = 0; ++fill; }
1562 if(out-buf < size-1)
1563 *(out++) = encodingTable[(inbuf [0] & 0xFC) >> 2];
1564 if(out-buf < size-1)
1565 *(out++) = encodingTable[((inbuf [0] & 0x03) << 4)
1566 | ((inbuf [1] & 0xF0) >> 4)];
1567 if(out-buf < size-1)
1568 {
1569 if(fill == 2)
1570 *(out++) = '=';
1571 else
1572 *(out++) = encodingTable[((inbuf [1] & 0x0F) << 2)
1573 | ((inbuf [2] & 0xC0) >> 6)];
1574 }
1575 if(out-buf < size-1)
1576 {
1577 if(fill >= 1)
1578 *(out++) = '=';
1579 else
1580 *(out++) = encodingTable[inbuf [2] & 0x3F];
1581 }
1582 bytes += 4;
1583 }
1584 if(out-buf < size)
1585 *out = 0;
1586 return bytes;
1587}
1588
[268]1589static int stop = 0;
1590
1591struct Args
1592{
1593 const char *server;
[502]1594 const char *port;
1595 int mode;
[477]1596 int timeout;
[502]1597 int rinex3;
[268]1598 const char *user;
1599 const char *password;
[502]1600 const char *proxyhost;
1601 const char *proxyport;
1602 const char *nmea;
[268]1603 const char *data;
1604 const char *headerfile;
[502]1605 const char *gpsephemeris;
1606 const char *glonassephemeris;
[268]1607};
1608
1609/* option parsing */
1610#ifdef NO_LONG_OPTS
1611#define LONG_OPT(a)
1612#else
1613#define LONG_OPT(a) a
1614static struct option opts[] = {
[502]1615{ "data", required_argument, 0, 'd'},
1616{ "server", required_argument, 0, 's'},
1617{ "password", required_argument, 0, 'p'},
1618{ "port", required_argument, 0, 'r'},
1619{ "timeout", required_argument, 0, 't'},
1620{ "header", required_argument, 0, 'f'},
1621{ "user", required_argument, 0, 'u'},
1622{ "gpsephemeris", required_argument, 0, 'E'},
1623{ "glonassephemeris", required_argument, 0, 'G'},
1624{ "rinex3", no_argument, 0, '3'},
1625{ "proxyport", required_argument, 0, 'R'},
1626{ "proxyhost", required_argument, 0, 'S'},
1627{ "nmea", required_argument, 0, 'n'},
1628{ "mode", required_argument, 0, 'M'},
1629{ "help", no_argument, 0, 'h'},
[268]1630{0,0,0,0}};
1631#endif
[502]1632#define ARGOPT "-d:s:p:r:t:f:u:E:G:M:S:R:n:h3"
[268]1633
[502]1634enum MODE { HTTP = 1, RTSP = 2, NTRIP1 = 3, AUTO = 4, END };
1635
[270]1636static const char *geturl(const char *url, struct Args *args)
1637{
1638 static char buf[1000];
1639 static char *Buffer = buf;
1640 static char *Bufend = buf+sizeof(buf);
1641
1642 if(strncmp("ntrip:", url, 6))
1643 return "URL must start with 'ntrip:'.";
1644 url += 6; /* skip ntrip: */
1645
1646 if(*url != '@' && *url != '/')
1647 {
1648 /* scan for mountpoint */
1649 args->data = Buffer;
[502]1650 while(*url && *url != '@' && *url != ';' &&*url != '/' && Buffer != Bufend)
[270]1651 *(Buffer++) = *(url++);
1652 if(Buffer == args->data)
1653 return "Mountpoint required.";
1654 else if(Buffer >= Bufend-1)
1655 return "Parsing buffer too short.";
1656 *(Buffer++) = 0;
1657 }
1658
1659 if(*url == '/') /* username and password */
1660 {
1661 ++url;
1662 args->user = Buffer;
[502]1663 while(*url && *url != '@' && *url != ';' && *url != ':' && Buffer != Bufend)
[270]1664 *(Buffer++) = *(url++);
1665 if(Buffer == args->user)
1666 return "Username cannot be empty.";
1667 else if(Buffer >= Bufend-1)
1668 return "Parsing buffer too short.";
1669 *(Buffer++) = 0;
1670
1671 if(*url == ':') ++url;
1672
1673 args->password = Buffer;
[502]1674 while(*url && *url != '@' && *url != ';' && Buffer != Bufend)
[270]1675 *(Buffer++) = *(url++);
1676 if(Buffer == args->password)
1677 return "Password cannot be empty.";
1678 else if(Buffer >= Bufend-1)
1679 return "Parsing buffer too short.";
1680 *(Buffer++) = 0;
1681 }
1682
1683 if(*url == '@') /* server */
1684 {
1685 ++url;
[502]1686 if(*url != '@' && *url != ':')
1687 {
1688 args->server = Buffer;
1689 while(*url && *url != '@' && *url != ':' && *url != ';' && Buffer != Bufend)
1690 *(Buffer++) = *(url++);
1691 if(Buffer == args->server)
1692 return "Servername cannot be empty.";
1693 else if(Buffer >= Bufend-1)
1694 return "Parsing buffer too short.";
1695 *(Buffer++) = 0;
1696 }
[270]1697
1698 if(*url == ':')
1699 {
[502]1700 ++url;
1701 args->port = Buffer;
1702 while(*url && *url != '@' && *url != ';' && Buffer != Bufend)
1703 *(Buffer++) = *(url++);
1704 if(Buffer == args->port)
1705 return "Port cannot be empty.";
1706 else if(Buffer >= Bufend-1)
1707 return "Parsing buffer too short.";
1708 *(Buffer++) = 0;
[270]1709 }
[502]1710
1711 if(*url == '@') /* proxy */
1712 {
1713 ++url;
1714 args->proxyhost = Buffer;
1715 while(*url && *url != ':' && *url != ';' && Buffer != Bufend)
1716 *(Buffer++) = *(url++);
1717 if(Buffer == args->proxyhost)
1718 return "Proxy servername cannot be empty.";
1719 else if(Buffer >= Bufend-1)
1720 return "Parsing buffer too short.";
1721 *(Buffer++) = 0;
1722
1723 if(*url == ':')
1724 {
1725 ++url;
1726 args->proxyport = Buffer;
1727 while(*url && *url != ';' && Buffer != Bufend)
1728 *(Buffer++) = *(url++);
1729 if(Buffer == args->proxyport)
1730 return "Proxy port cannot be empty.";
1731 else if(Buffer >= Bufend-1)
1732 return "Parsing buffer too short.";
1733 *(Buffer++) = 0;
1734 }
1735 }
[270]1736 }
[502]1737 if(*url == ';') /* NMEA */
1738 {
1739 args->nmea = ++url;
1740 while(*url)
1741 ++url;
1742 }
[270]1743
1744 return *url ? "Garbage at end of server string." : 0;
1745}
1746
[268]1747static int getargs(int argc, char **argv, struct Args *args)
1748{
1749 int res = 1;
1750 int getoptr;
1751 int help = 0;
1752 char *t;
1753
1754 args->server = "www.euref-ip.net";
[502]1755 args->port = "2101";
[477]1756 args->timeout = 60;
[268]1757 args->user = "";
1758 args->password = "";
1759 args->data = 0;
1760 args->headerfile = 0;
[502]1761 args->gpsephemeris = 0;
1762 args->glonassephemeris = 0;
1763 args->rinex3 = 0;
1764 args->nmea = 0;
1765 args->proxyhost = 0;
1766 args->proxyport = "2101";
1767 args->mode = AUTO;
[268]1768 help = 0;
1769
1770 do
1771 {
[477]1772
[268]1773#ifdef NO_LONG_OPTS
1774 switch((getoptr = getopt(argc, argv, ARGOPT)))
1775#else
1776 switch((getoptr = getopt_long(argc, argv, ARGOPT, opts, 0)))
1777#endif
1778 {
1779 case 's': args->server = optarg; break;
1780 case 'u': args->user = optarg; break;
1781 case 'p': args->password = optarg; break;
1782 case 'd': args->data = optarg; break;
1783 case 'f': args->headerfile = optarg; break;
[502]1784 case 'E': args->gpsephemeris = optarg; break;
1785 case 'G': args->glonassephemeris = optarg; break;
1786 case 'r': args->port = optarg; break;
1787 case '3': args->rinex3 = 1; break;
1788 case 'S': args->proxyhost = optarg; break;
1789 case 'n': args->nmea = optarg; break;
1790 case 'R': args->proxyport = optarg; break;
[268]1791 case 'h': help=1; break;
[502]1792 case 'M':
1793 args->mode = 0;
1794 if (!strcmp(optarg,"n") || !strcmp(optarg,"ntrip1"))
1795 args->mode = NTRIP1;
1796 else if(!strcmp(optarg,"h") || !strcmp(optarg,"http"))
1797 args->mode = HTTP;
1798 else if(!strcmp(optarg,"r") || !strcmp(optarg,"rtsp"))
1799 args->mode = RTSP;
1800 else if(!strcmp(optarg,"a") || !strcmp(optarg,"auto"))
1801 args->mode = AUTO;
1802 else args->mode = atoi(optarg);
1803 if((args->mode == 0) || (args->mode >= END))
1804 {
1805 fprintf(stderr, "Mode %s unknown\n", optarg);
[268]1806 res = 0;
[502]1807 }
[268]1808 break;
[502]1809 case 't':
[477]1810 args->timeout = strtoul(optarg, &t, 10);
1811 if((t && *t) || args->timeout < 0)
1812 res = 0;
1813 break;
1814
[270]1815 case 1:
1816 {
1817 const char *err;
1818 if((err = geturl(optarg, args)))
1819 {
[318]1820 RTCM3Error("%s\n\n", err);
[270]1821 res = 0;
1822 }
1823 }
1824 break;
[268]1825 case -1: break;
1826 }
1827 } while(getoptr != -1 || !res);
1828
1829 datestr[0] = datestr[7];
1830 datestr[1] = datestr[8];
1831 datestr[2] = datestr[9];
1832 datestr[3] = datestr[10];
1833 datestr[5] = datestr[12];
1834 datestr[6] = datestr[13];
1835 datestr[8] = datestr[15];
1836 datestr[9] = datestr[16];
1837 datestr[4] = datestr[7] = '-';
1838 datestr[10] = 0;
1839
[502]1840 if(args->gpsephemeris && args->glonassephemeris && args->rinex3)
[268]1841 {
[503]1842 RTCM3Error("RINEX3 produces a combined ephemeris file, but 2 files were specified.\n"
1843 "Please specify only one navigation file.\n");
[502]1844 res = 0;
1845 }
1846 else if(!res || help)
1847 {
[439]1848 RTCM3Error("Version %s (%s) GPL" COMPILEDATE
1849 "\nUsage: %s -s server -u user ...\n"
[502]1850 " -d " LONG_OPT("--data ") "the requested data set\n"
1851 " -f " LONG_OPT("--headerfile ") "file for RINEX header information\n"
1852 " -s " LONG_OPT("--server ") "the server name or address\n"
1853 " -p " LONG_OPT("--password ") "the login password\n"
1854 " -r " LONG_OPT("--port ") "the server port number (default 2101)\n"
1855 " -t " LONG_OPT("--timeout ") "timeout in seconds (default 60)\n"
1856 " -u " LONG_OPT("--user ") "the user name\n"
1857 " -E " LONG_OPT("--gpsephemeris ") "output file for GPS ephemeris data\n"
1858 " -G " LONG_OPT("--glonassephemeris ") "output file for GLONASS ephemeris data\n"
1859 " -3 " LONG_OPT("--rinex3 ") "output RINEX type 3 data\n"
1860 " -S " LONG_OPT("--proxyhost ") "proxy name or address\n"
1861 " -R " LONG_OPT("--proxyport ") "proxy port, optional (default 2101)\n"
1862 " -n " LONG_OPT("--nmea ") "NMEA string for sending to server\n"
1863 " -M " LONG_OPT("--mode ") "mode for data request\n"
1864 " Valid modes are:\n"
1865 " 1, h, http NTRIP Version 2.0 Caster in TCP/IP mode\n"
1866 " 2, r, rtsp NTRIP Version 2.0 Caster in RTSP/RTP mode\n"
1867 " 3, n, ntrip1 NTRIP Version 1.0 Caster\n"
1868 " 4, a, auto automatic detection (default)\n"
1869 "or using an URL:\n%s ntrip:data[/user[:password]][@[server][:port][@proxyhost[:proxyport]]][;nmea]\n"
[270]1870 , revisionstr, datestr, argv[0], argv[0]);
[268]1871 exit(1);
1872 }
1873 return res;
1874}
1875
1876/* let the output complete a block if necessary */
1877static void signalhandler(int sig)
1878{
1879 if(!stop)
1880 {
[318]1881 RTCM3Error("Stop signal number %d received. "
[268]1882 "Trying to terminate gentle.\n", sig);
1883 stop = 1;
1884 alarm(1);
1885 }
1886}
1887
[502]1888#define ALARMTIME (2*60)
1889
[268]1890/* for some reason we had to abort hard (maybe waiting for data */
1891#ifdef __GNUC__
1892static __attribute__ ((noreturn)) void signalhandler_alarm(
1893int sig __attribute__((__unused__)))
1894#else /* __GNUC__ */
1895static void signalhandler_alarm(int sig)
1896#endif /* __GNUC__ */
1897{
[318]1898 RTCM3Error("Programm forcefully terminated.\n");
[268]1899 exit(1);
1900}
1901
[27]1902int main(int argc, char **argv)
1903{
1904 struct Args args;
1905 struct RTCM3ParserData Parser;
1906
1907 setbuf(stdout, 0);
1908 setbuf(stdin, 0);
1909 setbuf(stderr, 0);
1910
1911 {
1912 char *a;
1913 int i=0;
1914 for(a = revisionstr+11; *a && *a != ' '; ++a)
1915 revisionstr[i++] = *a;
1916 revisionstr[i] = 0;
1917 }
1918
1919 signal(SIGINT, signalhandler);
1920 signal(SIGALRM,signalhandler_alarm);
1921 signal(SIGQUIT,signalhandler);
1922 signal(SIGTERM,signalhandler);
1923 signal(SIGPIPE,signalhandler);
1924 memset(&Parser, 0, sizeof(Parser));
1925 {
1926 time_t tim;
1927 tim = time(0) - ((10*365+2+5)*24*60*60+LEAPSECONDS);
1928 Parser.GPSWeek = tim/(7*24*60*60);
1929 Parser.GPSTOW = tim%(7*24*60*60);
1930 }
1931
1932 if(getargs(argc, argv, &args))
1933 {
[502]1934 int sockfd, numbytes;
[27]1935 char buf[MAXDATASIZE];
[502]1936 struct sockaddr_in their_addr; /* connector's address information */
[27]1937 struct hostent *he;
[502]1938 struct servent *se;
1939 const char *server, *port, *proxyserver = 0;
1940 char proxyport[6];
1941 char *b;
1942 long i;
1943 struct timeval tv;
[27]1944
[502]1945 alarm(ALARMTIME);
1946
[27]1947 Parser.headerfile = args.headerfile;
[502]1948 Parser.glonassephemeris = args.glonassephemeris;
1949 Parser.gpsephemeris = args.gpsephemeris;
1950 Parser.rinex3 = args.rinex3;
[27]1951
[502]1952 if(args.proxyhost)
[27]1953 {
[502]1954 int p;
1955 if((i = strtol(args.port, &b, 10)) && (!b || !*b))
1956 p = i;
1957 else if(!(se = getservbyname(args.port, 0)))
1958 {
1959 RTCM3Error("Can't resolve port %s.", args.port);
1960 exit(1);
1961 }
1962 else
1963 {
1964 p = ntohs(se->s_port);
1965 }
1966 snprintf(proxyport, sizeof(proxyport), "%d", p);
1967 port = args.proxyport;
1968 proxyserver = args.server;
1969 server = args.proxyhost;
1970 }
1971 else
1972 {
1973 server = args.server;
1974 port = args.port;
1975 }
1976
1977 memset(&their_addr, 0, sizeof(struct sockaddr_in));
1978 if((i = strtol(port, &b, 10)) && (!b || !*b))
1979 their_addr.sin_port = htons(i);
1980 else if(!(se = getservbyname(port, 0)))
1981 {
1982 RTCM3Error("Can't resolve port %s.", port);
[27]1983 exit(1);
1984 }
[502]1985 else
1986 {
1987 their_addr.sin_port = se->s_port;
1988 }
1989 if(!(he=gethostbyname(server)))
1990 {
1991 RTCM3Error("Server name lookup failed for '%s'.\n", server);
1992 exit(1);
1993 }
[27]1994 if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
1995 {
[502]1996 perror("socket");
[27]1997 exit(1);
1998 }
[477]1999
2000 tv.tv_sec = args.timeout;
2001 tv.tv_usec = 0;
2002 if(setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (struct timeval *)&tv, sizeof(struct timeval) ) == -1)
2003 {
2004 RTCM3Error("Function setsockopt: %s\n", strerror(errno));
2005 exit(1);
2006 }
2007
[502]2008 their_addr.sin_family = AF_INET;
[27]2009 their_addr.sin_addr = *((struct in_addr *)he->h_addr);
[502]2010
2011 if(args.data && args.mode == RTSP)
[27]2012 {
[502]2013 struct sockaddr_in local;
2014 int sockudp, localport;
2015 int cseq = 1;
2016 socklen_t len;
[27]2017
[502]2018 if((sockudp = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
2019 {
2020 perror("socket");
2021 exit(1);
2022 }
2023 /* fill structure with local address information for UDP */
2024 memset(&local, 0, sizeof(local));
2025 local.sin_family = AF_INET;
2026 local.sin_port = htons(0);
2027 local.sin_addr.s_addr = htonl(INADDR_ANY);
2028 len = sizeof(local);
2029 /* bind() in order to get a random RTP client_port */
2030 if((bind(sockudp, (struct sockaddr *)&local, len)) < 0)
2031 {
2032 perror("bind");
2033 exit(1);
2034 }
2035 if((getsockname(sockudp, (struct sockaddr*)&local, &len)) != -1)
2036 {
2037 localport = ntohs(local.sin_port);
2038 }
2039 else
2040 {
2041 perror("local access failed");
2042 exit(1);
2043 }
2044 if(connect(sockfd, (struct sockaddr *)&their_addr,
2045 sizeof(struct sockaddr)) == -1)
2046 {
2047 perror("connect");
2048 exit(1);
2049 }
[27]2050 i=snprintf(buf, MAXDATASIZE-40, /* leave some space for login */
[502]2051 "SETUP rtsp://%s%s%s/%s RTSP/1.0\r\n"
2052 "CSeq: %d\r\n"
2053 "Ntrip-Version: Ntrip/2.0\r\n"
2054 "Ntrip-Component: Ntripclient\r\n"
[27]2055 "User-Agent: %s/%s\r\n"
[502]2056 "Transport: RTP/GNSS;unicast;client_port=%u\r\n"
2057 "Authorization: Basic ",
2058 args.server, proxyserver ? ":" : "", proxyserver ? args.port : "",
2059 args.data, cseq++, AGENTSTRING, revisionstr, localport);
[479]2060 if(i > MAXDATASIZE-40 || i < 0) /* second check for old glibc */
[27]2061 {
[318]2062 RTCM3Error("Requested data too long\n");
[27]2063 exit(1);
2064 }
[339]2065 i += encode(buf+i, MAXDATASIZE-i-4, args.user, args.password);
2066 if(i > MAXDATASIZE-4)
[27]2067 {
[318]2068 RTCM3Error("Username and/or password too long\n");
[27]2069 exit(1);
2070 }
[339]2071 buf[i++] = '\r';
2072 buf[i++] = '\n';
2073 buf[i++] = '\r';
2074 buf[i++] = '\n';
[502]2075 if(args.nmea)
[27]2076 {
[502]2077 int j = snprintf(buf+i, MAXDATASIZE-i, "%s\r\n", args.nmea);
2078 if(j >= 0 && j < MAXDATASIZE-i)
2079 i += j;
2080 else
[27]2081 {
[502]2082 RTCM3Error("NMEA string too long\n");
2083 exit(1);
2084 }
2085 }
2086 if(send(sockfd, buf, (size_t)i, 0) != i)
2087 {
2088 perror("send");
2089 exit(1);
2090 }
2091 if((numbytes=recv(sockfd, buf, MAXDATASIZE-1, 0)) != -1)
2092 {
2093 if(numbytes >= 17 && !strncmp(buf, "RTSP/1.0 200 OK\r\n", 17))
[744]2094 {
[502]2095 int serverport = 0, session = 0;
2096 const char *portcheck = "server_port=";
2097 const char *sessioncheck = "session: ";
2098 int l = strlen(portcheck)-1;
2099 int j=0;
2100 for(i = 0; j != l && i < numbytes-l; ++i)
[27]2101 {
[502]2102 for(j = 0; j < l && tolower(buf[i+j]) == portcheck[j]; ++j)
2103 ;
2104 }
2105 if(i == numbytes-l)
2106 {
2107 RTCM3Error("No server port number found\n");
2108 exit(1);
2109 }
2110 else
2111 {
2112 i+=l;
2113 while(i < numbytes && buf[i] >= '0' && buf[i] <= '9')
2114 serverport = serverport * 10 + buf[i++]-'0';
2115 if(buf[i] != '\r' && buf[i] != ';')
[27]2116 {
[502]2117 RTCM3Error("Could not extract server port\n");
2118 exit(1);
[27]2119 }
[502]2120 }
2121 l = strlen(sessioncheck)-1;
2122 j=0;
2123 for(i = 0; j != l && i < numbytes-l; ++i)
2124 {
2125 for(j = 0; j < l && tolower(buf[i+j]) == sessioncheck[j]; ++j)
2126 ;
2127 }
2128 if(i == numbytes-l)
2129 {
2130 RTCM3Error("No session number found\n");
[27]2131 exit(1);
2132 }
[502]2133 else
2134 {
2135 i+=l;
2136 while(i < numbytes && buf[i] >= '0' && buf[i] <= '9')
2137 session = session * 10 + buf[i++]-'0';
2138 if(buf[i] != '\r')
2139 {
2140 RTCM3Error("Could not extract session number\n");
2141 exit(1);
2142 }
2143 }
2144
2145 i = snprintf(buf, MAXDATASIZE,
2146 "PLAY rtsp://%s%s%s/%s RTSP/1.0\r\n"
2147 "CSeq: %d\r\n"
2148 "Session: %d\r\n"
2149 "\r\n",
2150 args.server, proxyserver ? ":" : "", proxyserver ? args.port : "",
2151 args.data, cseq++, session);
2152
2153 if(i > MAXDATASIZE || i < 0) /* second check for old glibc */
2154 {
2155 RTCM3Error("Requested data too long\n");
2156 exit(1);
2157 }
2158 if(send(sockfd, buf, (size_t)i, 0) != i)
2159 {
2160 perror("send");
2161 exit(1);
2162 }
2163 if((numbytes=recv(sockfd, buf, MAXDATASIZE-1, 0)) != -1)
2164 {
2165 if(numbytes >= 17 && !strncmp(buf, "RTSP/1.0 200 OK\r\n", 17))
2166 {
2167 struct sockaddr_in addrRTP;
2168 /* fill structure with caster address information for UDP */
2169 memset(&addrRTP, 0, sizeof(addrRTP));
2170 addrRTP.sin_family = AF_INET;
2171 addrRTP.sin_port = htons(serverport);
2172 their_addr.sin_addr = *((struct in_addr *)he->h_addr);
2173 len = sizeof(addrRTP);
2174 int ts = 0;
2175 int sn = 0;
2176 int ssrc = 0;
2177 int init = 0;
2178 int u, v, w;
2179 while(!stop && (i = recvfrom(sockudp, buf, 1526, 0,
2180 (struct sockaddr*) &addrRTP, &len)) > 0)
2181 {
2182 alarm(ALARMTIME);
2183 if(i >= 12+1 && (unsigned char)buf[0] == (2 << 6) && buf[1] == 0x60)
2184 {
2185 u= ((unsigned char)buf[2]<<8)+(unsigned char)buf[3];
2186 v = ((unsigned char)buf[4]<<24)+((unsigned char)buf[5]<<16)
2187 +((unsigned char)buf[6]<<8)+(unsigned char)buf[7];
2188 w = ((unsigned char)buf[8]<<24)+((unsigned char)buf[9]<<16)
2189 +((unsigned char)buf[10]<<8)+(unsigned char)buf[11];
2190
2191 if(init)
2192 {
2193 if(u < -30000 && sn > 30000) sn -= 0xFFFF;
2194 if(ssrc != w || ts > v)
2195 {
2196 RTCM3Error("Illegal UDP data received.\n");
2197 exit(1);
2198 }
2199 if(u > sn) /* don't show out-of-order packets */
2200 fwrite(buf+12, (size_t)i-12, 1, stdout);
2201 }
2202 sn = u; ts = v; ssrc = w; init = 1;
2203 }
2204 else
2205 {
2206 RTCM3Error("Illegal UDP header.\n");
2207 exit(1);
2208 }
2209 }
2210 }
2211 i = snprintf(buf, MAXDATASIZE,
2212 "TEARDOWN rtsp://%s%s%s/%s RTSP/1.0\r\n"
2213 "CSeq: %d\r\n"
2214 "Session: %d\r\n"
2215 "\r\n",
2216 args.server, proxyserver ? ":" : "", proxyserver ? args.port : "",
2217 args.data, cseq++, session);
2218
2219 if(i > MAXDATASIZE || i < 0) /* second check for old glibc */
2220 {
2221 RTCM3Error("Requested data too long\n");
2222 exit(1);
2223 }
2224 if(send(sockfd, buf, (size_t)i, 0) != i)
2225 {
2226 perror("send");
2227 exit(1);
2228 }
2229 }
2230 else
2231 {
2232 RTCM3Error("Could not start data stream.\n");
2233 exit(1);
2234 }
[27]2235 }
2236 else
2237 {
[502]2238 RTCM3Error("Could not setup initial control connection.\n");
2239 exit(1);
[27]2240 }
2241 }
[502]2242 else
2243 {
2244 perror("recv");
2245 exit(1);
2246 }
[27]2247 }
2248 else
2249 {
[502]2250 if(connect(sockfd, (struct sockaddr *)&their_addr,
2251 sizeof(struct sockaddr)) == -1)
[27]2252 {
[502]2253 perror("connect");
2254 exit(1);
[27]2255 }
[502]2256 if(!args.data)
2257 {
2258 i = snprintf(buf, MAXDATASIZE,
2259 "GET %s%s%s%s/ HTTP/1.0\r\n"
2260 "Host: %s\r\n%s"
2261 "User-Agent: %s/%s\r\n"
[646]2262 "Connection: close\r\n"
[502]2263 "\r\n"
2264 , proxyserver ? "http://" : "", proxyserver ? proxyserver : "",
2265 proxyserver ? ":" : "", proxyserver ? proxyport : "",
2266 args.server, args.mode == NTRIP1 ? "" : "Ntrip-Version: Ntrip/2.0\r\n",
2267 AGENTSTRING, revisionstr);
2268 }
2269 else
2270 {
2271 i=snprintf(buf, MAXDATASIZE-40, /* leave some space for login */
2272 "GET %s%s%s%s/%s HTTP/1.0\r\n"
2273 "Host: %s\r\n%s"
2274 "User-Agent: %s/%s\r\n"
[646]2275 "Connection: close\r\n"
[502]2276 "Authorization: Basic "
2277 , proxyserver ? "http://" : "", proxyserver ? proxyserver : "",
2278 proxyserver ? ":" : "", proxyserver ? proxyport : "",
2279 args.data, args.server,
2280 args.mode == NTRIP1 ? "" : "Ntrip-Version: Ntrip/2.0\r\n",
2281 AGENTSTRING, revisionstr);
2282 if(i > MAXDATASIZE-40 || i < 0) /* second check for old glibc */
2283 {
2284 RTCM3Error("Requested data too long\n");
2285 exit(1);
2286 }
2287 i += encode(buf+i, MAXDATASIZE-i-4, args.user, args.password);
2288 if(i > MAXDATASIZE-4)
2289 {
2290 RTCM3Error("Username and/or password too long\n");
2291 exit(1);
2292 }
2293 buf[i++] = '\r';
2294 buf[i++] = '\n';
2295 buf[i++] = '\r';
2296 buf[i++] = '\n';
2297 if(args.nmea)
2298 {
2299 int j = snprintf(buf+i, MAXDATASIZE-i, "%s\r\n", args.nmea);
2300 if(j >= 0 && j < MAXDATASIZE-i)
2301 i += j;
2302 else
2303 {
2304 RTCM3Error("NMEA string too long\n");
2305 exit(1);
2306 }
2307 }
2308 }
2309 if(send(sockfd, buf, (size_t)i, 0) != i)
2310 {
2311 perror("send");
2312 exit(1);
2313 }
2314 if(args.data)
2315 {
2316 int k = 0;
2317 int chunkymode = 0;
2318 int starttime = time(0);
2319 int lastout = starttime;
2320 int totalbytes = 0;
2321 int chunksize = 0;
2322
2323 while(!stop && (numbytes=recv(sockfd, buf, MAXDATASIZE-1, 0)) != -1)
2324 {
2325 alarm(ALARMTIME);
2326 if(!k)
2327 {
2328 if(numbytes > 17 && (!strncmp(buf, "HTTP/1.1 200 OK\r\n", 17)
2329 || !strncmp(buf, "HTTP/1.0 200 OK\r\n", 17)))
[744]2330 {
[502]2331 const char *datacheck = "Content-Type: gnss/data\r\n";
2332 const char *chunkycheck = "Transfer-Encoding: chunked\r\n";
2333 int l = strlen(datacheck)-1;
2334 int j=0;
2335 for(i = 0; j != l && i < numbytes-l; ++i)
2336 {
2337 for(j = 0; j < l && buf[i+j] == datacheck[j]; ++j)
2338 ;
2339 }
2340 if(i == numbytes-l)
2341 {
2342 RTCM3Error("No 'Content-Type: gnss/data' found\n");
2343 exit(1);
2344 }
2345 l = strlen(chunkycheck)-1;
2346 j=0;
2347 for(i = 0; j != l && i < numbytes-l; ++i)
2348 {
2349 for(j = 0; j < l && buf[i+j] == chunkycheck[j]; ++j)
2350 ;
2351 }
2352 if(i < numbytes-l)
2353 chunkymode = 1;
2354 }
2355 else if(numbytes < 12 || strncmp("ICY 200 OK\r\n", buf, 12))
2356 {
2357 RTCM3Error("Could not get the requested data: ");
2358 for(k = 0; k < numbytes && buf[k] != '\n' && buf[k] != '\r'; ++k)
2359 {
2360 RTCM3Error("%c", isprint(buf[k]) ? buf[k] : '.');
2361 }
2362 RTCM3Error("\n");
2363 exit(1);
2364 }
2365 else if(args.mode != NTRIP1)
2366 {
2367 if(args.mode != AUTO)
2368 {
2369 RTCM3Error("NTRIP version 2 HTTP connection failed%s.\n",
2370 args.mode == AUTO ? ", falling back to NTRIP1" : "");
2371 }
2372 if(args.mode == HTTP)
2373 exit(1);
2374 }
2375 ++k;
2376 }
2377 else
2378 {
2379 if(chunkymode)
2380 {
2381 int stop = 0;
2382 int pos = 0;
2383 while(!stop && pos < numbytes)
2384 {
2385 switch(chunkymode)
2386 {
2387 case 1: /* reading number starts */
2388 chunksize = 0;
2389 ++chunkymode; /* no break */
2390 case 2: /* during reading number */
2391 i = buf[pos++];
2392 if(i >= '0' && i <= '9') chunksize = chunksize*16+i-'0';
2393 else if(i >= 'a' && i <= 'f') chunksize = chunksize*16+i-'a'+10;
2394 else if(i >= 'A' && i <= 'F') chunksize = chunksize*16+i-'A'+10;
2395 else if(i == '\r') ++chunkymode;
[646]2396 else if(i == ';') chunkymode = 5;
[502]2397 else stop = 1;
2398 break;
2399 case 3: /* scanning for return */
2400 if(buf[pos++] == '\n') chunkymode = chunksize ? 4 : 1;
2401 else stop = 1;
2402 break;
2403 case 4: /* output data */
2404 i = numbytes-pos;
2405 if(i > chunksize) i = chunksize;
2406 {
2407 int z;
2408 for(z = 0; z < i && !stop; ++z)
2409 HandleByte(&Parser, (unsigned int) buf[pos+z]);
2410 }
2411 totalbytes += i;
2412 chunksize -= i;
2413 pos += i;
2414 if(!chunksize)
2415 chunkymode = 1;
2416 break;
[646]2417 case 5:
2418 if(i == '\r') chunkymode = 3;
2419 break;
[502]2420 }
2421 }
2422 if(stop)
2423 {
2424 RTCM3Error("Error in chunky transfer encoding\n");
2425 break;
2426 }
2427 }
2428 else
2429 {
2430 totalbytes += numbytes;
2431 {
2432 int z;
2433 for(z = 0; z < numbytes && !stop; ++z)
2434 HandleByte(&Parser, (unsigned int) buf[z]);
2435 }
2436 }
2437 if(totalbytes < 0) /* overflow */
2438 {
2439 totalbytes = 0;
2440 starttime = time(0);
2441 lastout = starttime;
2442 }
2443 }
2444 }
2445 }
2446 else
2447 {
2448 while(!stop && (numbytes=recv(sockfd, buf, MAXDATASIZE-1, 0)) > 0)
2449 {
2450 alarm(ALARMTIME);
2451 fwrite(buf, (size_t)numbytes, 1, stdout);
2452 }
2453 }
2454 close(sockfd);
[27]2455 }
2456 }
2457 return 0;
2458}
[268]2459#endif /* NO_RTCM3_MAIN */
Note: See TracBrowser for help on using the repository browser.