154359Sroberto/*
2182007Sroberto * refclock_acts - clock driver for the NIST/USNO/PTB/NPL Computer Time
3182007Sroberto *	Services
454359Sroberto */
554359Sroberto#ifdef HAVE_CONFIG_H
654359Sroberto#include <config.h>
754359Sroberto#endif
854359Sroberto
9280849Scy#if defined(REFCLOCK) && defined(CLOCK_ACTS)
1054359Sroberto
1154359Sroberto#include "ntpd.h"
1254359Sroberto#include "ntp_io.h"
1354359Sroberto#include "ntp_unixtime.h"
1454359Sroberto#include "ntp_refclock.h"
1554359Sroberto#include "ntp_stdlib.h"
1654359Sroberto#include "ntp_control.h"
1754359Sroberto
1882498Sroberto#include <stdio.h>
1982498Sroberto#include <ctype.h>
2082498Sroberto#ifdef HAVE_SYS_IOCTL_H
2182498Sroberto# include <sys/ioctl.h>
2282498Sroberto#endif /* HAVE_SYS_IOCTL_H */
2382498Sroberto
24280849Scy#ifdef SYS_WINNT
25280849Scy#undef write	/* ports/winnt/include/config.h: #define write _write */
26280849Scyextern int async_write(int, const void *, unsigned int);
27280849Scy#define write(fd, data, octets)	async_write(fd, data, octets)
28280849Scy#endif
29280849Scy
3054359Sroberto/*
31182007Sroberto * This driver supports the US (NIST, USNO) and European (PTB, NPL,
32182007Sroberto * etc.) modem time services, as well as Spectracom GPS and WWVB
33182007Sroberto * receivers connected via a modem. The driver periodically dials a
34182007Sroberto * number from a telephone list, receives the timecode data and
35182007Sroberto * calculates the local clock correction. It is designed primarily for
36182007Sroberto * use as backup when neither a radio clock nor connectivity to Internet
37182007Sroberto * time servers is available.
3854359Sroberto *
39182007Sroberto * This driver requires a modem with a Hayes-compatible command set and
40182007Sroberto * control over the modem data terminal ready (DTR) control line. The
41182007Sroberto * modem setup string is hard-coded in the driver and may require
42280849Scy * changes for nonstandard modems or special circumstances.
4354359Sroberto *
44280849Scy * When enabled, the calling program dials the first number in the
45280849Scy * phones file. If that call fails, it dials the second number and
46280849Scy * so on. The phone number is specified by the Hayes ATDT prefix
47280849Scy * followed by the number itself, including the long-distance prefix
48280849Scy * and delay code, if necessary. The calling program is enabled
49280849Scy * when (a) fudge flag1 is set by ntpdc, (b) at each poll interval
50280849Scy * when no other synchronization sources are present, and (c) at each
51280849Scy * poll interval whether or not other synchronization sources are
52280849Scy * present. The calling program disconnects if (a) the called party
53280849Scy * is busy or does not answer, (b) the called party disconnects
54280849Scy * before a sufficient nuimber of timecodes have been received.
5554359Sroberto *
56182007Sroberto * The driver is transparent to each of the modem time services and
57182007Sroberto * Spectracom radios. It selects the parsing algorithm depending on the
58182007Sroberto * message length. There is some hazard should the message be corrupted.
59182007Sroberto * However, the data format is checked carefully and only if all checks
60182007Sroberto * succeed is the message accepted. Corrupted lines are discarded
61182007Sroberto * without complaint.
6254359Sroberto *
63182007Sroberto * Fudge controls
6454359Sroberto *
65182007Sroberto * flag1	force a call in manual mode
66182007Sroberto * flag2	enable port locking (not verified)
67280849Scy * flag3	not used
68182007Sroberto * flag4	not used
6954359Sroberto *
70182007Sroberto * time1	offset adjustment (s)
7154359Sroberto *
72280849Scy * Ordinarily, the serial port is connected to a modem and the phones
73280849Scy * list is defined. If no phones list is defined, the port can be
74280849Scy * connected directly to a device or another computer. In this case the
75280849Scy * driver will send a single character 'T' at each poll event. If
76280849Scy * fudge flag2 is enabled, port locking allows the modem to be shared
77280849Scy * when not in use by this driver.
7854359Sroberto */
7954359Sroberto/*
80182007Sroberto * National Institute of Science and Technology (NIST)
8154359Sroberto *
82182007Sroberto * Phone: (303) 494-4774 (Boulder, CO); (808) 335-4721 (Hawaii)
8354359Sroberto *
84182007Sroberto * Data Format
85182007Sroberto *
8654359Sroberto * National Institute of Standards and Technology
8754359Sroberto * Telephone Time Service, Generator 3B
8854359Sroberto * Enter question mark "?" for HELP
8954359Sroberto *                         D  L D
9054359Sroberto *  MJD  YR MO DA H  M  S  ST S UT1 msADV        <OTM>
91182007Sroberto * 47999 90-04-18 21:39:15 50 0 +.1 045.0 UTC(NIST) *<CR><LF>
92182007Sroberto * ...
9354359Sroberto *
94182007Sroberto * MJD, DST, DUT1 and UTC are not used by this driver. The "*" or "#" is
95182007Sroberto * the on-time markers echoed by the driver and used by NIST to measure
96280849Scy * and correct for the propagation delay. Note: the ACTS timecode has
97280849Scy * recently been changed to eliminate the * on-time indicator. The
98280849Scy * reason for this and the long term implications are not clear.
9954359Sroberto *
100182007Sroberto * US Naval Observatory (USNO)
10154359Sroberto *
102182007Sroberto * Phone: (202) 762-1594 (Washington, DC); (719) 567-6742 (Boulder, CO)
10354359Sroberto *
104182007Sroberto * Data Format (two lines, repeating at one-second intervals)
10554359Sroberto *
106182007Sroberto * jjjjj nnn hhmmss UTC<CR><LF>
107182007Sroberto * *<CR><LF>
10854359Sroberto *
109182007Sroberto * jjjjj	modified Julian day number (not used)
110182007Sroberto * nnn		day of year
111182007Sroberto * hhmmss	second of day
112182007Sroberto * *		on-time marker for previous timecode
113182007Sroberto * ...
11454359Sroberto *
115182007Sroberto * USNO does not correct for the propagation delay. A fudge time1 of
116182007Sroberto * about .06 s is advisable.
11754359Sroberto *
118182007Sroberto * European Services (PTB, NPL, etc.)
11954359Sroberto *
120182007Sroberto * PTB: +49 531 512038 (Germany)
121182007Sroberto * NPL: 0906 851 6333 (UK only)
12254359Sroberto *
123182007Sroberto * Data format (see the documentation for phone numbers and formats.)
12454359Sroberto *
125182007Sroberto * 1995-01-23 20:58:51 MEZ  10402303260219950123195849740+40000500<CR><LF>
12654359Sroberto *
127182007Sroberto * Spectracom GPS and WWVB Receivers
12854359Sroberto *
129182007Sroberto * If a modem is connected to a Spectracom receiver, this driver will
130182007Sroberto * call it up and retrieve the time in one of two formats. As this
131182007Sroberto * driver does not send anything, the radio will have to either be
132182007Sroberto * configured in continuous mode or be polled by another local driver.
13354359Sroberto */
13454359Sroberto/*
13554359Sroberto * Interface definitions
13654359Sroberto */
137182007Sroberto#define	DEVICE		"/dev/acts%d" /* device name and unit */
138280849Scy#define	SPEED232	B19200	/* uart speed (19200 bps) */
13954359Sroberto#define	PRECISION	(-10)	/* precision assumed (about 1 ms) */
140280849Scy#define LOCKFILE	"/var/spool/lock/LCK..cua%d"
141182007Sroberto#define DESCRIPTION	"Automated Computer Time Service" /* WRU */
142182007Sroberto#define REFID		"NONE"	/* default reference ID */
143182007Sroberto#define MSGCNT		20	/* max message count */
144280849Scy#define	MAXPHONE	10	/* max number of phone numbers */
145182007Sroberto
146182007Sroberto/*
147280849Scy * Calling program modes (mode)
148182007Sroberto */
149280849Scy#define MODE_BACKUP	0	/* backup mode */
150280849Scy#define MODE_AUTO	1	/* automatic mode */
15154359Sroberto#define MODE_MANUAL	2	/* manual mode */
15254359Sroberto
153182007Sroberto/*
154280849Scy * Service identifiers (message length)
155182007Sroberto */
156182007Sroberto#define REFACTS		"NIST"	/* NIST reference ID */
157280849Scy#define LENACTS		50	/* NIST format A */
158182007Sroberto#define REFUSNO		"USNO"	/* USNO reference ID */
159182007Sroberto#define LENUSNO		20	/* USNO */
160182007Sroberto#define REFPTB		"PTB\0"	/* PTB/NPL reference ID */
161182007Sroberto#define LENPTB		78	/* PTB/NPL format */
162182007Sroberto#define REFWWVB		"WWVB"	/* WWVB reference ID */
163182007Sroberto#define	LENWWVB0	22	/* WWVB format 0 */
164182007Sroberto#define	LENWWVB2	24	/* WWVB format 2 */
165182007Sroberto#define LF		0x0a	/* ASCII LF */
16654359Sroberto
16754359Sroberto/*
168280849Scy * Modem setup strings. These may have to be changed for
169280849Scy * some modems.
17054359Sroberto *
17154359Sroberto * AT	command prefix
172182007Sroberto * B1	US answer tone
173182007Sroberto * &C0	disable carrier detect
17454359Sroberto * &D2	hang up and return to command mode on DTR transition
17554359Sroberto * E0	modem command echo disabled
176280849Scy * L1	set modem speaker volume to low level
177182007Sroberto * M1	speaker enabled until carrier detect
17854359Sroberto * Q0	return result codes
17954359Sroberto * V1	return result codes as English words
180280849Scy * Y1	enable long-space disconnect
18154359Sroberto */
182280849Scyconst char def_modem_setup[] = "ATB1&C0&D2E0L1M1Q0V1Y1";
183280849Scyconst char *modem_setup = def_modem_setup;
18454359Sroberto
18554359Sroberto/*
186182007Sroberto * Timeouts (all in seconds)
18754359Sroberto */
188182007Sroberto#define SETUP		3	/* setup timeout */
189280849Scy#define	REDIAL		30	/* redial timeout */
190182007Sroberto#define ANSWER		60	/* answer timeout */
191280849Scy#define TIMECODE	60	/* message timeout */
192280849Scy#define	MAXCODE		20	/* max timecodes */
19354359Sroberto
19454359Sroberto/*
195182007Sroberto * State machine codes
19654359Sroberto */
197280849Scytypedef enum {
198280849Scy	S_IDLE,			/* wait for poll */
199280849Scy	S_SETUP,		/* send modem setup */
200280849Scy	S_CONNECT,		/* wait for answer */
201280849Scy	S_MSG			/* wait for timecode */
202280849Scy} teModemState;
20354359Sroberto
20454359Sroberto/*
20554359Sroberto * Unit control structure
20654359Sroberto */
20754359Srobertostruct actsunit {
208182007Sroberto	int	unit;		/* unit number */
20954359Sroberto	int	state;		/* the first one was Delaware */
210182007Sroberto	int	timer;		/* timeout counter */
211182007Sroberto	int	retry;		/* retry index */
212182007Sroberto	int	msgcnt;		/* count of messages received */
213182007Sroberto	l_fp	tstamp;		/* on-time timestamp */
214280849Scy	char	*bufptr;	/* next incoming char stored here */
215280849Scy	char	buf[BMAX];	/* bufptr roams within buf[] */
21654359Sroberto};
21754359Sroberto
21854359Sroberto/*
21954359Sroberto * Function prototypes
22054359Sroberto */
221280849Scystatic	int	acts_start	(int, struct peer *);
222280849Scystatic	void	acts_shutdown	(int, struct peer *);
223280849Scystatic	void	acts_receive	(struct recvbuf *);
224280849Scystatic	void	acts_message	(struct peer *, const char *);
225280849Scystatic	void	acts_timecode	(struct peer *, const char *);
226280849Scystatic	void	acts_poll	(int, struct peer *);
227280849Scystatic	void	acts_timeout	(struct peer *, teModemState);
228280849Scystatic	void	acts_timer	(int, struct peer *);
229280849Scystatic	void	acts_close	(struct peer *);
23054359Sroberto
23154359Sroberto/*
23254359Sroberto * Transfer vector (conditional structure name)
23354359Sroberto */
234280849Scystruct refclock refclock_acts = {
23554359Sroberto	acts_start,		/* start up driver */
23654359Sroberto	acts_shutdown,		/* shut down driver */
23754359Sroberto	acts_poll,		/* transmit poll message */
238182007Sroberto	noentry,		/* not used */
239182007Sroberto	noentry,		/* not used */
240182007Sroberto	noentry,		/* not used */
241182007Sroberto	acts_timer		/* housekeeping timer */
24254359Sroberto};
24354359Sroberto
24454359Sroberto/*
245182007Sroberto * Initialize data for processing
24654359Sroberto */
24754359Srobertostatic int
248280849Scyacts_start(
249182007Sroberto	int	unit,
25054359Sroberto	struct peer *peer
25154359Sroberto	)
25254359Sroberto{
253182007Sroberto	struct actsunit *up;
25454359Sroberto	struct refclockproc *pp;
255280849Scy	const char *setup;
25654359Sroberto
25754359Sroberto	/*
258182007Sroberto	 * Allocate and initialize unit structure
25954359Sroberto	 */
260280849Scy	up = emalloc_zero(sizeof(struct actsunit));
261182007Sroberto	up->unit = unit;
26254359Sroberto	pp = peer->procptr;
263280849Scy	pp->unitptr = up;
26454359Sroberto	pp->io.clock_recv = acts_receive;
265280849Scy	pp->io.srcclock = peer;
26654359Sroberto	pp->io.datalen = 0;
267280849Scy	pp->io.fd = -1;
26854359Sroberto
26954359Sroberto	/*
27054359Sroberto	 * Initialize miscellaneous variables
27154359Sroberto	 */
27254359Sroberto	peer->precision = PRECISION;
27354359Sroberto	pp->clockdesc = DESCRIPTION;
274280849Scy	memcpy(&pp->refid, REFID, 4);
27554359Sroberto	peer->sstclktype = CTL_SST_TS_TELEPHONE;
276280849Scy	up->bufptr = up->buf;
277280849Scy	if (def_modem_setup == modem_setup) {
278280849Scy		setup = get_ext_sys_var("modemsetup");
279280849Scy		if (setup != NULL)
280280849Scy			modem_setup = estrdup(setup);
281280849Scy	}
282280849Scy
28354359Sroberto	return (1);
28454359Sroberto}
28554359Sroberto
28654359Sroberto
28754359Sroberto/*
28854359Sroberto * acts_shutdown - shut down the clock
28954359Sroberto */
29054359Srobertostatic void
291280849Scyacts_shutdown(
292182007Sroberto	int	unit,
29354359Sroberto	struct peer *peer
29454359Sroberto	)
29554359Sroberto{
296182007Sroberto	struct actsunit *up;
29754359Sroberto	struct refclockproc *pp;
29854359Sroberto
299182007Sroberto	/*
300182007Sroberto	 * Warning: do this only when a call is not in progress.
301182007Sroberto	 */
30254359Sroberto	pp = peer->procptr;
303280849Scy	up = pp->unitptr;
304280849Scy	acts_close(peer);
30554359Sroberto	free(up);
30654359Sroberto}
30754359Sroberto
30854359Sroberto
30954359Sroberto/*
31054359Sroberto * acts_receive - receive data from the serial interface
31154359Sroberto */
31254359Srobertostatic void
313280849Scyacts_receive(
31454359Sroberto	struct recvbuf *rbufp
31554359Sroberto	)
31654359Sroberto{
317182007Sroberto	struct actsunit *up;
31854359Sroberto	struct refclockproc *pp;
31954359Sroberto	struct peer *peer;
320280849Scy	char	tbuf[sizeof(up->buf)];
321280849Scy	char *	tptr;
322280849Scy	int	octets;
323182007Sroberto
32454359Sroberto	/*
325182007Sroberto	 * Initialize pointers and read the timecode and timestamp. Note
326182007Sroberto	 * we are in raw mode and victim of whatever the terminal
327182007Sroberto	 * interface kicks up; so, we have to reassemble messages from
328182007Sroberto	 * arbitrary fragments. Capture the timecode at the beginning of
329182007Sroberto	 * the message and at the '*' and '#' on-time characters.
33054359Sroberto	 */
331280849Scy	peer = rbufp->recv_peer;
33254359Sroberto	pp = peer->procptr;
333280849Scy	up = pp->unitptr;
334280849Scy	octets = sizeof(up->buf) - (up->bufptr - up->buf);
335280849Scy	refclock_gtraw(rbufp, tbuf, octets, &pp->lastrec);
336182007Sroberto	for (tptr = tbuf; *tptr != '\0'; tptr++) {
337182007Sroberto		if (*tptr == LF) {
338280849Scy			if (up->bufptr == up->buf) {
339182007Sroberto				up->tstamp = pp->lastrec;
340182007Sroberto				continue;
341182007Sroberto			} else {
342182007Sroberto				*up->bufptr = '\0';
343280849Scy				up->bufptr = up->buf;
344280849Scy				acts_message(peer, up->buf);
345182007Sroberto			}
346280849Scy		} else if (!iscntrl((unsigned char)*tptr)) {
347182007Sroberto			*up->bufptr++ = *tptr;
348182007Sroberto			if (*tptr == '*' || *tptr == '#') {
349182007Sroberto				up->tstamp = pp->lastrec;
350280849Scy				if (write(pp->io.fd, tptr, 1) < 0)
351280849Scy					msyslog(LOG_ERR, "acts: write echo fails %m");
352182007Sroberto			}
353182007Sroberto		}
35454359Sroberto	}
355182007Sroberto}
356182007Sroberto
357182007Sroberto
358182007Sroberto/*
359182007Sroberto * acts_message - process message
360182007Sroberto */
361182007Srobertovoid
362182007Srobertoacts_message(
363280849Scy	struct peer *peer,
364280849Scy	const char *msg
365182007Sroberto	)
366182007Sroberto{
367182007Sroberto	struct actsunit *up;
368182007Sroberto	struct refclockproc *pp;
369280849Scy	char	tbuf[BMAX];
370280849Scy	int		dtr = TIOCM_DTR;
371182007Sroberto
372280849Scy	DPRINTF(1, ("acts: %d %s\n", (int)strlen(msg), msg));
373280849Scy
374182007Sroberto	/*
375182007Sroberto	 * What to do depends on the state and the first token in the
376280849Scy	 * message.
377182007Sroberto	 */
378182007Sroberto	pp = peer->procptr;
379280849Scy	up = pp->unitptr;
380280849Scy
381280849Scy	/*
382280849Scy	 * Extract the first token in the line.
383280849Scy	 */
384280849Scy	strlcpy(tbuf, msg, sizeof(tbuf));
385182007Sroberto	strtok(tbuf, " ");
386280849Scy	switch (up->state) {
38754359Sroberto
388182007Sroberto	/*
389182007Sroberto	 * We are waiting for the OK response to the modem setup
390280849Scy	 * command. When this happens, dial the number followed.
391280849Scy	 * If anything other than OK is received, just ignore it
392280849Scy	 * and wait for timeoue.
393182007Sroberto	 */
394280849Scy	case S_SETUP:
395182007Sroberto		if (strcmp(tbuf, "OK") != 0) {
396280849Scy			/*
397280849Scy			 * We disable echo with MODEM_SETUP's E0 but
398280849Scy			 * if the modem was previously E1, we will
399280849Scy			 * see MODEM_SETUP echoed before the OK/ERROR.
400280849Scy			 * Ignore it.
401280849Scy			 */
402280849Scy			if (!strcmp(tbuf, modem_setup))
403280849Scy				return;
404280849Scy			break;
405182007Sroberto		}
406280849Scy
407280849Scy		mprintf_event(PEVNT_CLOCK, peer, "DIAL #%d %s",
408280849Scy			      up->retry, sys_phone[up->retry]);
409280849Scy		if (ioctl(pp->io.fd, TIOCMBIS, &dtr) < 0)
410280849Scy			msyslog(LOG_ERR, "acts: ioctl(TIOCMBIS) failed: %m");
411280849Scy		if (write(pp->io.fd, sys_phone[up->retry],
412280849Scy		    strlen(sys_phone[up->retry])) < 0)
413280849Scy			msyslog(LOG_ERR, "acts: write DIAL fails %m");
414280849Scy		write(pp->io.fd, "\r", 1);
415280849Scy		up->retry++;
416280849Scy		up->state = S_CONNECT;
417280849Scy		up->timer = ANSWER;
41854359Sroberto		return;
41954359Sroberto
420182007Sroberto	/*
421280849Scy	 * We are waiting for the CONNECT response to the dial
422280849Scy	 * command. When this happens, listen for timecodes. If
423280849Scy	 * somthing other than CONNECT is received, like BUSY
424280849Scy	 * or NO CARRIER, abort the call.
425182007Sroberto	 */
426182007Sroberto	case S_CONNECT:
427280849Scy		if (strcmp(tbuf, "CONNECT") != 0)
428280849Scy			break;
429280849Scy
430280849Scy		report_event(PEVNT_CLOCK, peer, msg);
431280849Scy		up->state = S_MSG;
432280849Scy		up->timer = TIMECODE;
433280849Scy		return;
434280849Scy
435280849Scy	/*
436280849Scy	 * We are waiting for a timecode response. Pass it to
437280849Scy	 * the parser. If NO CARRIER is received, save the
438280849Scy	 * messages and abort the call.
439280849Scy	 */
440280849Scy	case S_MSG:
441280849Scy		if (strcmp(tbuf, "NO") == 0)
442280849Scy			report_event(PEVNT_CLOCK, peer, msg);
443280849Scy		if (up->msgcnt < MAXCODE)
444280849Scy			acts_timecode(peer, msg);
445280849Scy		else
446280849Scy			acts_timeout(peer, S_MSG);
447280849Scy		return;
448280849Scy	}
449280849Scy
450280849Scy	/*
451280849Scy	 * Other response. Tell us about it.
452280849Scy	 */
453280849Scy	report_event(PEVNT_CLOCK, peer, msg);
454280849Scy	acts_close(peer);
455280849Scy}
456280849Scy
457280849Scy
458280849Scy/*
459280849Scy * acts_timeout - called on timeout
460280849Scy */
461280849Scystatic void
462280849Scyacts_timeout(
463280849Scy	struct peer *peer,
464280849Scy	teModemState	dstate
465280849Scy	)
466280849Scy{
467280849Scy	struct actsunit *up;
468280849Scy	struct refclockproc *pp;
469280849Scy	int	fd;
470280849Scy	int	rc;
471280849Scy	char	device[20];
472280849Scy	char	lockfile[128], pidbuf[8];
473280849Scy
474280849Scy	/*
475280849Scy	 * The state machine is driven by messages from the modem,
476280849Scy	 * when first started and at timeout.
477280849Scy	 */
478280849Scy	pp = peer->procptr;
479280849Scy	up = pp->unitptr;
480280849Scy	switch (dstate) {
481280849Scy
482280849Scy	/*
483280849Scy	 * System poll event. Lock the modem port, open the device
484280849Scy	 * and send the setup command.
485280849Scy	 */
486280849Scy	case S_IDLE:
487280849Scy		if (-1 != pp->io.fd)
488280849Scy			return;		/* port is already open */
489280849Scy
490280849Scy		/*
491280849Scy		 * Lock the modem port. If busy, retry later. Note: if
492280849Scy		 * something fails between here and the close, the lock
493280849Scy		 * file may not be removed.
494280849Scy		 */
495280849Scy		if (pp->sloppyclockflag & CLK_FLAG2) {
496280849Scy			snprintf(lockfile, sizeof(lockfile), LOCKFILE,
497280849Scy			    up->unit);
498280849Scy			fd = open(lockfile, O_WRONLY | O_CREAT | O_EXCL,
499280849Scy			    0644);
500280849Scy			if (fd < 0) {
501280849Scy				report_event(PEVNT_CLOCK, peer, "acts: port busy");
502280849Scy				return;
503280849Scy			}
504280849Scy			snprintf(pidbuf, sizeof(pidbuf), "%d\n",
505280849Scy			    (u_int)getpid());
506280849Scy			if (write(fd, pidbuf, strlen(pidbuf)) < 0)
507280849Scy				msyslog(LOG_ERR, "acts: write lock fails %m");
508280849Scy			close(fd);
509280849Scy		}
510280849Scy
511280849Scy		/*
512280849Scy		 * Open the device in raw mode and link the I/O.
513280849Scy		 */
514280849Scy		snprintf(device, sizeof(device), DEVICE,
515280849Scy		    up->unit);
516280849Scy		fd = refclock_open(device, SPEED232, LDISC_ACTS |
517280849Scy		    LDISC_RAW | LDISC_REMOTE);
518280849Scy		if (fd < 0) {
519280849Scy			msyslog(LOG_ERR, "acts: open fails %m");
52054359Sroberto			return;
52154359Sroberto		}
522280849Scy		pp->io.fd = fd;
523280849Scy		if (!io_addclock(&pp->io)) {
524280849Scy			msyslog(LOG_ERR, "acts: addclock fails");
525280849Scy			close(fd);
526280849Scy			pp->io.fd = -1;
527280849Scy			return;
528280849Scy		}
529280849Scy		up->msgcnt = 0;
530280849Scy		up->bufptr = up->buf;
531280849Scy
532280849Scy		/*
533280849Scy		 * If the port is directly connected to the device, skip
534280849Scy		 * the modem business and send 'T' for Spectrabum.
535280849Scy		 */
536280849Scy		if (sys_phone[up->retry] == NULL) {
537280849Scy			if (write(pp->io.fd, "T", 1) < 0)
538280849Scy				msyslog(LOG_ERR, "acts: write T fails %m");
539280849Scy			up->state = S_MSG;
540280849Scy			up->timer = TIMECODE;
541280849Scy			return;
542280849Scy		}
543280849Scy
544280849Scy		/*
545280849Scy		 * Initialize the modem. This works with Hayes-
546280849Scy		 * compatible modems.
547280849Scy		 */
548280849Scy		mprintf_event(PEVNT_CLOCK, peer, "SETUP %s",
549280849Scy			      modem_setup);
550280849Scy		rc = write(pp->io.fd, modem_setup, strlen(modem_setup));
551280849Scy		if (rc < 0)
552280849Scy			msyslog(LOG_ERR, "acts: write SETUP fails %m");
553280849Scy		write(pp->io.fd, "\r", 1);
554280849Scy		up->state = S_SETUP;
555280849Scy		up->timer = SETUP;
55654359Sroberto		return;
55754359Sroberto
558182007Sroberto	/*
559280849Scy	 * In SETUP state the modem did not respond OK to setup string.
560182007Sroberto	 */
561280849Scy	case S_SETUP:
562280849Scy		report_event(PEVNT_CLOCK, peer, "no modem");
563280849Scy		break;
564280849Scy
565280849Scy	/*
566280849Scy	 * In CONNECT state the call did not complete. Abort the call.
567280849Scy	 */
568280849Scy	case S_CONNECT:
569280849Scy		report_event(PEVNT_CLOCK, peer, "no answer");
570280849Scy		break;
571280849Scy
572280849Scy	/*
573280849Scy	 * In MSG states no further timecodes are expected. If any
574280849Scy	 * timecodes have arrived, update the clock. In any case,
575280849Scy	 * terminate the call.
576280849Scy	 */
577182007Sroberto	case S_MSG:
578280849Scy		if (up->msgcnt == 0) {
579280849Scy			report_event(PEVNT_CLOCK, peer, "no timecodes");
580280849Scy		} else {
581280849Scy			pp->lastref = pp->lastrec;
582280849Scy			record_clock_stats(&peer->srcadr, pp->a_lastcode);
583280849Scy			refclock_receive(peer);
584280849Scy		}
585182007Sroberto		break;
58654359Sroberto	}
587280849Scy	acts_close(peer);
588182007Sroberto}
58954359Sroberto
590280849Scy
591182007Sroberto/*
592280849Scy * acts_close - close and prepare for next call.
593280849Scy *
594280849Scy * In ClOSE state no further protocol actions are required
595280849Scy * other than to close and release the device and prepare to
596280849Scy * dial the next number if necessary.
597280849Scy */
598280849Scyvoid
599280849Scyacts_close(
600280849Scy	struct peer *peer
601280849Scy	)
602280849Scy{
603280849Scy	struct actsunit *up;
604280849Scy	struct refclockproc *pp;
605280849Scy	char	lockfile[128];
606280849Scy	int	dtr;
607280849Scy
608280849Scy	pp = peer->procptr;
609280849Scy	up = pp->unitptr;
610280849Scy	if (pp->io.fd != -1) {
611280849Scy		report_event(PEVNT_CLOCK, peer, "close");
612280849Scy		dtr = TIOCM_DTR;
613280849Scy		if (ioctl(pp->io.fd, TIOCMBIC, &dtr) < 0)
614280849Scy			msyslog(LOG_ERR, "acts: ioctl(TIOCMBIC) failed: %m");
615280849Scy		io_closeclock(&pp->io);
616280849Scy		pp->io.fd = -1;
617280849Scy	}
618280849Scy	if (pp->sloppyclockflag & CLK_FLAG2) {
619280849Scy		snprintf(lockfile, sizeof(lockfile),
620280849Scy		    LOCKFILE, up->unit);
621280849Scy		unlink(lockfile);
622280849Scy	}
623280849Scy	if (up->msgcnt == 0 && up->retry > 0) {
624280849Scy		if (sys_phone[up->retry] != NULL) {
625280849Scy			up->state = S_IDLE;
626280849Scy			up->timer = REDIAL;
627280849Scy			return;
628280849Scy		}
629280849Scy	}
630280849Scy	up->state = S_IDLE;
631280849Scy	up->timer = 0;
632280849Scy}
633280849Scy
634280849Scy
635280849Scy/*
636280849Scy * acts_poll - called by the transmit routine
637280849Scy */
638280849Scystatic void
639280849Scyacts_poll(
640280849Scy	int	unit,
641280849Scy	struct peer *peer
642280849Scy	)
643280849Scy{
644280849Scy	struct actsunit *up;
645280849Scy	struct refclockproc *pp;
646280849Scy
647280849Scy	/*
648280849Scy	 * This routine is called at every system poll. All it does is
649280849Scy	 * set flag1 under certain conditions. The real work is done by
650280849Scy	 * the timeout routine and state machine.
651280849Scy	 */
652280849Scy	pp = peer->procptr;
653280849Scy	up = pp->unitptr;
654280849Scy	switch (peer->ttl) {
655280849Scy
656280849Scy	/*
657280849Scy	 * In manual mode the calling program is activated by the ntpdc
658280849Scy	 * program using the enable flag (fudge flag1), either manually
659280849Scy	 * or by a cron job.
660280849Scy	 */
661280849Scy	case MODE_MANUAL:
662280849Scy		return;
663280849Scy
664280849Scy	/*
665280849Scy	 * In automatic mode the calling program runs continuously at
666280849Scy	 * intervals determined by the poll event or specified timeout.
667280849Scy	 */
668280849Scy	case MODE_AUTO:
669280849Scy		break;
670280849Scy
671280849Scy	/*
672280849Scy	 * In backup mode the calling program runs continuously as long
673280849Scy	 * as either no peers are available or this peer is selected.
674280849Scy	 */
675280849Scy	case MODE_BACKUP:
676280849Scy		if (!(sys_peer == NULL || sys_peer == peer))
677280849Scy			return;
678280849Scy
679280849Scy		break;
680280849Scy	}
681280849Scy	pp->polls++;
682280849Scy	if (S_IDLE == up->state) {
683280849Scy		up->retry = 0;
684280849Scy		acts_timeout(peer, S_IDLE);
685280849Scy	}
686280849Scy}
687280849Scy
688280849Scy
689280849Scy/*
690280849Scy * acts_timer - called at one-second intervals
691280849Scy */
692280849Scystatic void
693280849Scyacts_timer(
694280849Scy	int	unit,
695280849Scy	struct peer *peer
696280849Scy	)
697280849Scy{
698280849Scy	struct actsunit *up;
699280849Scy	struct refclockproc *pp;
700280849Scy
701280849Scy	/*
702280849Scy	 * This routine implments a timeout which runs for a programmed
703280849Scy	 * interval. The counter is initialized by the state machine and
704280849Scy	 * counts down to zero. Upon reaching zero, the state machine is
705280849Scy	 * called. If flag1 is set while timer is zero, force a call.
706280849Scy	 */
707280849Scy	pp = peer->procptr;
708280849Scy	up = pp->unitptr;
709280849Scy	if (up->timer == 0) {
710280849Scy		if (pp->sloppyclockflag & CLK_FLAG1) {
711280849Scy			pp->sloppyclockflag &= ~CLK_FLAG1;
712280849Scy			acts_timeout(peer, S_IDLE);
713280849Scy		}
714280849Scy	} else {
715280849Scy		up->timer--;
716280849Scy		if (up->timer == 0)
717280849Scy			acts_timeout(peer, up->state);
718280849Scy	}
719280849Scy}
720280849Scy
721280849Scy/*
722182007Sroberto * acts_timecode - identify the service and parse the timecode message
723182007Sroberto */
724182007Srobertovoid
725182007Srobertoacts_timecode(
726280849Scy	struct peer *	peer,	/* peer structure pointer */
727280849Scy	const char *	str	/* timecode string */
728182007Sroberto	)
729182007Sroberto{
730182007Sroberto	struct actsunit *up;
731182007Sroberto	struct refclockproc *pp;
732182007Sroberto	int	day;		/* day of the month */
733182007Sroberto	int	month;		/* month of the year */
734182007Sroberto	u_long	mjd;		/* Modified Julian Day */
735182007Sroberto	double	dut1;		/* DUT adjustment */
736182007Sroberto
737182007Sroberto	u_int	dst;		/* ACTS daylight/standard time */
738182007Sroberto	u_int	leap;		/* ACTS leap indicator */
739182007Sroberto	double	msADV;		/* ACTS transmit advance (ms) */
740182007Sroberto	char	utc[10];	/* ACTS timescale */
741182007Sroberto	char	flag;		/* ACTS on-time character (* or #) */
742182007Sroberto
743182007Sroberto	char	synchar;	/* WWVB synchronized indicator */
744182007Sroberto	char	qualchar;	/* WWVB quality indicator */
745182007Sroberto	char	leapchar;	/* WWVB leap indicator */
746182007Sroberto	char	dstchar;	/* WWVB daylight/savings indicator */
747182007Sroberto	int	tz;		/* WWVB timezone */
748182007Sroberto
749280849Scy	int	leapmonth;	/* PTB/NPL month of leap */
750182007Sroberto	char	leapdir;	/* PTB/NPL leap direction */
751182007Sroberto
75254359Sroberto	/*
753182007Sroberto	 * The parser selects the modem format based on the message
754182007Sroberto	 * length. Since the data are checked carefully, occasional
755182007Sroberto	 * errors due noise are forgivable.
75654359Sroberto	 */
757182007Sroberto	pp = peer->procptr;
758280849Scy	up = pp->unitptr;
759182007Sroberto	pp->nsec = 0;
760280849Scy	switch (strlen(str)) {
76154359Sroberto
76254359Sroberto	/*
763182007Sroberto	 * For USNO format on-time character '*', which is on a line by
764182007Sroberto	 * itself. Be sure a timecode has been received.
76554359Sroberto	 */
766182007Sroberto	case 1:
767182007Sroberto		if (*str == '*' && up->msgcnt > 0)
768182007Sroberto			break;
769182007Sroberto
77054359Sroberto		return;
771182007Sroberto
77254359Sroberto	/*
773280849Scy	 * ACTS format A: "jjjjj yy-mm-dd hh:mm:ss ds l uuu aaaaa
774280849Scy	 * UTC(NIST) *".
77554359Sroberto	 */
776182007Sroberto	case LENACTS:
777182007Sroberto		if (sscanf(str,
778182007Sroberto		    "%5ld %2d-%2d-%2d %2d:%2d:%2d %2d %1d %3lf %5lf %9s %c",
779182007Sroberto		    &mjd, &pp->year, &month, &day, &pp->hour,
780182007Sroberto		    &pp->minute, &pp->second, &dst, &leap, &dut1,
781182007Sroberto		    &msADV, utc, &flag) != 13) {
782182007Sroberto			refclock_report(peer, CEVNT_BADREPLY);
783182007Sroberto			return;
784182007Sroberto		}
785182007Sroberto		pp->day = ymd2yd(pp->year, month, day);
786182007Sroberto		pp->leap = LEAP_NOWARNING;
787182007Sroberto		if (leap == 1)
788280849Scy			pp->leap = LEAP_ADDSECOND;
789280849Scy		else if (leap == 2)
790280849Scy			pp->leap = LEAP_DELSECOND;
791182007Sroberto		memcpy(&pp->refid, REFACTS, 4);
792182007Sroberto		up->msgcnt++;
793280849Scy		if (flag != '#' && up->msgcnt < 10)
794280849Scy			return;
795280849Scy
796182007Sroberto		break;
797182007Sroberto
79854359Sroberto	/*
799182007Sroberto	 * USNO format: "jjjjj nnn hhmmss UTC"
80054359Sroberto	 */
801182007Sroberto	case LENUSNO:
802182007Sroberto		if (sscanf(str, "%5ld %3d %2d%2d%2d %3s",
803182007Sroberto		    &mjd, &pp->day, &pp->hour, &pp->minute,
804182007Sroberto		    &pp->second, utc) != 6) {
805182007Sroberto			refclock_report(peer, CEVNT_BADREPLY);
806182007Sroberto			return;
807182007Sroberto		}
80854359Sroberto
809182007Sroberto		/*
810182007Sroberto		 * Wait for the on-time character, which follows in a
811182007Sroberto		 * separate message. There is no provision for leap
812182007Sroberto		 * warning.
813182007Sroberto		 */
814182007Sroberto		pp->leap = LEAP_NOWARNING;
815182007Sroberto		memcpy(&pp->refid, REFUSNO, 4);
816182007Sroberto		up->msgcnt++;
817280849Scy		break;
818182007Sroberto
81954359Sroberto	/*
820182007Sroberto	 * PTB/NPL format: "yyyy-mm-dd hh:mm:ss MEZ"
82154359Sroberto	 */
822182007Sroberto	case LENPTB:
823182007Sroberto		if (sscanf(str,
824182007Sroberto		    "%*4d-%*2d-%*2d %*2d:%*2d:%2d %*5c%*12c%4d%2d%2d%2d%2d%5ld%2lf%c%2d%3lf%*15c%c",
825182007Sroberto		    &pp->second, &pp->year, &month, &day, &pp->hour,
826182007Sroberto		    &pp->minute, &mjd, &dut1, &leapdir, &leapmonth,
827182007Sroberto		    &msADV, &flag) != 12) {
828182007Sroberto			refclock_report(peer, CEVNT_BADREPLY);
829182007Sroberto			return;
830182007Sroberto		}
831182007Sroberto		pp->leap = LEAP_NOWARNING;
832182007Sroberto		if (leapmonth == month) {
833182007Sroberto			if (leapdir == '+')
834280849Scy				pp->leap = LEAP_ADDSECOND;
835182007Sroberto			else if (leapdir == '-')
836280849Scy				pp->leap = LEAP_DELSECOND;
837182007Sroberto		}
838182007Sroberto		pp->day = ymd2yd(pp->year, month, day);
839182007Sroberto		memcpy(&pp->refid, REFPTB, 4);
840182007Sroberto		up->msgcnt++;
841182007Sroberto		break;
84282498Sroberto
843182007Sroberto
84482498Sroberto	/*
845182007Sroberto	 * WWVB format 0: "I  ddd hh:mm:ss DTZ=nn"
84682498Sroberto	 */
847182007Sroberto	case LENWWVB0:
848182007Sroberto		if (sscanf(str, "%c %3d %2d:%2d:%2d %cTZ=%2d",
849182007Sroberto		    &synchar, &pp->day, &pp->hour, &pp->minute,
850182007Sroberto		    &pp->second, &dstchar, &tz) != 7) {
851182007Sroberto			refclock_report(peer, CEVNT_BADREPLY);
85254359Sroberto			return;
85354359Sroberto		}
854182007Sroberto		pp->leap = LEAP_NOWARNING;
855182007Sroberto		if (synchar != ' ')
856182007Sroberto			pp->leap = LEAP_NOTINSYNC;
857182007Sroberto		memcpy(&pp->refid, REFWWVB, 4);
858182007Sroberto		up->msgcnt++;
859182007Sroberto		break;
860182007Sroberto
861182007Sroberto	/*
862182007Sroberto	 * WWVB format 2: "IQyy ddd hh:mm:ss.mmm LD"
863182007Sroberto	 */
864182007Sroberto	case LENWWVB2:
865182007Sroberto		if (sscanf(str, "%c%c%2d %3d %2d:%2d:%2d.%3ld%c%c%c",
866182007Sroberto		    &synchar, &qualchar, &pp->year, &pp->day,
867182007Sroberto		    &pp->hour, &pp->minute, &pp->second, &pp->nsec,
868182007Sroberto		    &dstchar, &leapchar, &dstchar) != 11) {
869182007Sroberto			refclock_report(peer, CEVNT_BADREPLY);
87054359Sroberto			return;
87154359Sroberto		}
872182007Sroberto		pp->nsec *= 1000000;
873182007Sroberto		pp->leap = LEAP_NOWARNING;
874182007Sroberto		if (synchar != ' ')
875182007Sroberto			pp->leap = LEAP_NOTINSYNC;
876182007Sroberto		else if (leapchar == 'L')
877182007Sroberto			pp->leap = LEAP_ADDSECOND;
878182007Sroberto		memcpy(&pp->refid, REFWWVB, 4);
879182007Sroberto		up->msgcnt++;
880182007Sroberto		break;
88154359Sroberto
88254359Sroberto	/*
883182007Sroberto	 * None of the above. Just forget about it and wait for the next
884182007Sroberto	 * message or timeout.
88554359Sroberto	 */
886182007Sroberto	default:
887182007Sroberto		return;
88854359Sroberto	}
88954359Sroberto
89054359Sroberto	/*
891182007Sroberto	 * We have a valid timecode. The fudge time1 value is added to
892182007Sroberto	 * each sample by the main line routines. Note that in current
893182007Sroberto	 * telephone networks the propatation time can be different for
894182007Sroberto	 * each call and can reach 200 ms for some calls.
89554359Sroberto	 */
896182007Sroberto	peer->refid = pp->refid;
897182007Sroberto	pp->lastrec = up->tstamp;
898280849Scy	if (up->msgcnt == 0)
899280849Scy		return;
900280849Scy
901280849Scy	strlcpy(pp->a_lastcode, str, sizeof(pp->a_lastcode));
902280849Scy	pp->lencode = strlen(pp->a_lastcode);
903182007Sroberto	if (!refclock_process(pp)) {
904182007Sroberto		refclock_report(peer, CEVNT_BADTIME);
905182007Sroberto		return;
906280849Scy	}
907132451Sroberto	pp->lastref = pp->lastrec;
90854359Sroberto}
90954359Sroberto#else
91054359Srobertoint refclock_acts_bs;
91154359Sroberto#endif /* REFCLOCK */
912