xref: /freebsd/contrib/ntp/ntpd/refclock_acts.c (revision f078c492a9b57877c723586db26d789cda1b98ea)
1 /*
2  * refclock_acts - clock driver for the NIST/USNO/PTB/NPL Computer Time
3  *	Services
4  */
5 #ifdef HAVE_CONFIG_H
6 #include <config.h>
7 #endif
8 
9 #if defined(REFCLOCK) && defined(CLOCK_ACTS)
10 
11 #include "ntpd.h"
12 #include "ntp_io.h"
13 #include "ntp_unixtime.h"
14 #include "ntp_refclock.h"
15 #include "ntp_stdlib.h"
16 #include "ntp_control.h"
17 
18 #include <stdio.h>
19 #include <ctype.h>
20 #ifdef HAVE_SYS_IOCTL_H
21 # include <sys/ioctl.h>
22 #endif /* HAVE_SYS_IOCTL_H */
23 
24 #ifdef SYS_WINNT
25 #undef write	/* ports/winnt/include/config.h: #define write _write */
26 extern int async_write(int, const void *, unsigned int);
27 #define write(fd, data, octets)	async_write(fd, data, octets)
28 #endif
29 
30 /*
31  * This driver supports the US (NIST, USNO) and European (PTB, NPL,
32  * etc.) modem time services, as well as Spectracom GPS and WWVB
33  * receivers connected via a modem. The driver periodically dials a
34  * number from a telephone list, receives the timecode data and
35  * calculates the local clock correction. It is designed primarily for
36  * use as backup when neither a radio clock nor connectivity to Internet
37  * time servers is available.
38  *
39  * This driver requires a modem with a Hayes-compatible command set and
40  * control over the modem data terminal ready (DTR) control line. The
41  * modem setup string is hard-coded in the driver and may require
42  * changes for nonstandard modems or special circumstances.
43  *
44  * When enabled, the calling program dials the first number in the
45  * phones file. If that call fails, it dials the second number and
46  * so on. The phone number is specified by the Hayes ATDT prefix
47  * followed by the number itself, including the long-distance prefix
48  * and delay code, if necessary. The calling program is enabled
49  * when (a) fudge flag1 is set by ntpdc, (b) at each poll interval
50  * when no other synchronization sources are present, and (c) at each
51  * poll interval whether or not other synchronization sources are
52  * present. The calling program disconnects if (a) the called party
53  * is busy or does not answer, (b) the called party disconnects
54  * before a sufficient nuimber of timecodes have been received.
55  *
56  * The driver is transparent to each of the modem time services and
57  * Spectracom radios. It selects the parsing algorithm depending on the
58  * message length. There is some hazard should the message be corrupted.
59  * However, the data format is checked carefully and only if all checks
60  * succeed is the message accepted. Corrupted lines are discarded
61  * without complaint.
62  *
63  * Fudge controls
64  *
65  * flag1	force a call in manual mode
66  * flag2	enable port locking (not verified)
67  * flag3	not used
68  * flag4	not used
69  *
70  * time1	offset adjustment (s)
71  *
72  * Ordinarily, the serial port is connected to a modem and the phones
73  * list is defined. If no phones list is defined, the port can be
74  * connected directly to a device or another computer. In this case the
75  * driver will send a single character 'T' at each poll event. If
76  * fudge flag2 is enabled, port locking allows the modem to be shared
77  * when not in use by this driver.
78  */
79 /*
80  * National Institute of Science and Technology (NIST)
81  *
82  * Phone: (303) 494-4774 (Boulder, CO); (808) 335-4721 (Hawaii)
83  *
84  * Data Format
85  *
86  * National Institute of Standards and Technology
87  * Telephone Time Service, Generator 3B
88  * Enter question mark "?" for HELP
89  *                         D  L D
90  *  MJD  YR MO DA H  M  S  ST S UT1 msADV        <OTM>
91  * 47999 90-04-18 21:39:15 50 0 +.1 045.0 UTC(NIST) *<CR><LF>
92  * ...
93  *
94  * MJD, DST, DUT1 and UTC are not used by this driver. The "*" or "#" is
95  * the on-time markers echoed by the driver and used by NIST to measure
96  * and correct for the propagation delay. Note: the ACTS timecode has
97  * recently been changed to eliminate the * on-time indicator. The
98  * reason for this and the long term implications are not clear.
99  *
100  * US Naval Observatory (USNO)
101  *
102  * Phone: (202) 762-1594 (Washington, DC); (719) 567-6742 (Boulder, CO)
103  *
104  * Data Format (two lines, repeating at one-second intervals)
105  *
106  * jjjjj nnn hhmmss UTC<CR><LF>
107  * *<CR><LF>
108  *
109  * jjjjj	modified Julian day number (not used)
110  * nnn		day of year
111  * hhmmss	second of day
112  * *		on-time marker for previous timecode
113  * ...
114  *
115  * USNO does not correct for the propagation delay. A fudge time1 of
116  * about .06 s is advisable.
117  *
118  * European Services (PTB, NPL, etc.)
119  *
120  * PTB: +49 531 512038 (Germany)
121  * NPL: 0906 851 6333 (UK only)
122  *
123  * Data format (see the documentation for phone numbers and formats.)
124  *
125  * 1995-01-23 20:58:51 MEZ  10402303260219950123195849740+40000500<CR><LF>
126  *
127  * Spectracom GPS and WWVB Receivers
128  *
129  * If a modem is connected to a Spectracom receiver, this driver will
130  * call it up and retrieve the time in one of two formats. As this
131  * driver does not send anything, the radio will have to either be
132  * configured in continuous mode or be polled by another local driver.
133  */
134 /*
135  * Interface definitions
136  */
137 #define	DEVICE		"/dev/acts%d" /* device name and unit */
138 #define	SPEED232	B19200	/* uart speed (19200 bps) */
139 #define	PRECISION	(-10)	/* precision assumed (about 1 ms) */
140 #define LOCKFILE	"/var/spool/lock/LCK..cua%d"
141 #define DESCRIPTION	"Automated Computer Time Service" /* WRU */
142 #define REFID		"NONE"	/* default reference ID */
143 #define MSGCNT		20	/* max message count */
144 #define	MAXPHONE	10	/* max number of phone numbers */
145 
146 /*
147  * Calling program modes (mode)
148  */
149 #define MODE_BACKUP	0	/* backup mode */
150 #define MODE_AUTO	1	/* automatic mode */
151 #define MODE_MANUAL	2	/* manual mode */
152 
153 /*
154  * Service identifiers (message length)
155  */
156 #define REFACTS		"NIST"	/* NIST reference ID */
157 #define LENACTS		50	/* NIST format A */
158 #define REFUSNO		"USNO"	/* USNO reference ID */
159 #define LENUSNO		20	/* USNO */
160 #define REFPTB		"PTB\0"	/* PTB/NPL reference ID */
161 #define LENPTB		78	/* PTB/NPL format */
162 #define REFWWVB		"WWVB"	/* WWVB reference ID */
163 #define	LENWWVB0	22	/* WWVB format 0 */
164 #define	LENWWVB2	24	/* WWVB format 2 */
165 #define LF		0x0a	/* ASCII LF */
166 
167 /*
168  * Modem setup strings. These may have to be changed for
169  * some modems.
170  *
171  * AT	command prefix
172  * B1	US answer tone
173  * &C0	disable carrier detect
174  * &D2	hang up and return to command mode on DTR transition
175  * E0	modem command echo disabled
176  * L1	set modem speaker volume to low level
177  * M1	speaker enabled until carrier detect
178  * Q0	return result codes
179  * V1	return result codes as English words
180  * Y1	enable long-space disconnect
181  */
182 const char def_modem_setup[] = "ATB1&C0&D2E0L1M1Q0V1Y1";
183 const char *modem_setup = def_modem_setup;
184 
185 /*
186  * Timeouts (all in seconds)
187  */
188 #define SETUP		3	/* setup timeout */
189 #define	REDIAL		30	/* redial timeout */
190 #define ANSWER		60	/* answer timeout */
191 #define TIMECODE	60	/* message timeout */
192 #define	MAXCODE		20	/* max timecodes */
193 
194 /*
195  * State machine codes
196  */
197 typedef enum {
198 	S_IDLE,			/* wait for poll */
199 	S_SETUP,		/* send modem setup */
200 	S_CONNECT,		/* wait for answer */
201 	S_MSG			/* wait for timecode */
202 } teModemState;
203 
204 /*
205  * Unit control structure
206  */
207 struct actsunit {
208 	int	unit;		/* unit number */
209 	int	state;		/* the first one was Delaware */
210 	int	timer;		/* timeout counter */
211 	int	retry;		/* retry index */
212 	int	msgcnt;		/* count of messages received */
213 	l_fp	tstamp;		/* on-time timestamp */
214 	char	*bufptr;	/* next incoming char stored here */
215 	char	buf[BMAX];	/* bufptr roams within buf[] */
216 };
217 
218 /*
219  * Function prototypes
220  */
221 static	int	acts_start	(int, struct peer *);
222 static	void	acts_shutdown	(int, struct peer *);
223 static	void	acts_receive	(struct recvbuf *);
224 static	void	acts_message	(struct peer *, const char *);
225 static	void	acts_timecode	(struct peer *, const char *);
226 static	void	acts_poll	(int, struct peer *);
227 static	void	acts_timeout	(struct peer *, teModemState);
228 static	void	acts_timer	(int, struct peer *);
229 static	void	acts_close	(struct peer *);
230 
231 /*
232  * Transfer vector (conditional structure name)
233  */
234 struct refclock refclock_acts = {
235 	acts_start,		/* start up driver */
236 	acts_shutdown,		/* shut down driver */
237 	acts_poll,		/* transmit poll message */
238 	noentry,		/* not used */
239 	noentry,		/* not used */
240 	noentry,		/* not used */
241 	acts_timer		/* housekeeping timer */
242 };
243 
244 /*
245  * Initialize data for processing
246  */
247 static int
248 acts_start(
249 	int	unit,
250 	struct peer *peer
251 	)
252 {
253 	struct actsunit *up;
254 	struct refclockproc *pp;
255 	const char *setup;
256 
257 	/*
258 	 * Allocate and initialize unit structure
259 	 */
260 	up = emalloc_zero(sizeof(struct actsunit));
261 	up->unit = unit;
262 	pp = peer->procptr;
263 	pp->unitptr = up;
264 	pp->io.clock_recv = acts_receive;
265 	pp->io.srcclock = peer;
266 	pp->io.datalen = 0;
267 	pp->io.fd = -1;
268 
269 	/*
270 	 * Initialize miscellaneous variables
271 	 */
272 	peer->precision = PRECISION;
273 	pp->clockdesc = DESCRIPTION;
274 	memcpy(&pp->refid, REFID, 4);
275 	peer->sstclktype = CTL_SST_TS_TELEPHONE;
276 	up->bufptr = up->buf;
277 	if (def_modem_setup == modem_setup) {
278 		setup = get_ext_sys_var("modemsetup");
279 		if (setup != NULL)
280 			modem_setup = estrdup(setup);
281 	}
282 
283 	return (1);
284 }
285 
286 
287 /*
288  * acts_shutdown - shut down the clock
289  */
290 static void
291 acts_shutdown(
292 	int	unit,
293 	struct peer *peer
294 	)
295 {
296 	struct actsunit *up;
297 	struct refclockproc *pp;
298 
299 	/*
300 	 * Warning: do this only when a call is not in progress.
301 	 */
302 	pp = peer->procptr;
303 	up = pp->unitptr;
304 	acts_close(peer);
305 	free(up);
306 }
307 
308 
309 /*
310  * acts_receive - receive data from the serial interface
311  */
312 static void
313 acts_receive(
314 	struct recvbuf *rbufp
315 	)
316 {
317 	struct actsunit *up;
318 	struct refclockproc *pp;
319 	struct peer *peer;
320 	char	tbuf[sizeof(up->buf)];
321 	char *	tptr;
322 	int	octets;
323 
324 	/*
325 	 * Initialize pointers and read the timecode and timestamp. Note
326 	 * we are in raw mode and victim of whatever the terminal
327 	 * interface kicks up; so, we have to reassemble messages from
328 	 * arbitrary fragments. Capture the timecode at the beginning of
329 	 * the message and at the '*' and '#' on-time characters.
330 	 */
331 	peer = rbufp->recv_peer;
332 	pp = peer->procptr;
333 	up = pp->unitptr;
334 	octets = sizeof(up->buf) - (up->bufptr - up->buf);
335 	refclock_gtraw(rbufp, tbuf, octets, &pp->lastrec);
336 	for (tptr = tbuf; *tptr != '\0'; tptr++) {
337 		if (*tptr == LF) {
338 			if (up->bufptr == up->buf) {
339 				up->tstamp = pp->lastrec;
340 				continue;
341 			} else {
342 				*up->bufptr = '\0';
343 				up->bufptr = up->buf;
344 				acts_message(peer, up->buf);
345 			}
346 		} else if (!iscntrl((unsigned char)*tptr)) {
347 			*up->bufptr++ = *tptr;
348 			if (*tptr == '*' || *tptr == '#') {
349 				up->tstamp = pp->lastrec;
350 				if (write(pp->io.fd, tptr, 1) < 0)
351 					msyslog(LOG_ERR, "acts: write echo fails %m");
352 			}
353 		}
354 	}
355 }
356 
357 
358 /*
359  * acts_message - process message
360  */
361 void
362 acts_message(
363 	struct peer *peer,
364 	const char *msg
365 	)
366 {
367 	struct actsunit *up;
368 	struct refclockproc *pp;
369 	char	tbuf[BMAX];
370 	int		dtr = TIOCM_DTR;
371 
372 	DPRINTF(1, ("acts: %d %s\n", (int)strlen(msg), msg));
373 
374 	/*
375 	 * What to do depends on the state and the first token in the
376 	 * message.
377 	 */
378 	pp = peer->procptr;
379 	up = pp->unitptr;
380 
381 	/*
382 	 * Extract the first token in the line.
383 	 */
384 	strlcpy(tbuf, msg, sizeof(tbuf));
385 	strtok(tbuf, " ");
386 	switch (up->state) {
387 
388 	/*
389 	 * We are waiting for the OK response to the modem setup
390 	 * command. When this happens, dial the number followed.
391 	 * If anything other than OK is received, just ignore it
392 	 * and wait for timeoue.
393 	 */
394 	case S_SETUP:
395 		if (strcmp(tbuf, "OK") != 0) {
396 			/*
397 			 * We disable echo with MODEM_SETUP's E0 but
398 			 * if the modem was previously E1, we will
399 			 * see MODEM_SETUP echoed before the OK/ERROR.
400 			 * Ignore it.
401 			 */
402 			if (!strcmp(tbuf, modem_setup))
403 				return;
404 			break;
405 		}
406 
407 		mprintf_event(PEVNT_CLOCK, peer, "DIAL #%d %s",
408 			      up->retry, sys_phone[up->retry]);
409 		if (ioctl(pp->io.fd, TIOCMBIS, &dtr) < 0)
410 			msyslog(LOG_ERR, "acts: ioctl(TIOCMBIS) failed: %m");
411 		if (write(pp->io.fd, sys_phone[up->retry],
412 		    strlen(sys_phone[up->retry])) < 0)
413 			msyslog(LOG_ERR, "acts: write DIAL fails %m");
414 		write(pp->io.fd, "\r", 1);
415 		up->retry++;
416 		up->state = S_CONNECT;
417 		up->timer = ANSWER;
418 		return;
419 
420 	/*
421 	 * We are waiting for the CONNECT response to the dial
422 	 * command. When this happens, listen for timecodes. If
423 	 * somthing other than CONNECT is received, like BUSY
424 	 * or NO CARRIER, abort the call.
425 	 */
426 	case S_CONNECT:
427 		if (strcmp(tbuf, "CONNECT") != 0)
428 			break;
429 
430 		report_event(PEVNT_CLOCK, peer, msg);
431 		up->state = S_MSG;
432 		up->timer = TIMECODE;
433 		return;
434 
435 	/*
436 	 * We are waiting for a timecode response. Pass it to
437 	 * the parser. If NO CARRIER is received, save the
438 	 * messages and abort the call.
439 	 */
440 	case S_MSG:
441 		if (strcmp(tbuf, "NO") == 0)
442 			report_event(PEVNT_CLOCK, peer, msg);
443 		if (up->msgcnt < MAXCODE)
444 			acts_timecode(peer, msg);
445 		else
446 			acts_timeout(peer, S_MSG);
447 		return;
448 	}
449 
450 	/*
451 	 * Other response. Tell us about it.
452 	 */
453 	report_event(PEVNT_CLOCK, peer, msg);
454 	acts_close(peer);
455 }
456 
457 
458 /*
459  * acts_timeout - called on timeout
460  */
461 static void
462 acts_timeout(
463 	struct peer *peer,
464 	teModemState	dstate
465 	)
466 {
467 	struct actsunit *up;
468 	struct refclockproc *pp;
469 	int	fd;
470 	int	rc;
471 	char	device[20];
472 	char	lockfile[128], pidbuf[8];
473 
474 	/*
475 	 * The state machine is driven by messages from the modem,
476 	 * when first started and at timeout.
477 	 */
478 	pp = peer->procptr;
479 	up = pp->unitptr;
480 	switch (dstate) {
481 
482 	/*
483 	 * System poll event. Lock the modem port, open the device
484 	 * and send the setup command.
485 	 */
486 	case S_IDLE:
487 		if (-1 != pp->io.fd)
488 			return;		/* port is already open */
489 
490 		/*
491 		 * Lock the modem port. If busy, retry later. Note: if
492 		 * something fails between here and the close, the lock
493 		 * file may not be removed.
494 		 */
495 		if (pp->sloppyclockflag & CLK_FLAG2) {
496 			snprintf(lockfile, sizeof(lockfile), LOCKFILE,
497 			    up->unit);
498 			fd = open(lockfile, O_WRONLY | O_CREAT | O_EXCL,
499 			    0644);
500 			if (fd < 0) {
501 				report_event(PEVNT_CLOCK, peer, "acts: port busy");
502 				return;
503 			}
504 			snprintf(pidbuf, sizeof(pidbuf), "%d\n",
505 			    (u_int)getpid());
506 			if (write(fd, pidbuf, strlen(pidbuf)) < 0)
507 				msyslog(LOG_ERR, "acts: write lock fails %m");
508 			close(fd);
509 		}
510 
511 		/*
512 		 * Open the device in raw mode and link the I/O.
513 		 */
514 		snprintf(device, sizeof(device), DEVICE,
515 		    up->unit);
516 		fd = refclock_open(device, SPEED232, LDISC_ACTS |
517 		    LDISC_RAW | LDISC_REMOTE);
518 		if (fd < 0) {
519 			msyslog(LOG_ERR, "acts: open fails %m");
520 			return;
521 		}
522 		pp->io.fd = fd;
523 		if (!io_addclock(&pp->io)) {
524 			msyslog(LOG_ERR, "acts: addclock fails");
525 			close(fd);
526 			pp->io.fd = -1;
527 			return;
528 		}
529 		up->msgcnt = 0;
530 		up->bufptr = up->buf;
531 
532 		/*
533 		 * If the port is directly connected to the device, skip
534 		 * the modem business and send 'T' for Spectrabum.
535 		 */
536 		if (sys_phone[up->retry] == NULL) {
537 			if (write(pp->io.fd, "T", 1) < 0)
538 				msyslog(LOG_ERR, "acts: write T fails %m");
539 			up->state = S_MSG;
540 			up->timer = TIMECODE;
541 			return;
542 		}
543 
544 		/*
545 		 * Initialize the modem. This works with Hayes-
546 		 * compatible modems.
547 		 */
548 		mprintf_event(PEVNT_CLOCK, peer, "SETUP %s",
549 			      modem_setup);
550 		rc = write(pp->io.fd, modem_setup, strlen(modem_setup));
551 		if (rc < 0)
552 			msyslog(LOG_ERR, "acts: write SETUP fails %m");
553 		write(pp->io.fd, "\r", 1);
554 		up->state = S_SETUP;
555 		up->timer = SETUP;
556 		return;
557 
558 	/*
559 	 * In SETUP state the modem did not respond OK to setup string.
560 	 */
561 	case S_SETUP:
562 		report_event(PEVNT_CLOCK, peer, "no modem");
563 		break;
564 
565 	/*
566 	 * In CONNECT state the call did not complete. Abort the call.
567 	 */
568 	case S_CONNECT:
569 		report_event(PEVNT_CLOCK, peer, "no answer");
570 		break;
571 
572 	/*
573 	 * In MSG states no further timecodes are expected. If any
574 	 * timecodes have arrived, update the clock. In any case,
575 	 * terminate the call.
576 	 */
577 	case S_MSG:
578 		if (up->msgcnt == 0) {
579 			report_event(PEVNT_CLOCK, peer, "no timecodes");
580 		} else {
581 			pp->lastref = pp->lastrec;
582 			record_clock_stats(&peer->srcadr, pp->a_lastcode);
583 			refclock_receive(peer);
584 		}
585 		break;
586 	}
587 	acts_close(peer);
588 }
589 
590 
591 /*
592  * acts_close - close and prepare for next call.
593  *
594  * In ClOSE state no further protocol actions are required
595  * other than to close and release the device and prepare to
596  * dial the next number if necessary.
597  */
598 void
599 acts_close(
600 	struct peer *peer
601 	)
602 {
603 	struct actsunit *up;
604 	struct refclockproc *pp;
605 	char	lockfile[128];
606 	int	dtr;
607 
608 	pp = peer->procptr;
609 	up = pp->unitptr;
610 	if (pp->io.fd != -1) {
611 		report_event(PEVNT_CLOCK, peer, "close");
612 		dtr = TIOCM_DTR;
613 		if (ioctl(pp->io.fd, TIOCMBIC, &dtr) < 0)
614 			msyslog(LOG_ERR, "acts: ioctl(TIOCMBIC) failed: %m");
615 		io_closeclock(&pp->io);
616 		pp->io.fd = -1;
617 	}
618 	if (pp->sloppyclockflag & CLK_FLAG2) {
619 		snprintf(lockfile, sizeof(lockfile),
620 		    LOCKFILE, up->unit);
621 		unlink(lockfile);
622 	}
623 	if (up->msgcnt == 0 && up->retry > 0) {
624 		if (sys_phone[up->retry] != NULL) {
625 			up->state = S_IDLE;
626 			up->timer = REDIAL;
627 			return;
628 		}
629 	}
630 	up->state = S_IDLE;
631 	up->timer = 0;
632 }
633 
634 
635 /*
636  * acts_poll - called by the transmit routine
637  */
638 static void
639 acts_poll(
640 	int	unit,
641 	struct peer *peer
642 	)
643 {
644 	struct actsunit *up;
645 	struct refclockproc *pp;
646 
647 	/*
648 	 * This routine is called at every system poll. All it does is
649 	 * set flag1 under certain conditions. The real work is done by
650 	 * the timeout routine and state machine.
651 	 */
652 	pp = peer->procptr;
653 	up = pp->unitptr;
654 	switch (peer->ttl) {
655 
656 	/*
657 	 * In manual mode the calling program is activated by the ntpdc
658 	 * program using the enable flag (fudge flag1), either manually
659 	 * or by a cron job.
660 	 */
661 	case MODE_MANUAL:
662 		return;
663 
664 	/*
665 	 * In automatic mode the calling program runs continuously at
666 	 * intervals determined by the poll event or specified timeout.
667 	 */
668 	case MODE_AUTO:
669 		break;
670 
671 	/*
672 	 * In backup mode the calling program runs continuously as long
673 	 * as either no peers are available or this peer is selected.
674 	 */
675 	case MODE_BACKUP:
676 		if (!(sys_peer == NULL || sys_peer == peer))
677 			return;
678 
679 		break;
680 	}
681 	pp->polls++;
682 	if (S_IDLE == up->state) {
683 		up->retry = 0;
684 		acts_timeout(peer, S_IDLE);
685 	}
686 }
687 
688 
689 /*
690  * acts_timer - called at one-second intervals
691  */
692 static void
693 acts_timer(
694 	int	unit,
695 	struct peer *peer
696 	)
697 {
698 	struct actsunit *up;
699 	struct refclockproc *pp;
700 
701 	/*
702 	 * This routine implments a timeout which runs for a programmed
703 	 * interval. The counter is initialized by the state machine and
704 	 * counts down to zero. Upon reaching zero, the state machine is
705 	 * called. If flag1 is set while timer is zero, force a call.
706 	 */
707 	pp = peer->procptr;
708 	up = pp->unitptr;
709 	if (up->timer == 0) {
710 		if (pp->sloppyclockflag & CLK_FLAG1) {
711 			pp->sloppyclockflag &= ~CLK_FLAG1;
712 			acts_timeout(peer, S_IDLE);
713 		}
714 	} else {
715 		up->timer--;
716 		if (up->timer == 0)
717 			acts_timeout(peer, up->state);
718 	}
719 }
720 
721 /*
722  * acts_timecode - identify the service and parse the timecode message
723  */
724 void
725 acts_timecode(
726 	struct peer *	peer,	/* peer structure pointer */
727 	const char *	str	/* timecode string */
728 	)
729 {
730 	struct actsunit *up;
731 	struct refclockproc *pp;
732 	int	day;		/* day of the month */
733 	int	month;		/* month of the year */
734 	u_long	mjd;		/* Modified Julian Day */
735 	double	dut1;		/* DUT adjustment */
736 
737 	u_int	dst;		/* ACTS daylight/standard time */
738 	u_int	leap;		/* ACTS leap indicator */
739 	double	msADV;		/* ACTS transmit advance (ms) */
740 	char	utc[10];	/* ACTS timescale */
741 	char	flag;		/* ACTS on-time character (* or #) */
742 
743 	char	synchar;	/* WWVB synchronized indicator */
744 	char	qualchar;	/* WWVB quality indicator */
745 	char	leapchar;	/* WWVB leap indicator */
746 	char	dstchar;	/* WWVB daylight/savings indicator */
747 	int	tz;		/* WWVB timezone */
748 
749 	int	leapmonth;	/* PTB/NPL month of leap */
750 	char	leapdir;	/* PTB/NPL leap direction */
751 
752 	/*
753 	 * The parser selects the modem format based on the message
754 	 * length. Since the data are checked carefully, occasional
755 	 * errors due noise are forgivable.
756 	 */
757 	pp = peer->procptr;
758 	up = pp->unitptr;
759 	pp->nsec = 0;
760 	switch (strlen(str)) {
761 
762 	/*
763 	 * For USNO format on-time character '*', which is on a line by
764 	 * itself. Be sure a timecode has been received.
765 	 */
766 	case 1:
767 		if (*str == '*' && up->msgcnt > 0)
768 			break;
769 
770 		return;
771 
772 	/*
773 	 * ACTS format A: "jjjjj yy-mm-dd hh:mm:ss ds l uuu aaaaa
774 	 * UTC(NIST) *".
775 	 */
776 	case LENACTS:
777 		if (sscanf(str,
778 		    "%5ld %2d-%2d-%2d %2d:%2d:%2d %2d %1d %3lf %5lf %9s %c",
779 		    &mjd, &pp->year, &month, &day, &pp->hour,
780 		    &pp->minute, &pp->second, &dst, &leap, &dut1,
781 		    &msADV, utc, &flag) != 13) {
782 			refclock_report(peer, CEVNT_BADREPLY);
783 			return;
784 		}
785 		pp->day = ymd2yd(pp->year, month, day);
786 		pp->leap = LEAP_NOWARNING;
787 		if (leap == 1)
788 			pp->leap = LEAP_ADDSECOND;
789 		else if (leap == 2)
790 			pp->leap = LEAP_DELSECOND;
791 		memcpy(&pp->refid, REFACTS, 4);
792 		up->msgcnt++;
793 		if (flag != '#' && up->msgcnt < 10)
794 			return;
795 
796 		break;
797 
798 	/*
799 	 * USNO format: "jjjjj nnn hhmmss UTC"
800 	 */
801 	case LENUSNO:
802 		if (sscanf(str, "%5ld %3d %2d%2d%2d %3s",
803 		    &mjd, &pp->day, &pp->hour, &pp->minute,
804 		    &pp->second, utc) != 6) {
805 			refclock_report(peer, CEVNT_BADREPLY);
806 			return;
807 		}
808 
809 		/*
810 		 * Wait for the on-time character, which follows in a
811 		 * separate message. There is no provision for leap
812 		 * warning.
813 		 */
814 		pp->leap = LEAP_NOWARNING;
815 		memcpy(&pp->refid, REFUSNO, 4);
816 		up->msgcnt++;
817 		break;
818 
819 	/*
820 	 * PTB/NPL format: "yyyy-mm-dd hh:mm:ss MEZ"
821 	 */
822 	case LENPTB:
823 		if (sscanf(str,
824 		    "%*4d-%*2d-%*2d %*2d:%*2d:%2d %*5c%*12c%4d%2d%2d%2d%2d%5ld%2lf%c%2d%3lf%*15c%c",
825 		    &pp->second, &pp->year, &month, &day, &pp->hour,
826 		    &pp->minute, &mjd, &dut1, &leapdir, &leapmonth,
827 		    &msADV, &flag) != 12) {
828 			refclock_report(peer, CEVNT_BADREPLY);
829 			return;
830 		}
831 		pp->leap = LEAP_NOWARNING;
832 		if (leapmonth == month) {
833 			if (leapdir == '+')
834 				pp->leap = LEAP_ADDSECOND;
835 			else if (leapdir == '-')
836 				pp->leap = LEAP_DELSECOND;
837 		}
838 		pp->day = ymd2yd(pp->year, month, day);
839 		memcpy(&pp->refid, REFPTB, 4);
840 		up->msgcnt++;
841 		break;
842 
843 
844 	/*
845 	 * WWVB format 0: "I  ddd hh:mm:ss DTZ=nn"
846 	 */
847 	case LENWWVB0:
848 		if (sscanf(str, "%c %3d %2d:%2d:%2d %cTZ=%2d",
849 		    &synchar, &pp->day, &pp->hour, &pp->minute,
850 		    &pp->second, &dstchar, &tz) != 7) {
851 			refclock_report(peer, CEVNT_BADREPLY);
852 			return;
853 		}
854 		pp->leap = LEAP_NOWARNING;
855 		if (synchar != ' ')
856 			pp->leap = LEAP_NOTINSYNC;
857 		memcpy(&pp->refid, REFWWVB, 4);
858 		up->msgcnt++;
859 		break;
860 
861 	/*
862 	 * WWVB format 2: "IQyy ddd hh:mm:ss.mmm LD"
863 	 */
864 	case LENWWVB2:
865 		if (sscanf(str, "%c%c%2d %3d %2d:%2d:%2d.%3ld%c%c%c",
866 		    &synchar, &qualchar, &pp->year, &pp->day,
867 		    &pp->hour, &pp->minute, &pp->second, &pp->nsec,
868 		    &dstchar, &leapchar, &dstchar) != 11) {
869 			refclock_report(peer, CEVNT_BADREPLY);
870 			return;
871 		}
872 		pp->nsec *= 1000000;
873 		pp->leap = LEAP_NOWARNING;
874 		if (synchar != ' ')
875 			pp->leap = LEAP_NOTINSYNC;
876 		else if (leapchar == 'L')
877 			pp->leap = LEAP_ADDSECOND;
878 		memcpy(&pp->refid, REFWWVB, 4);
879 		up->msgcnt++;
880 		break;
881 
882 	/*
883 	 * None of the above. Just forget about it and wait for the next
884 	 * message or timeout.
885 	 */
886 	default:
887 		return;
888 	}
889 
890 	/*
891 	 * We have a valid timecode. The fudge time1 value is added to
892 	 * each sample by the main line routines. Note that in current
893 	 * telephone networks the propatation time can be different for
894 	 * each call and can reach 200 ms for some calls.
895 	 */
896 	peer->refid = pp->refid;
897 	pp->lastrec = up->tstamp;
898 	if (up->msgcnt == 0)
899 		return;
900 
901 	strlcpy(pp->a_lastcode, str, sizeof(pp->a_lastcode));
902 	pp->lencode = strlen(pp->a_lastcode);
903 	if (!refclock_process(pp)) {
904 		refclock_report(peer, CEVNT_BADTIME);
905 		return;
906 	}
907 	pp->lastref = pp->lastrec;
908 }
909 #else
910 int refclock_acts_bs;
911 #endif /* REFCLOCK */
912